#include        "u.h"
#include        "../port/lib.h"
#include        "mem.h"
#include        "dat.h"
#include        "fns.h"
#include        "io.h"
#include        "../port/error.h"

#define Image   IMAGE
#include        <draw.h>
#include        <cursor.h>
#include        "screen.h"

enum
{
       /* controller command byte */
       Cscs1=          (1<<6),         /* scan code set 1 */
       Cmousedis=      (1<<5),         /* mouse disable */
       Ckbddis=        (1<<4),         /* kbd disable */
       Csf=            (1<<2),         /* system flag */
       Cmouseint=      (1<<1),         /* mouse interrupt enable */
       Ckbdint=        (1<<0),         /* kbd interrupt enable */

       /*
        *  responses from keyboard
        */
       Rok=            0xAA,           /* self test OK */
       Recho=          0xEE,           /* ??? */
       Rack=           0xFA,           /* command acknowledged */
       Rfail=          0xFC,           /* self test failed */
       Rresend=        0xFE,           /* ??? */
       Rovfl=          0xFF,           /* input overflow */

       /*
        *  status bits
        */
       Sobf=           1<<0,   /* output buffer full */
       Sibf=           1<<1,   /* input buffer full */
       Ssys=           1<<2,   /* set by self-test */
       Slast=          1<<3,   /* last access was to data */
       Senabled=       1<<4,   /* keyboard is enabled */
       Stxtimeout=     1<<5,   /* transmit to kybd has timed out */
       Srxtimeout=     1<<6,   /* receive from kybd has timed out */
       Sparity=        1<<7,   /* parity on byte was even */

       Spec=           0x80,

       PF=             Spec|0x20,      /* num pad function key */
       View=           Spec|0x00,      /* view (shift window up) */
       KF=             0xF000, /* function key (begin Unicode private space) */
       Shift=          Spec|0x60,
       Break=          Spec|0x61,
       Ctrl=           Spec|0x62,
       Latin=          Spec|0x63,
       Caps=           Spec|0x64,
       Num=            Spec|0x65,
       Middle=         Spec|0x66,
       No=             0x00,           /* peter */

       Home=           KF|13,
       Up=             KF|14,
       Pgup=           KF|15,
       Print=          KF|16,
       Left=           KF|17,
       Right=          KF|18,
       End=            '\r',
       Down=           View,
       Pgdown=         KF|19,
       Ins=            KF|20,
       Del=            0x7F,
};

#define KBDCTL  (*(uchar*)(KeyboardIO+Keyctl))
#define KBDDAT  (*(uchar*)(KeyboardIO+Keydat))
#define OUTWAIT while(KBDCTL & Sibf); kdbdly(1)
#define INWAIT  while(!(KBDCTL & Sobf)); kdbdly(1)
#define ACKWAIT INWAIT ; if(KBDDAT != Rack) print("bad response\n"); kdbdly(1)

Rune kbtab[] =
{
[0x00]  No,     0x1b,   '1',    '2',    '3',    '4',    '5',    '6',
[0x08]  '7',    '8',    '9',    '0',    '-',    '=',    '\b',   '\t',
[0x10]  'q',    'w',    'e',    'r',    't',    'y',    'u',    'i',
[0x18]  'o',    'p',    '[',    ']',    '\n',   Ctrl,   'a',    's',
[0x20]  'd',    'f',    'g',    'h',    'j',    'k',    'l',    ';',
[0x28]  '\'',   '`',    Shift,  '\\',   'z',    'x',    'c',    'v',
[0x30]  'b',    'n',    'm',    ',',    '.',    '/',    Shift,  '*',
[0x38]  Latin,  ' ',    Ctrl,   KF|1,   KF|2,   KF|3,   KF|4,   KF|5,
[0x40]  KF|6,   KF|7,   KF|8,   KF|9,   KF|10,  Num,    KF|12,  '7',
[0x48]  '8',    '9',    '-',    '4',    '5',    '6',    '+',    '1',
[0x50]  '2',    '3',    '0',    '.',    No,     No,     No,     KF|11,
[0x58]  KF|12,  No,     No,     No,     No,     No,     No,     No,
};

