/*
 *
 * UNICON - The Console Chinese & I18N
 * Copyright (c) 1999-2000
 *
 * This file is part of UNICON, a console Chinese & I18N
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * See the file COPYING directory of this archive
 * Author: see CREDITS
 */

#include <linux/config.h>
#include <linux/version.h>
#include <linux/malloc.h>
#include <linux/sysctl.h>
#include <linux/swapctl.h>
#include <linux/proc_fs.h>
#include <linux/ctype.h>
#include <linux/utsname.h>
#include <linux/swapctl.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>       
#include <linux/wrapper.h>  
#include <linux/fb_doublebyte.h>
#include <linux/miscdevice.h>
                             
#include "unikey.h"
#include "xl_keyhooks.h"

#define  FAILURE   -1


#ifndef KERNEL_VERSION
#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))
#endif

#include <asm/uaccess.h>
#include "unikey.h"
#include "xl_hzfb.h"

//local variables
int Device_Open = 0;
static TTY_KEY_T ui[MAXTTYS];
short int bHasClosed[MAXTTYS];

static spinlock_t unikey_read_lock;
#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,3,0))
    static wait_queue_head_t inq;  /* read and write queues */
#else
    static struct wait_queue *inq;  /* read and write queues */
#endif

static int vt_has_resized = 0;
int nCurTty = 1;
int bFlushInput = 1;
HzFb_T our_fbhzinfo;

static int bFlushInputMethod = 0;
static int FlushInputMethodTty = 0;
static int FlushInputMethodFontType = 0;

static int device_open(struct inode *inode, struct file *file)
{
#ifdef DEBUG
	printk ("device_open(%p,%p)\n", inode, file);
#endif
//      if (Device_Open)
//          return -EBUSY;
	Device_Open++;
	MOD_INC_USE_COUNT;
	return SUCCESS;
}


static int device_release(struct inode *inode, struct file *file)
{
#ifdef DEBUG
	printk ("device_release(%p,%p)\n", inode, file);
#endif
 
	Device_Open --;

	MOD_DEC_USE_COUNT;

	return 0;
}

static ssize_t device_read(
	struct file *file,
	char *buffer,
	size_t length,
	loff_t *offset)
{
        int n; 
        unsigned long flags;
        static TTY_KEY_T a;
#ifdef DEBUG
	printk("device_read(%p,%p,%d)\n", file, buffer, length);
#endif
        spin_lock_irqsave(&unikey_read_lock, flags);
	copy_from_user(&a, (TTY_KEY_T *)buffer, sizeof(TTY_KEY_T));
        //n = nCurTty - 1;
        n = a.nTty;
        ui[n].nTty = a.nTty;
        if (bFlushInputMethod == 1)
        {
            if (bFlushInput == 1)
                ui[n].op |= FLUSH_BOTTOM;
            ui[n].op |= (FLUSH_INPUTMETHOD | FlushInputMethodFontType << 4);
            ui[n].nTty = FlushInputMethodTty;
	    copy_to_user((TTY_KEY_T *)buffer, &ui[n], sizeof(TTY_KEY_T)); 
            ui[n].nTotal = 0;
            ui[n].op = 0;
            bFlushInputMethod = 0;
        }    
        else if (n >= 0 && n < MAXTTYS)
        {
            if (bFlushInput == 1)
                ui[n].op |= FLUSH_BOTTOM;
	    copy_to_user((TTY_KEY_T *)buffer, &ui[n], sizeof(TTY_KEY_T)); 
            ui[n].nTotal = 0;
            ui[n].op = 0;
        }
        else
        {
            memset (&a, 0, sizeof (TTY_KEY_T));
	    copy_to_user((TTY_KEY_T *)buffer, &a, sizeof(TTY_KEY_T)); 
        }
        bFlushInput = 0;
        spin_unlock_irqrestore(&unikey_read_lock, flags);
	return sizeof (TTY_KEY_T);//SUCCESS;
}

static ssize_t device_write(
	struct file *file,
	const char *buffer,
	size_t length,
	loff_t *offset)
{
        static TTY_KEY_T a;
        int n;
        unsigned long flags;
#ifdef DEBUG
        printk ("device_write(%p,%s,%d)", file, buffer, length);
#endif
        spin_lock_irqsave(&unikey_read_lock, flags);
	copy_from_user(&a, (TTY_KEY_T *)buffer, sizeof(TTY_KEY_T));
        n = Unicon_fnSendKeys (a.nTty, a.buf, a.nTotal);
        spin_unlock_irqrestore(&unikey_read_lock, flags);
        return n;
}

static unsigned int device_poll (struct file *file, 
                                 struct poll_table_struct *poll_table)
{
    int i;
    unsigned long flags;

