/* keyboard.c: Routines for dealing with the Spectrum's keyboard
   Copyright (c) 1999-2017 Philip Kendall

   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.,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

   Author contact information:

   E-mail: philip-fuse@shadowmagic.org.uk

*/

#include "config.h"

#ifdef HAVE_LIB_GLIB
#include <glib.h>
#endif				/* #ifdef HAVE_LIB_GLIB */

#include "libspectrum.h"

#include "infrastructure/startup_manager.h"
#include "keyboard.h"
#include "ui/ui.h"

/* Bit masks for each of the eight keyboard half-rows; `AND' the selected
   ones of these to get the value to return
*/
libspectrum_byte keyboard_return_values[8];

/* The hash used for storing the UI -> Fuse input layer key mappings */
static GHashTable *keysyms_hash;

struct spectrum_keys_wrapper {
  input_key input;
  keyboard_spectrum_keys_t spectrum;
};

/* Which Spectrum keys should be pressed for each key passed from the
   Fuse input layer */
static struct spectrum_keys_wrapper spectrum_keys_table[] = {

  { INPUT_KEY_Escape,      { KEYBOARD_1,     KEYBOARD_Caps   } },
  { INPUT_KEY_1,           { KEYBOARD_1,     KEYBOARD_NONE   } },
  { INPUT_KEY_2,           { KEYBOARD_2,     KEYBOARD_NONE   } },
  { INPUT_KEY_3,           { KEYBOARD_3,     KEYBOARD_NONE   } },
  { INPUT_KEY_4,           { KEYBOARD_4,     KEYBOARD_NONE   } },
  { INPUT_KEY_5,           { KEYBOARD_5,     KEYBOARD_NONE   } },
  { INPUT_KEY_6,           { KEYBOARD_6,     KEYBOARD_NONE   } },
  { INPUT_KEY_7,           { KEYBOARD_7,     KEYBOARD_NONE   } },
  { INPUT_KEY_8,           { KEYBOARD_8,     KEYBOARD_NONE   } },
  { INPUT_KEY_9,           { KEYBOARD_9,     KEYBOARD_NONE   } },
  { INPUT_KEY_0,           { KEYBOARD_0,     KEYBOARD_NONE   } },
  { INPUT_KEY_minus,       { KEYBOARD_j,     KEYBOARD_Symbol } },
  { INPUT_KEY_equal,       { KEYBOARD_l,     KEYBOARD_Symbol } },
  { INPUT_KEY_BackSpace,   { KEYBOARD_0,     KEYBOARD_Caps   } },

  { INPUT_KEY_Tab,         { KEYBOARD_Caps,  KEYBOARD_Symbol } },
  { INPUT_KEY_q,           { KEYBOARD_q,     KEYBOARD_NONE   } },
  { INPUT_KEY_w,           { KEYBOARD_w,     KEYBOARD_NONE   } },
  { INPUT_KEY_e,           { KEYBOARD_e,     KEYBOARD_NONE   } },
  { INPUT_KEY_r,           { KEYBOARD_r,     KEYBOARD_NONE   } },
  { INPUT_KEY_t,           { KEYBOARD_t,     KEYBOARD_NONE   } },
  { INPUT_KEY_y,           { KEYBOARD_y,     KEYBOARD_NONE   } },
  { INPUT_KEY_u,           { KEYBOARD_u,     KEYBOARD_NONE   } },
  { INPUT_KEY_i,           { KEYBOARD_i,     KEYBOARD_NONE   } },
  { INPUT_KEY_o,           { KEYBOARD_o,     KEYBOARD_NONE   } },
  { INPUT_KEY_p,           { KEYBOARD_p,     KEYBOARD_NONE   } },

  { INPUT_KEY_Caps_Lock,   { KEYBOARD_2,     KEYBOARD_Caps   } },
  { INPUT_KEY_a,           { KEYBOARD_a,     KEYBOARD_NONE   } },
  { INPUT_KEY_s,           { KEYBOARD_s,     KEYBOARD_NONE   } },
  { INPUT_KEY_d,           { KEYBOARD_d,     KEYBOARD_NONE   } },
  { INPUT_KEY_f,           { KEYBOARD_f,     KEYBOARD_NONE   } },
  { INPUT_KEY_g,           { KEYBOARD_g,     KEYBOARD_NONE   } },
  { INPUT_KEY_h,           { KEYBOARD_h,     KEYBOARD_NONE   } },
  { INPUT_KEY_j,           { KEYBOARD_j,     KEYBOARD_NONE   } },
  { INPUT_KEY_k,           { KEYBOARD_k,     KEYBOARD_NONE   } },
  { INPUT_KEY_l,           { KEYBOARD_l,     KEYBOARD_NONE   } },
  { INPUT_KEY_semicolon,   { KEYBOARD_o,     KEYBOARD_Symbol } },
  { INPUT_KEY_apostrophe,  { KEYBOARD_7,     KEYBOARD_Symbol } },
  { INPUT_KEY_numbersign,  { KEYBOARD_3,     KEYBOARD_Symbol } },
  { INPUT_KEY_Return,      { KEYBOARD_Enter, KEYBOARD_NONE   } },

