/**
 *
 * ChoX11: XLib replacement for RISC OS
 *
 * ChoX11 graphics queueing
 *
 * Copyright 2003 by Peter Naulls
 * Written by Peter Naulls and Alan Buckley
 * 
 * Initial keyboard handling by Alan Buckley
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation. No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or
 * implied warranty.
 *
 * Notes:
 *   Uses the Wimp keypress event to generate the X key press events
 *   Polls on NULL events after a key press for a key release event
 *
 *   Current limitations
 *     Key glyphs and inkey numbers are for standard keyboard only
 *     Modifier handling looks at modifiers when key event is received
 *     not when key is pressed.
 *     Doesn't handle CAPS LOCK at all, upper case letters are always
 *     reported as if shift was held down.
 *     Only supports key combinations reported in Wimp Keypress.
 *
 *     Alt+key is not supported for key release if Alt+Key would generate
 *     a wimp key code >= 128.
 *     Key release is only generated if key pressed is required as well.
 *     Ctrl+M is treated as the return key, but is then immediately released.
 *     Ctrl+H is treated as the backspace key, but is then immediately released.
 *     Keypad keys are treated a value on main keyboard, but then immediately
 *     released.
 */


#define XLIB_ILLEGAL_ACCESS
#define NeedFunctionPrototypes 1
#define NeedNestedPrototypes   1

#include <X11/Xlib.h> 
#include <X11/keysym.h>

#include "chox11.h"

#include "kernel.h"
#include "Desklib:Kbd.h"

#include <stdio.h>
#include <assert.h>

// Our internal key codes
#define KEY_F0         'A'
#define KEY_F1         'B'
#define KEY_INSERT     'N'
#define KEY_TAB        'O'
#define KEY_COPY       'P'
#define KEY_LEFTARROW  'Q'
#define KEY_RIGHTARROW 'R'
#define KEY_DOWNARROW  'S'
#define KEY_UPARROW    'T'
#define KEY_PAGEDOWN   'U'
#define KEY_PAGEUP     'V'
#define KEY_RETURN     'W'
#define KEY_BACKSPACE  'X'

static int Chox11Key_release_num = 0;
static int Chox11Key_release_max  = 0;
typedef struct {
  int inkey;
  int keycode;
} Chox11Key_ReleaseEvent;

static Chox11Key_ReleaseEvent *Chox11Key_release_list = NULL;


// Key code to inkey number table
// TODO: Support other keyboard layouts

#define IK_UNKNOWN 127

static char Chox11Key_KeyCodeToInkey[256] = {
  79, // Ctrl+@
  65,100,82,50,34,67,83,84,37,69,70,86,101, // Ctrl+a to Ctrl+m
  85,54,55,16,51,81,35,53,99,33,66,68,97,   // Ctrl+n to Ctrl+z
  112, // Escape
  IK_UNKNOWN, IK_UNKNOWN, IK_UNKNOWN, IK_UNKNOWN, // Char 28 to 31
  98,  // Space
  48,  // XK_exclam
  49,  // quotedbl
  120, // numbersign
  18,  // dollar
  19,  // percent
  36,  // ampersand
  79,  // apostrophe
  38,  // parenleft
  39,  // parenright
  21,  // asterisk
  93,  // plus
  102, // comma
  23,  // minus
  103, // period
  104, // slash
  39, 48, 49, 17, 18, 19, 52, 36, 21, 38, // Numbers 0 to 1
  72,  // colon
  72,  // semicolon
  102, // less
  93,  // equal
  103, // greater
  104, // question
  79,  // at
  IK_UNKNOWN, // Print (F0)
  113, 114, 115, 20, 116, 117, 22, 118, 119, 30, 38, 29, // F1 to F12
  61,  // Insert
  96,  // Tab
  105, // Copy
  25,  // Left Arrow
  121, // Right Arrow
  41,  // Down Arrow
  57,  // Up Arrow
  63,  // Page Down
  78,  // Page Up
  73,  // Return key
  47,  // Backspace key
  IK_UNKNOWN, IK_UNKNOWN, // Y to Z unused
  56,  // bracketleft
  94,  // backslash
  88,  // bracketright
  52,  // asciicircum
  23,  // underscore
  45,  // grave
  65,100,82,50,34,67,83,84,37,69,70,86,101, // a to m
  85,54,55,16,51,81,35,53,99,33,66,68,97,   // n to z
  56,  // braceleft
  94,  // bar
  88,  // braceright
  120, // asciitilde
  89,  // Delete
};