    spin_lock_irqsave(&unikey_read_lock, flags);
    i = nCurTty - 1;
    if (i >= 0 && i < MAXTTYS)
    {
        if (bFlushInput == 1 || ui[i].nTotal != 0 || bFlushInputMethod != 0)
        {
            spin_unlock_irqrestore(&unikey_read_lock, flags);
            return 1;
        }
    }
    poll_wait (file, &inq, poll_table);
    spin_unlock_irqrestore(&unikey_read_lock, flags);
    return 0;
}
 
void device_put_ascii (AsciiPut_T * p)
{
    AsciiPut_T a;
    unsigned long flags;
    spin_lock_irqsave(&unikey_read_lock, flags);
    if (bHasClosed [nCurTty - 1] == 1)
    {
        spin_unlock_irqrestore(&unikey_read_lock, flags);
        return;
    }
    copy_from_user(&a, p, sizeof(AsciiPut_T));
    FbPutAscii (&our_fbhzinfo, a.x, a.y, a.cl, a.ch); 
    spin_unlock_irqrestore(&unikey_read_lock, flags);
}

void device_put_chinese (ChinesePut_T * p)
{
    ChinesePut_T a;
    unsigned long flags;

    spin_lock_irqsave(&unikey_read_lock, flags);
    if (bHasClosed [nCurTty - 1] == 1)
    {
       spin_unlock_irqrestore(&unikey_read_lock, flags);
       return;
    }
    copy_from_user(&a, p, sizeof(ChinesePut_T));
    FbPutChinese (&our_fbhzinfo, a.x, a.y, a.cl, a.c1, a.c2); 
    spin_unlock_irqrestore(&unikey_read_lock, flags);
}

void  device_cls_input_bottom (char *cl)
{
    unsigned color;
    unsigned long flags;

    spin_lock_irqsave(&unikey_read_lock, flags);
    if (bHasClosed [nCurTty - 1] == 0)
        copy_from_user(&color, cl, sizeof(unsigned char));
    else
        color = 0;

    FbClearRect (&our_fbhzinfo, color,
                 our_fbhzinfo.height/16-1, 
                 our_fbhzinfo.height/16);
    spin_unlock_irqrestore(&unikey_read_lock, flags);
}

void  device_get_vt_info (VtInfo_T * p)
{
    VtInfo_T a;
    unsigned long flags;

    spin_lock_irqsave(&unikey_read_lock, flags);
    a.width = our_fbhzinfo.width;
    a.height = our_fbhzinfo.height;
    a.vt_has_resized = vt_has_resized;
    copy_to_user(p, &a, sizeof(VtInfo_T)); 
    spin_unlock_irqrestore(&unikey_read_lock, flags);
}

void device_set_current_tty (int *pnTty)
{
    int a;
    unsigned long flags;
    spin_lock_irqsave(&unikey_read_lock, flags);
    copy_from_user(&a, pnTty, sizeof(int));
    if (a >= 1 && a <= MAXTTYS)
        nCurTty = a;
    spin_unlock_irqrestore(&unikey_read_lock, flags);
}

void device_set_resize_flag (void)
{
    vt_has_resized = 1;
}

void device_clear_resize_flag (void)
{
    vt_has_resized = 0;
}

void device_set_font (VtFont_T * p)
{
//    printk ("device_set_font::tty=%d,font_type=%d,input_method_notify=%d\n",
//             p->tty, p->font_type,p->input_method_notify);
    UniconFontManager->setfont (p->tty, p->font_type);
    if (p->input_method_notify == 1)
    {
        unsigned long flags;
        spin_lock_irqsave(&unikey_read_lock, flags);
        bFlushInputMethod = 1;
        FlushInputMethodTty = p->tty;
        FlushInputMethodFontType = p->font_type;
        // ui[p->tty].op = FLUSH_INPUTMETHOD | p->font_type << 4;
        // ui[nCurTty - 1].op = FLUSH_INPUTMETHOD | p->font_type << 4;
        // ui[nCurTty - 1].op = FLUSH_INPUTMETHOD | p->font_type << 4;
        wake_up_interruptible (&inq);
        spin_unlock_irqrestore(&unikey_read_lock, flags);
    }
}