  { INPUT_KEY_Shift_L,     { KEYBOARD_NONE,  KEYBOARD_Caps   } },
  { INPUT_KEY_z,           { KEYBOARD_z,     KEYBOARD_NONE   } },
  { INPUT_KEY_x,           { KEYBOARD_x,     KEYBOARD_NONE   } },
  { INPUT_KEY_c,           { KEYBOARD_c,     KEYBOARD_NONE   } },
  { INPUT_KEY_v,           { KEYBOARD_v,     KEYBOARD_NONE   } },
  { INPUT_KEY_b,           { KEYBOARD_b,     KEYBOARD_NONE   } },
  { INPUT_KEY_n,           { KEYBOARD_n,     KEYBOARD_NONE   } },
  { INPUT_KEY_m,           { KEYBOARD_m,     KEYBOARD_NONE   } },
  { INPUT_KEY_comma,       { KEYBOARD_n,     KEYBOARD_Symbol } },
  { INPUT_KEY_period,      { KEYBOARD_m,     KEYBOARD_Symbol } },
  { INPUT_KEY_slash,       { KEYBOARD_v,     KEYBOARD_Symbol } },
  { INPUT_KEY_Shift_R,     { KEYBOARD_NONE,  KEYBOARD_Caps   } },

  { INPUT_KEY_asterisk,    { KEYBOARD_b,     KEYBOARD_Symbol } },
  { INPUT_KEY_dollar,      { KEYBOARD_4,     KEYBOARD_Symbol } },
  { INPUT_KEY_exclam,      { KEYBOARD_1,     KEYBOARD_Symbol } },
  { INPUT_KEY_less,        { KEYBOARD_r,     KEYBOARD_Symbol } },
  { INPUT_KEY_parenright,  { KEYBOARD_9,     KEYBOARD_Symbol } },
  { INPUT_KEY_colon,       { KEYBOARD_z,     KEYBOARD_Symbol } },
  { INPUT_KEY_plus,        { KEYBOARD_k,     KEYBOARD_Symbol } },

  { INPUT_KEY_Control_L,   { KEYBOARD_NONE,  KEYBOARD_Symbol } },
  { INPUT_KEY_Alt_L,       { KEYBOARD_NONE,  KEYBOARD_Symbol } },
  { INPUT_KEY_Meta_L,      { KEYBOARD_NONE,  KEYBOARD_Symbol } },
  { INPUT_KEY_Super_L,     { KEYBOARD_NONE,  KEYBOARD_Symbol } },
  { INPUT_KEY_Hyper_L,     { KEYBOARD_NONE,  KEYBOARD_Symbol } },
  { INPUT_KEY_space,       { KEYBOARD_space, KEYBOARD_NONE   } },
  { INPUT_KEY_Hyper_R,     { KEYBOARD_NONE,  KEYBOARD_Symbol } },
  { INPUT_KEY_Super_R,     { KEYBOARD_NONE,  KEYBOARD_Symbol } },
  { INPUT_KEY_Meta_R,      { KEYBOARD_NONE,  KEYBOARD_Symbol } },
  { INPUT_KEY_Alt_R,       { KEYBOARD_NONE,  KEYBOARD_Symbol } },
  { INPUT_KEY_Control_R,   { KEYBOARD_NONE,  KEYBOARD_Symbol } },
  { INPUT_KEY_Mode_switch, { KEYBOARD_NONE,  KEYBOARD_Symbol } },

  { INPUT_KEY_Left,        { KEYBOARD_5,     KEYBOARD_NONE   } },
  { INPUT_KEY_Down,        { KEYBOARD_6,     KEYBOARD_NONE   } },
  { INPUT_KEY_Up,          { KEYBOARD_7,     KEYBOARD_NONE   } },
  { INPUT_KEY_Right,       { KEYBOARD_8,     KEYBOARD_NONE   } },

  { INPUT_KEY_KP_Enter,    { KEYBOARD_Enter, KEYBOARD_NONE   } },

  { INPUT_KEY_NONE, { KEYBOARD_NONE, KEYBOARD_NONE } } /* End marker */

};