/* Keyboard information */
static char *Chox11Key_Glyphs      = "abcdefghijklmnopqrstuvwxyz1234567890`-=[];'#\\,./";
static char *Chox11Key_ShiftGlyphs = "ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"$%^&*()_+{}:@~|<>?";

/**
 * Fill in key event details from Wimp keypress event
 */

void Chox11Key_ConvertWimpKey(int key, XKeyEvent *xkey) { 

/* As keycodes can only be from 8 to 255 we use the upper
 * case letters codes for special keys and pass them on
 * as lower case with the shift modifier
 */
 char *p = strchr(Chox11Key_ShiftGlyphs, (char)key);

 if (p) {
    key = Chox11Key_Glyphs[p - Chox11Key_ShiftGlyphs];
    xkey->state |= ShiftMask;

  } else if (strchr(Chox11Key_Glyphs, (char)key)) {
    // Sanity check on unshifted glyphs
    xkey->state &= ~ShiftMask;
  } else if (key == 13) {
    key = KEY_RETURN;
  } else if (key == 8) {
    key = KEY_BACKSPACE;
  } else if (key >= 0x180) {
    // Special keys
    if ((key & 0xF) <= 0x0d) {
      if (key & 0x10) xkey->state |= ShiftMask;
      else xkey->state &= ~ShiftMask;
      if (key & 0x20) xkey->state |= ControlMask;
      else xkey->state &= ~ControlMask;

      key &= ~0x30; // Strip off the shift bits
      if (key >= 0x180 && key <= 0x189) {
        key = KEY_F0 + (key &0xF); // F0 to F9
      } else if (key >= 0x1CA && key <= 0x1CC) {
        key = KEY_F0 + (key &0xF); // F10 to F12
      } else if (key == 0x1CD) {
        key = KEY_INSERT; // Insert
      } else {
        key = KEY_TAB + key - 0x18A; // Tab, Copy, Left and right arrow
      }
    } else if (xkey->state & ShiftMask) {
      // Key codes are shares so use current shift state
      // to tell them apart.
      switch(key) {
         case 0x1AE:
         case 0x18E: key = KEY_PAGEDOWN;  break;
         case 0x1AF:
         case 0x18F: key = KEY_PAGEUP;    break;
         case 0x1BE:
         case 0x19E: key = KEY_DOWNARROW; break;
         case 0X1BF:
         case 0x19F: key = KEY_UPARROW;   break;
      }
    } else {
      switch(key) {
         case 0x1AE:
         case 0x18E: key = KEY_DOWNARROW;  break;
         case 0x1AF:
         case 0x18F: key = KEY_UPARROW;    break;
         case 0x1BE:
         case 0x19E: key = KEY_PAGEDOWN; break;
         case 0X1BF:
         case 0x19F: key = KEY_PAGEUP;   break;
      }
    }
  }

  xkey->keycode = key;
}

void Chox11Key_AddKeyRelease(XKeyEvent *xkey) {
  int inkey = IK_UNKNOWN;

  if (Chox11Key_release_num > 0) {
     // Only add the key release once (it could be generated multiple times by key repeat)
     int j;
     for (j = 0; j < Chox11Key_release_num; j++)
        if (Chox11Key_release_list[j].keycode == xkey->keycode) return;
  }
 
  if (xkey->keycode >= 0 && xkey->keycode < 128) inkey = Chox11Key_KeyCodeToInkey[xkey->keycode];
  if (inkey == IK_UNKNOWN) return;

  if (Chox11Key_release_num == Chox11Key_release_max) {
    void *temp = realloc(Chox11Key_release_list, sizeof(Chox11Key_ReleaseEvent) * (Chox11Key_release_max + 4));
    if (temp == NULL) return;
    Chox11Key_release_list = temp;
    Chox11Key_release_max += 4;
  }
  Chox11Key_release_list[Chox11Key_release_num].inkey = inkey ^ 0xFF;
  Chox11Key_release_list[Chox11Key_release_num].keycode = xkey->keycode;
  Chox11Key_release_num++;         
  if (Chox11Key_release_num == 1) Chox11_AddNullHandler(KEY_RELEASE_NULL_HANDLER);
}