int device_ioctl(
	struct inode *inode,
	struct file *file,
	unsigned int ioctl_num,
	unsigned long ioctl_param)
{
	int i;

	switch (ioctl_num) {
		case UNI_INPUT_GET_INFO:
			i = device_read(file, (char *)ioctl_param, 0, 0);
			break;
		case UNI_INPUT_SET_INFO:
			device_write(file, (char *)ioctl_param, 0, 0); 
			break;
                case UNI_INPUT_PUT_ASCII:
                        device_put_ascii ((AsciiPut_T *) ioctl_param);
                        break;
                case UNI_INPUT_PUT_CHINESE:
                        device_put_chinese ((ChinesePut_T *) ioctl_param);
                        break;
                case UNI_INPUT_CLS_BOTTOM:
                        device_cls_input_bottom ((char *) ioctl_param);
                        break;
                case UNI_INPUT_GET_VT_INFO:
                        device_get_vt_info ((VtInfo_T *) ioctl_param);
                        break;
                case UNI_INPUT_SET_CUR_TTY:
                        device_set_current_tty ((int *) ioctl_param);
                        break;
                case UNI_INPUT_SET_RESIZE_FLAG:
                        device_set_resize_flag ();
                        break;
                case UNI_INPUT_SET_UNRESIZE_FLAG:
                        device_clear_resize_flag ();
                        break;
                case UNI_SET_CURRENT_FONT:
                        device_set_font ((VtFont_T *) ioctl_param);
                        break;
		default:
			break;
	}
	return SUCCESS;
}

struct file_operations Fops = {
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0))
        THIS_MODULE,
#endif
	NULL,   		/* seek */
	device_read, 		/* read */
	device_write,		/* write */
	NULL,   		/* readdir */
        device_poll,   		/* select */
	device_ioctl,   	/* ioctl */
	NULL,   		/* mmap */
	device_open,		/* open */
	NULL,  			/* flush */
	device_release,  	/* close */
        NULL,                   // int (*fsync) ;
        NULL,                   // int (*fasync);
        NULL,                   // int (*lock) 
        NULL,                   // ssize_t (*readv) 
        NULL,                   // ssize_t (*writev) 
};

static struct miscdevice unikey_device = {
	MINOR_NUM,
	DEVICE_NAME,
	&Fops
};

extern void UniconFontManagerOpen ();
int init_module (void)
{
        int i;

        Device_Open = 0;
        for (i = 0; i < MAXTTYS; i++)
        {
            ui[i].nTty = i;
            ui[i].nTotal = 0;
            ui[i].op = FLUSH_BOTTOM;
            bHasClosed[i] = 0;
        }
	/* Register the character device */
	misc_register(&unikey_device);
        Unicon_InitTTY ();
        HzFbInit (&our_fbhzinfo);
        UniconFontManagerOpen ();
        printk ("Unikey module successfully installed.\n");
#ifdef DEBUG
        printk ("our fbhzinfo:\n");
        printk ("width = %d\n", our_fbhzinfo.width);
        printk ("height = %d\n", our_fbhzinfo.height);
        printk ("line_length = %d\n", our_fbhzinfo.line_length);
        printk ("bit_per_pixels = %d\n", our_fbhzinfo.bits_per_pixel);
        printk ("fb mem = 0x%x\n", our_fbhzinfo.fb_mem);
#endif
#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,3,0)) /* maybe it's wrong */
        spin_lock_init(&unikey_read_lock);
        init_waitqueue_head (&inq);
#endif
	return 0;
}

extern void UniconFontManagerClose ();
void cleanup_module (void)
{
        UniconFontManagerClose ();
        Unicon_ExitTTY ();
        HzFbExit (&our_fbhzinfo);

	// Unregister the device
	misc_deregister(&unikey_device);
        printk ("Unikey module successfully uninstalled.\n");
}

void WriteTtyKey (int nTTY, unsigned char ch)
{
    unsigned long flags;
#ifdef DEBUG
     printk ("ch = %c, 0x%x, TTY = %d\n", ch, ch, nTTY);
#endif
    if (Device_Open == 0) 
        Unicon_fnSendKey (ch, TTY_NORMAL);
    else 
    {
        spin_lock_irqsave(&unikey_read_lock, flags);
        if (ui[nTTY-1].nTotal > MAX_CHAR)
        {
            spin_unlock_irqrestore(&unikey_read_lock, flags);
            return;
        }
        ui[nTTY-1].buf[ui[nTTY-1].nTotal++] = ch;
        wake_up_interruptible (&inq); 
        spin_unlock_irqrestore(&unikey_read_lock, flags);
    }
}

void OnTtyChangeUpdate (int nTty)
{
    unsigned long flags;
#ifdef DEBUG
    printk (KERN_DEBUG"New Tty = %d\n", nTty);
#endif
    if ((nTty < MAXTTYS && nTty >= 0))
    {
        spin_lock_irqsave(&unikey_read_lock, flags);
        nCurTty = nTty + 1;
        bFlushInput = 1;
        ui[nTty].op = FLUSH_BOTTOM;
        wake_up_interruptible (&inq);
        spin_unlock_irqrestore(&unikey_read_lock, flags);
    }
    else
        nCurTty = nTty + 1;
}