static GHashTable *spectrum_keys;

/* When each Spectrum key is pressed, twiddle this {port,bit} pair
   in `keyboard_return_values'. 
*/
struct key_bit {
  int port;
  libspectrum_byte bit;
};

struct key_info {
  keyboard_key_name key;
  struct key_bit bit;
};

static struct key_info keyboard_data_table[] = {

  { KEYBOARD_1,      { 3, 0x01 } },
  { KEYBOARD_2,      { 3, 0x02 } },
  { KEYBOARD_3,      { 3, 0x04 } },
  { KEYBOARD_4,      { 3, 0x08 } },
  { KEYBOARD_5,      { 3, 0x10 } },
  { KEYBOARD_6,      { 4, 0x10 } },
  { KEYBOARD_7,      { 4, 0x08 } },
  { KEYBOARD_8,      { 4, 0x04 } },
  { KEYBOARD_9,      { 4, 0x02 } },
  { KEYBOARD_0,      { 4, 0x01 } },

  { KEYBOARD_q,      { 2, 0x01 } },
  { KEYBOARD_w,      { 2, 0x02 } },
  { KEYBOARD_e,      { 2, 0x04 } },
  { KEYBOARD_r,      { 2, 0x08 } },
  { KEYBOARD_t,      { 2, 0x10 } },
  { KEYBOARD_y,      { 5, 0x10 } },
  { KEYBOARD_u,      { 5, 0x08 } },
  { KEYBOARD_i,      { 5, 0x04 } },
  { KEYBOARD_o,      { 5, 0x02 } },
  { KEYBOARD_p,      { 5, 0x01 } },

  { KEYBOARD_a,      { 1, 0x01 } },
  { KEYBOARD_s,      { 1, 0x02 } },
  { KEYBOARD_d,      { 1, 0x04 } },
  { KEYBOARD_f,      { 1, 0x08 } },
  { KEYBOARD_g,      { 1, 0x10 } },
  { KEYBOARD_h,      { 6, 0x10 } },
  { KEYBOARD_j,      { 6, 0x08 } },
  { KEYBOARD_k,      { 6, 0x04 } },
  { KEYBOARD_l,      { 6, 0x02 } },
  { KEYBOARD_Enter,  { 6, 0x01 } },

  { KEYBOARD_Caps,   { 0, 0x01 } },
  { KEYBOARD_z,      { 0, 0x02 } },
  { KEYBOARD_x,      { 0, 0x04 } },
  { KEYBOARD_c,      { 0, 0x08 } },
  { KEYBOARD_v,      { 0, 0x10 } },
  { KEYBOARD_b,      { 7, 0x10 } },
  { KEYBOARD_n,      { 7, 0x08 } },
  { KEYBOARD_m,      { 7, 0x04 } },
  { KEYBOARD_Symbol, { 7, 0x02 } },
  { KEYBOARD_space,  { 7, 0x01 } },

  { KEYBOARD_NONE,   { 0, 0x00 } }	/* End marker */

};

/* The hash used for storing keyboard_data_table */
static GHashTable *keyboard_data;

/* A textual name for each key */
struct key_text_t {
  keyboard_key_name key;
  const char *text;
};

struct key_text_t key_text_table[] = {

  { KEYBOARD_NONE, "Nothing" },

  { KEYBOARD_space, "Space" },

  { KEYBOARD_0, "0" },
  { KEYBOARD_1, "1" },
  { KEYBOARD_2, "2" },
  { KEYBOARD_3, "3" },
  { KEYBOARD_4, "4" },
  { KEYBOARD_5, "5" },
  { KEYBOARD_6, "6" },
  { KEYBOARD_7, "7" },
  { KEYBOARD_8, "8" },
  { KEYBOARD_9, "9" },

  { KEYBOARD_a, "A" },
  { KEYBOARD_b, "B" },
  { KEYBOARD_c, "C" },
  { KEYBOARD_d, "D" },
  { KEYBOARD_e, "E" },
  { KEYBOARD_f, "F" },
  { KEYBOARD_g, "G" },
  { KEYBOARD_h, "H" },
  { KEYBOARD_i, "I" },
  { KEYBOARD_j, "J" },
  { KEYBOARD_k, "K" },
  { KEYBOARD_l, "L" },
  { KEYBOARD_m, "M" },
  { KEYBOARD_n, "N" },
  { KEYBOARD_o, "O" },
  { KEYBOARD_p, "P" },
  { KEYBOARD_q, "Q" },
  { KEYBOARD_r, "R" },
  { KEYBOARD_s, "S" },
  { KEYBOARD_t, "T" },
  { KEYBOARD_u, "U" },
  { KEYBOARD_v, "V" },
  { KEYBOARD_w, "W" },
  { KEYBOARD_x, "X" },
  { KEYBOARD_y, "Y" },
  { KEYBOARD_z, "Z" },