void Chox11Key_CheckKeyRelease() {
  if (Chox11Key_release_num) {
    int j = 0;
    while (j < Chox11Key_release_num) {
      if (!Kbd_KeyDown(Chox11Key_release_list[j].inkey)) {
        int move_count = Chox11Key_release_num - j - 1;
        Chox11_CreateKeyReleaseEvent(Chox11Key_release_list[j].keycode);
  
        if (move_count) memmove(Chox11Key_release_list+j, Chox11Key_release_list+j+1, sizeof(Chox11Key_ReleaseEvent) * move_count);
        Chox11Key_release_num--;
      } else
        j++;
    }
    if (Chox11Key_release_num == 0) Chox11_RemoveNullHandler(KEY_RELEASE_NULL_HANDLER);
  }
}

void Chox11Key_FreeKeyRelease() {
  free(Chox11Key_release_list);
  Chox11Key_release_list = NULL;
  Chox11Key_release_num = 0;
  Chox11Key_release_max = 0;
}

KeySym XLookupKeysym(
    XKeyEvent*		 key_event,
    int			 index

) {
  KeySym ret_val = key_event->keycode;

  if (ret_val < 32) {
     if (key_event->state & ControlMask) {
        ret_val = XK_grave + ret_val;
     } else     
       ret_val = 0xFF00 + ret_val;
  } else if (ret_val >= KEY_F1 && ret_val < KEY_INSERT) {
       ret_val = XK_F1 + ret_val - KEY_F1;
  } else if (ret_val >= KEY_INSERT && ret_val <= KEY_BACKSPACE) {
    switch(ret_val) {
      case KEY_F0: ret_val = XK_Print; break;
      case KEY_INSERT: ret_val = XK_Insert; break;
      case KEY_TAB:    ret_val = XK_Tab; break;
      case KEY_COPY:   ret_val = XK_End; break;
      case KEY_LEFTARROW: ret_val = XK_Left; break;
      case KEY_RIGHTARROW: ret_val = XK_Right; break;
      case KEY_DOWNARROW: ret_val = XK_Down; break;
      case KEY_UPARROW:   ret_val = XK_Up; break;
      case KEY_PAGEDOWN:  ret_val = XK_Page_Down; break;
      case KEY_PAGEUP:    ret_val = XK_Page_Up; break;
      case KEY_RETURN:    ret_val = XK_Return; break;
      case KEY_BACKSPACE: ret_val = XK_BackSpace; break;
    }
  }

  if (index & 1) {
    char *p = strchr(Chox11Key_Glyphs, (char)ret_val);
    if (p) ret_val = Chox11Key_ShiftGlyphs[p - Chox11Key_Glyphs];
    else ret_val = NoSymbol;
  }
     
  return ret_val;
}

int XLookupString(

    XKeyEvent*		key_event,
    char*		buffer_return,
    int			bytes_buffer,
    KeySym*		keysym_return,
    XComposeStatus*	status_in_out

) {
  int keysym = XLookupKeysym(key_event,0);

  // Unlike XLookupKeysym the keysym returned takes into account the modifiers
  if (key_event->state & ShiftMask) {
      char *p = strchr(Chox11Key_Glyphs, (char)keysym);
      if (p) keysym = Chox11Key_ShiftGlyphs[p - Chox11Key_Glyphs];
    }

  if (keysym_return) *keysym_return = keysym;

//TODO: Check if key has been remapped

  if (buffer_return && bytes_buffer > 1) {
    if (keysym >= 32 && keysym <= 255) {
       buffer_return[0] = (char)keysym;
       buffer_return[1] = 0;
    } else buffer_return[0] = 0;
  }
  
//TODO:  if (status_in_out) *status_in_out = ??;
     
  return Success;
}