Rune kbtabshift[] =
{
[0x00]  No,     0x1b,   '!',    '@',    '#',    '$',    '%',    '^',
[0x08]  '&',    '*',    '(',    ')',    '_',    '+',    '\b',   '\t',
[0x10]  'Q',    'W',    'E',    'R',    'T',    'Y',    'U',    'I',
[0x18]  'O',    'P',    '{',    '}',    '\n',   Ctrl,   'A',    'S',
[0x20]  'D',    'F',    'G',    'H',    'J',    'K',    'L',    ':',
[0x28]  '"',    '~',    Shift,  '|',    'Z',    'X',    'C',    'V',
[0x30]  'B',    'N',    'M',    '<',    '>',    '?',    Shift,  '*',
[0x38]  Latin,  ' ',    Ctrl,   KF|1,   KF|2,   KF|3,   KF|4,   KF|5,
[0x40]  KF|6,   KF|7,   KF|8,   KF|9,   KF|10,  Num,    KF|12,  '7',
[0x48]  '8',    '9',    '-',    '4',    '5',    '6',    '+',    '1',
[0x50]  '2',    '3',    '0',    '.',    No,     No,     No,     KF|11,
[0x58]  KF|12,  No,     No,     No,     No,     No,     No,     No,
};

Rune kbtabesc1[] =
{
[0x00]  No,     No,     No,     No,     No,     No,     No,     No,
[0x08]  No,     No,     No,     No,     No,     No,     No,     No,
[0x10]  No,     No,     No,     No,     No,     No,     No,     No,
[0x18]  No,     No,     No,     No,     '\n',   Ctrl,   No,     No,
[0x20]  No,     No,     No,     No,     No,     No,     No,     No,
[0x28]  No,     No,     Shift,  No,     No,     No,     No,     No,
[0x30]  No,     No,     No,     No,     No,     '/',    No,     Print,
[0x38]  Latin,  No,     No,     No,     No,     No,     No,     No,
[0x40]  No,     No,     No,     No,     No,     No,     Break,  Home,
[0x48]  Up,     Pgup,   No,     Left,   No,     Right,  No,     End,
[0x50]  Down,   Pgdown, Ins,    Del,    No,     No,     No,     No,
[0x58]  No,     No,     No,     No,     No,     No,     No,     No,
};

struct Kbd
{
       Lock;
       int l;
} kbd;
static uchar ccc;

void
kdbdly(int l)
{
       int i;

       l *= 21;        /* experimentally determined */
       for(i=0; i<l; i++)
               ;
}

/*
*  wait for a keyboard event (or some max time)
*/
int
kbdwait(void)
{
       int tries;

       for(tries = 0; tries < 2000; tries++){
               if(KBDCTL & Sobf)
                       return 1;
               kdbdly(1);
       }
       return 0;
}

void
mouseintr(void)
{
       uchar c;
       static int nb;
       int buttons, dx, dy;
       static short msg[3];
       static uchar b[] = {0, 1, 4, 5, 2, 3, 6, 7, 0, 1, 2, 5, 2, 3, 6, 7 };

       kbdwait();
       c = KBDDAT;

       /*
        *  check byte 0 for consistency
        */
       if(nb==0 && (c&0xc8)!=0x08)
               return;

       msg[nb] = c;
       if(++nb == 3){
               nb = 0;
               if(msg[0] & 0x10)
                       msg[1] |= 0xFF00;
               if(msg[0] & 0x20)
                       msg[2] |= 0xFF00;

               buttons = b[msg[0]&7];
               dx = msg[1];
               dy = -msg[2];
               mousetrack(buttons, dx, dy);
       }
}