  { KEYBOARD_Enter, "Enter" },
  { KEYBOARD_Caps, "Caps Shift" },
  { KEYBOARD_Symbol, "Symbol Shift" },

  { KEYBOARD_JOYSTICK_FIRE, "Joystick Fire" },

  { KEYBOARD_NONE, NULL },		/* End marker */

};

static GHashTable *key_text;

static int
keyboard_init( void *context )
{
  struct key_info *ptr;
  struct spectrum_keys_wrapper *ptr2;
  keysyms_map_t *ptr3;
  struct key_text_t *ptr4;

  keyboard_release_all();

  keyboard_data = g_hash_table_new( g_int_hash, g_int_equal );

  for( ptr = keyboard_data_table; ptr->key != KEYBOARD_NONE; ptr++ )
    g_hash_table_insert( keyboard_data, &( ptr->key ), &( ptr->bit ) );

  spectrum_keys = g_hash_table_new( g_int_hash, g_int_equal );

  for( ptr2 = spectrum_keys_table; ptr2->input != INPUT_KEY_NONE; ptr2++ )
    g_hash_table_insert( spectrum_keys,
			 &( ptr2->input ), &( ptr2->spectrum ) );

  keysyms_hash = g_hash_table_new( g_int_hash, g_int_equal );

  for( ptr3 = keysyms_map; ptr3->ui; ptr3++ )
    g_hash_table_insert( keysyms_hash, &( ptr3->ui ), &( ptr3->fuse ) );

  key_text = g_hash_table_new( g_int_hash, g_int_equal );

  for( ptr4 = key_text_table; ptr4->text != NULL; ptr4++ )
    g_hash_table_insert( key_text, &( ptr4->key ), &( ptr4->text ) );

  return 0;
}

static void
keyboard_end( void )
{
  g_hash_table_destroy( keyboard_data );
  g_hash_table_destroy( spectrum_keys );
  g_hash_table_destroy( keysyms_hash );
  g_hash_table_destroy( key_text );
}

void
keyboard_register_startup( void )
{
  startup_manager_module dependencies[] = {
    STARTUP_MANAGER_MODULE_LIBSPECTRUM,
    STARTUP_MANAGER_MODULE_SETUID
  };
  startup_manager_register( STARTUP_MANAGER_MODULE_KEYBOARD, dependencies,
                            ARRAY_SIZE( dependencies ), keyboard_init,
                            keyboard_end, NULL );
}

libspectrum_byte
keyboard_read( libspectrum_byte porth )
{
  libspectrum_byte data = 0xff; int i;

  for( i=0; i<8; i++,porth>>=1 ) {
    if(! (porth&0x01) ) data &= keyboard_return_values[i];
  }

  return data;

}

void
keyboard_press( keyboard_key_name key )
{
  struct key_bit *ptr;

  ptr = g_hash_table_lookup( keyboard_data, &key );

  if( ptr ) keyboard_return_values[ ptr->port ] &= ~( ptr->bit );
}

void
keyboard_release( keyboard_key_name key )
{
  struct key_bit *ptr;

  ptr = g_hash_table_lookup( keyboard_data, &key );

  if( ptr ) keyboard_return_values[ ptr->port ] |= ptr->bit;
}

int keyboard_release_all( void )
{
  int i;

  for( i=0; i<8; i++ ) keyboard_return_values[i] = 0xff;

  return 0;
}

const keyboard_spectrum_keys_t*
keyboard_get_spectrum_keys( input_key keysym )
{
  return g_hash_table_lookup( spectrum_keys, &keysym );
}

input_key
keysyms_remap( libspectrum_dword ui_keysym )
{
  const input_key *ptr;

  ptr = g_hash_table_lookup( keysyms_hash, &ui_keysym );

  return ptr ? *ptr : INPUT_KEY_NONE;
}

const char*
keyboard_key_text( keyboard_key_name key )
{
  const char **ptr;

  ptr = g_hash_table_lookup( key_text, &key );

  return ptr ? *ptr : "[Unknown key]";
}

libspectrum_byte
keyboard_simulate_keypress( libspectrum_byte porth, keyboard_key_name key )
{
  libspectrum_byte r = 0xff;
  struct key_bit *data;

  data = g_hash_table_lookup( keyboard_data, &key );

  if( data ) {
    libspectrum_byte mask = (1 << data->port);
    if( !(porth & mask) ) {
      r &= ~data->bit;
    }
  }

  return r;
}