void
kbdintr(void)
{
       int c, i;
       static int esc1, esc2;
       static int caps;
       static int ctl;
       static int num;
       static int collecting, nk;
       static Rune kc[5];
       static int shift;
       int keyup;

       kbdwait();
       c = KBDDAT;

       /*
        *  e0's is the first of a 2 character sequence
        */
       if(c == 0xe0){
               esc1 = 1;
               return;
       } else if(c == 0xe1){
               esc2 = 2;
               return;
       }

       keyup = c&0x80;
       c &= 0x7f;
       if(c > sizeof kbtab){
               print("unknown key %ux\n", c|keyup);
               return;
       }

       if(esc1){
               c = kbtabesc1[c];
               esc1 = 0;
       }
       else if(esc2){
               esc2--;
               return;
       }
       else if(shift)
               c = kbtabshift[c];
       else
               c = kbtab[c];

       if(caps && c<='z' && c>='a')
               c += 'A' - 'a';

       /*
        *  keyup only important for shifts
        */
       if(keyup){
               switch(c){
               case Shift:
                       shift = 0;
                       break;
               case Ctrl:
                       ctl = 0;
                       break;
               }
               return;
       }
       /*
        *  normal character
        */
       if(!(c & Spec)){
               if(ctl)
                       c &= 0x1f;
               if(!collecting){
                       kbdputc(kbdq, c);
                       return;
               }
               kc[nk++] = c;
               c = latin1(kc, nk);
               if(c < -1)      /* need more keystrokes */
                       return;
               if(c != -1)     /* valid sequence */
                       kbdputc(kbdq, c);
               else    /* dump characters */
                       for(i=0; i<nk; i++)
                               kbdputc(kbdq, kc[i]);
               nk = 0;
               collecting = 0;
               return;
       }
       else {
               switch(c){
               case Caps:
                       caps ^= 1;
                       return;
               case Num:
                       num ^= 1;
                       return;
               case Shift:
                       shift = 1;
                       return;
               case Latin:
                       collecting = 1;
                       nk = 0;
                       return;
               case Ctrl:
                       ctl = 1;
                       return;
               }
       }
       kbdputc(kbdq, c);
}

void
lights(int l)
{
       USED(l);
}

static void
empty(void)
{
       int i;

       /*
        *  empty the buffer
        */
       kdbdly(20);
       while(KBDCTL & Sobf){
               i = KBDDAT;
               USED(i);
               kdbdly(1);
       }
}

/*
*  send a command to the mouse
*/
static int
mousecmd(int cmd)
{
       int tries;
       unsigned int c;

       c = 0;
       tries = 0;
       do{
               if(tries++ > 2)
                       break;
               OUTWAIT;
               KBDCTL = 0xD4;
               OUTWAIT;
               KBDDAT = cmd;
               OUTWAIT;
               kbdwait();
               c = KBDDAT;
       } while(c == 0xFE || c == 0);
       if(c != 0xFA){
               /*print("mouse returns %2.2ux to the %2.2ux command\n", c, cmd);/**/
               return -1;
       }
       return 0;
}

int
kbdinit(void)
{
       int i;

       kbdq = qopen(4*1024, 0, 0, 0);
       if(kbdq == nil)
               panic("kbdinit");
       qnoblock(kbdq, 1);

       /*
        *  empty the buffer
        */
       while(KBDCTL & Sobf){
               i = KBDDAT;
               USED(i);
       }


       /* wait for a quiescent controller */
       OUTWAIT;
       KBDCTL = 0x20;
       if(kbdwait() == 0) {
               print("kbdinit: can't read ccc\n");
               ccc = 0;
       } else
               ccc = KBDDAT;

       /* enable kbd xfers and interrupts */
       ccc &= ~Ckbddis;
       ccc |= Csf | Ckbdint | Cscs1 | Cmouseint;

       OUTWAIT;
       KBDCTL = 0x60;
       OUTWAIT;
       KBDDAT = ccc;
       OUTWAIT;

       mousecmd(0xEA); /* streaming */
       mousecmd(0xE8); /* set resolution */
       mousecmd(3);
       mousecmd(0xF4); /* enabled */

       return 1;
}

void
mousectl(char* field[], int)
{
       int s;

       if(strncmp(field[0], "reset", 5) == 0){
               s = splhi();
               mousecmd(0xF6);
               mousecmd(0xEA); /* streaming */
               mousecmd(0xE8); /* set resolution */
               mousecmd(3);
               mousecmd(0xF4); /* enabled */
               splx(s);
       }
       else if(strcmp(field[0], "accelerated") == 0){
               s = splhi();
               mousecmd(0xE7);
               splx(s);
       }
       else
               error(Ebadctl);
}