/*
Copyright (C) 1994-1995  Apogee Software, Ltd.
Copyright (C) 2002-2015  icculus.org, GNU/Linux port
Copyright (C) 2017-2018  Steven LeVesque

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.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL2/SDL_scancode.h>
#include <SDL2/SDL_keycode.h>
#include <SDL2/SDL_mouse.h>

#if USE_SDL
#include "SDL2/SDL.h"
#endif

#include "rt_main.h"
#include "rt_def.h"
#include "rt_in.h"
#include "_rt_in.h"
#include "isr.h"
#include "rt_util.h"
#include "rt_vh_a.h"
#include "rt_cfg.h"
#include "rt_msg.h"
#include "rt_playr.h"
#include "rt_net.h"
#include "rt_com.h"
#include "rt_cfg.h"
#include "keyb.h"
#include "HashTable.h"
#include "modexlib.h"

#define MAXMESSAGELENGTH      (COM_MAXTEXTSTRINGLENGTH-1)

//****************************************************************************
//
// GLOBALS
//
//****************************************************************************]

//
// Used by menu routines that need to wait for a button release.
// Sometimes the mouse driver misses an interrupt, so you can't wait for
// a button to be released.  Instead, you must ignore any buttons that
// are pressed.
//
int IgnoreMouse = 0;

// configuration variables
//
boolean  MousePresent;
boolean  JoysPresent[MaxJoys];
boolean  JoyPadPresent     = 0;

//    Global variables
//
boolean  Paused;
char LastASCII;
volatile int LastScan;

byte Joy_xb,
     Joy_yb,
     Joy_xs,
     Joy_ys;
word Joy_x,
     Joy_y;


int LastLetter = 0;
char LetterQueue[MAXLETTERS];
ModemMessage MSG;


#if USE_SDL
static SDL_Joystick* sdl_joysticks[MaxJoys];
static int sdl_mouse_delta_x = 0;
static int sdl_mouse_delta_y = 0;
static word sdl_mouse_button_mask = 0;
static int sdl_total_sticks = 0;
static word *sdl_stick_button_state = NULL;
static word sdl_sticks_joybits = 0;
static int sdl_mouse_grabbed = 0;
//static unsigned int scancodes[SDL_NUM_SCANCODES]; //TODO: replace with a hashtable if possible
static HashTable * scancodes;
extern boolean sdl_fullscreen;
#endif


//   'q','w','e','r','t','y','u','i','o','p','[',']','\\', 0 ,'a','s',

const char ScanChars[128] =    // Scan code names with single chars
{
    0, 0,'1','2','3','4','5','6','7','8','9','0','-','=', 0, 0,
    'q','w','e','r','t','y','u','i','o','p','[',']', 0, 0,'a','s',
    'd','f','g','h','j','k','l',';','\'','`', 0,'\\','z','x','c','v',
    'b','n','m',',','.','/', 0, 0, 0,' ', 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'-', 0,'5', 0,'+', 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

const char ShiftedScanChars[128] =    // Shifted Scan code names with single chars
{
    0, 0,'!','@','#','$','%','^','&','*','(',')','_','+', 0, 0,
    'Q','W','E','R','T','Y','U','I','O','P','{','}', 0, 0,'A','S',
    'D','F','G','H','J','K','L',':','"','~', 0,'|','Z','X','C','V',
    'B','N','M','<','>','?', 0, 0, 0,' ', 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'-', 0,'5', 0,'+', 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

//****************************************************************************
//
// LOCALS
//
//****************************************************************************]

static KeyboardDef KbdDefs = {0x1d,0x38,0x47,0x48,0x49,0x4b,0x4d,0x4f,0x50,0x51};
static JoystickDef JoyDefs[MaxJoys];
static ControlType Controls[MAXPLAYERS];


static boolean  IN_Started;

static   Direction   DirTable[] =      // Quick lookup for total direction
{
    dir_NorthWest, dir_North,  dir_NorthEast,
    dir_West,      dir_None,   dir_East,
    dir_SouthWest, dir_South,  dir_SouthEast
};

int (far *function_ptr)();

static char *ParmStrings[] = {"nojoys","nomouse",NULL};


static int sdl_mouse_button_filter(SDL_Event const *event)
{
    /*
     * What DOS games expect:
     *  0	left button pressed if 1
     *  1	right button pressed if 1
     *  2	middle button pressed if 1
     *
     *   (That is, this is what Int 33h (AX=0x05) returns...)
     */

    Uint8 bmask = SDL_GetMouseState(NULL, NULL);
    sdl_mouse_button_mask = 0;  /* this is a static var. */
    if (bmask & SDL_BUTTON_LMASK) sdl_mouse_button_mask |= 1;
    if (bmask & SDL_BUTTON_RMASK) sdl_mouse_button_mask |= 2;
    if (bmask & SDL_BUTTON_MMASK) sdl_mouse_button_mask |= 4;
    return(0);
} /* sdl_mouse_up_filter */


static int sdl_mouse_motion_filter(SDL_Event const *event)
{
    static int mouse_x = 0;
    static int mouse_y = 0;
    int mouse_relative_x = 0;
    int mouse_relative_y = 0;

    if (event->type == SDL_JOYBALLMOTION)
    {
        mouse_relative_x = event->jball.xrel/100;
        mouse_relative_y = event->jball.yrel/100;
        mouse_x += mouse_relative_x;
        mouse_y += mouse_relative_y;
    } /* if */
    else
    {
        //if (sdl_mouse_grabbed || sdl_fullscreen)
        //{
            mouse_relative_x = event->motion.xrel;
            mouse_relative_y = event->motion.yrel;
            mouse_x += mouse_relative_x;
            mouse_y += mouse_relative_y;
        //} /* if */
        //else
        //{
            //mouse_relative_x = event->motion.x - mouse_x;
            //mouse_relative_y = event->motion.y - mouse_y;
            //mouse_x = event->motion.x;
            //mouse_y = event->motion.y;
        //} /* else */
    } /* else */

    /* set static vars... */
    sdl_mouse_delta_x += mouse_relative_x;
    sdl_mouse_delta_y += mouse_relative_y;

    return(0);
} /* sdl_mouse_motion_filter */


/**
 * Attempt to flip the video surface to fullscreen or windowed mode.
 *  Attempts to maintain the surface's state, but makes no guarantee
 *  that pointers (i.e., the surface's pixels field) will be the same
 *  after this call.
 *
 * Caveats: Your surface pointers will be changing; if you have any other
 *           copies laying about, they are invalidated.
 *
 *          Do NOT call this from an SDL event filter on Windows. You can
 *           call it based on the return values from SDL_PollEvent, etc, just
 *           not during the function you passed to SDL_SetEventFilter().
 *
 *          Thread safe? Likely not.
 *
 *   @param surface pointer to surface ptr to toggle. May be different
 *                  pointer on return. MAY BE NULL ON RETURN IF FAILURE!
 *   @param flags   pointer to flags to set on surface. The value pointed
 *                  to will be XOR'd with SDL_FULLSCREEN before use. Actual
 *                  flags set will be filled into pointer. Contents are
 *                  undefined on failure. Can be NULL, in which case the
 *                  surface's current flags are used.
 *  @return non-zero on success, zero on failure.
 */
/*
 * The windib driver can't alert us to the keypad enter key, which
 *  Ken's code depends on heavily. It sends it as the same key as the
 *  regular return key. These users will have to hit SHIFT-ENTER,
 *  which we check for explicitly, and give the engine a keypad enter
 *  enter event.
 */
static int handle_keypad_enter_hack(const SDL_Event *event)
{
    static int kp_enter_hack = 0;
    int retval = 0;

    if (event->key.keysym.sym == SDLK_RETURN)
    {
        if (event->key.state == SDL_PRESSED)
        {
            if (event->key.keysym.mod & KMOD_SHIFT)
            {
                kp_enter_hack = 1;
                retval = Lookup(scancodes, SDLK_KP_ENTER);
                //retval = scancodes[SDLK_KP_ENTER];
            } /* if */
        } /* if */

        else  /* key released */
        {
            if (kp_enter_hack)
            {
                kp_enter_hack = 0;
                //retval = scancodes = Lookup(scancodes, SDLK_KP_ENTER);
                retval = Lookup(scancodes, SDLK_KP_ENTER);

                //retval = scancodes[SDLK_KP_ENTER];
            } /* if */
        } /* if */
    } /* if */

    return(retval);
} /* handle_keypad_enter_hack */


static int sdl_key_filter(const SDL_Event *event)
{
    int k;
    int keyon;
    int strippedkey;
    //SDL_GrabMode grab_mode = SDL_GRAB_OFF;
    int extended;


    if ( (event->key.keysym.sym == SDLK_g) &&
            (event->key.state == SDL_PRESSED) &&
            (event->key.keysym.mod & KMOD_CTRL) )
    {
        //if (!sdl_fullscreen)
        //{
            //sdl_mouse_grabbed = ((sdl_mouse_grabbed) ? 0 : 1);
            //if (sdl_mouse_grabbed)
                //grab_mode = SDL_GRAB_ON;
            //SDL_WM_GrabInput(grab_mode);
        //}
        return(0);
    } /* if */

    else if ( ( (event->key.keysym.sym == SDLK_RETURN) ||
                (event->key.keysym.sym == SDLK_KP_ENTER) ) &&
              (event->key.state == SDL_PRESSED) &&
              (event->key.keysym.mod & KMOD_ALT) )
    {
        //if (SDL_WM_ToggleFullScreen(SDL_GetVideoSurface()))
            //sdl_fullscreen ^= 1;
        //return(0);
    } /* if */

    /* HDG: put this above the scancode lookup otherwise it is never reached */
    if ( (event->key.keysym.sym == SDLK_PAUSE) &&
            (event->key.state == SDL_PRESSED))
    {
        PausePressed = true;
        return(0);
    }

    k = handle_keypad_enter_hack(event);
    if (!k)
    {
        //if (event->key.keysym.sym == SDLK_a)
        //{
            //k = 0x1e;
        //}
        //else
        //{
            k = Lookup(scancodes, event->key.keysym.sym);
            //k = scancodes[event->key.keysym.sym];
        //}

        if (!k)   /* No DOS equivalent defined. */
            return(0);
    } /* if */

    /* Fix elweirdo SDL capslock/numlock handling, always treat as press */
    if ( (event->key.keysym.sym != SDLK_CAPSLOCK) &&
            (event->key.keysym.sym != SDLK_NUMLOCKCLEAR)  &&
            (event->key.state == SDL_RELEASED) )
        k += 128;  /* +128 signifies that the key is released in DOS. */

    if (event->key.keysym.sym == SDLK_SCROLLLOCK)
        PanicPressed = true;

    else
    {
        extended = ((k & 0xFF00) >> 8);

        keyon = k & 0x80;
        strippedkey = k & 0x7f;

        if (extended != 0)
        {
            KeyboardQueue[ Keytail ] = extended;
            Keytail = ( Keytail + 1 )&( KEYQMAX - 1 );
            k = Lookup(scancodes, event->key.keysym.sym) & 0xFF;
            //k = scancodes[event->key.keysym.sym] & 0xFF;
            if (event->key.state == SDL_RELEASED)
                k += 128;  /* +128 signifies that the key is released in DOS. */
        }

        if (keyon)        // Up event
            Keystate[strippedkey]=0;
        else                 // Down event
        {
            Keystate[strippedkey]=1;
            LastScan = k;
        }

        KeyboardQueue[ Keytail ] = k;
        Keytail = ( Keytail + 1 )&( KEYQMAX - 1 );
    }

    return(0);
} /* sdl_key_filter */


static int root_sdl_event_filter(const SDL_Event *event)
{
    switch (event->type)
    {
    case SDL_KEYUP:
    case SDL_KEYDOWN:
        return(sdl_key_filter(event));
    case SDL_JOYBALLMOTION:
    case SDL_MOUSEMOTION:
        return(sdl_mouse_motion_filter(event));
    case SDL_MOUSEBUTTONUP:
    case SDL_MOUSEBUTTONDOWN:
        return(sdl_mouse_button_filter(event));
    case SDL_QUIT:
        /* !!! rcg TEMP */
        fprintf(stderr, "\n\n\nSDL_QUIT!\n\n\n");
        SDL_Quit();
        exit(42);
        break;
    case SDL_WINDOWEVENT:
        sdl_handle_window_events();
        break;
    } /* switch */

    return(1);
} /* root_sdl_event_filter */


static void sdl_handle_events(void)
{
    SDL_Event event;
    while (SDL_PollEvent(&event))
        root_sdl_event_filter(&event);
} /* sdl_handle_events */


//******************************************************************************
//
// IN_PumpEvents () - Let platform process an event queue.
//
//******************************************************************************
void IN_PumpEvents(void)
{
    sdl_handle_events();
    /* no-op. */
}



//******************************************************************************
//
// INL_GetMouseDelta () - Gets the amount that the mouse has moved from the
//                        mouse driver
//
//******************************************************************************

void INL_GetMouseDelta(int *x,int *y)
{
    IN_PumpEvents();

    *x = sdl_mouse_delta_x;
    *y = sdl_mouse_delta_y;

    sdl_mouse_delta_x = sdl_mouse_delta_y = 0;

}



//******************************************************************************
//
// IN_GetMouseButtons () - Gets the status of the mouse buttons from the
//                         mouse driver
//
//******************************************************************************

word IN_GetMouseButtons
(
    void
)

{
    word buttons = 0;

    IN_PumpEvents();

    buttons = sdl_mouse_button_mask;

    return (buttons);
}


//******************************************************************************
//
// IN_IgnoreMouseButtons () -
//    Tells the mouse to ignore the currently pressed buttons.
//
//******************************************************************************

void IN_IgnoreMouseButtons
(
    void
)

{
    IgnoreMouse |= IN_GetMouseButtons();
}


//******************************************************************************
//
// IN_GetJoyAbs () - Reads the absolute position of the specified joystick
//
//******************************************************************************

void IN_GetJoyAbs (word joy, word *xp, word *yp)
{
    Joy_x  = Joy_y = 0;
    Joy_xs = joy? 2 : 0;       // Find shift value for x axis
    Joy_xb = 1 << Joy_xs;      // Use shift value to get x bit mask
    Joy_ys = joy? 3 : 1;       // Do the same for y axis
    Joy_yb = 1 << Joy_ys;
    if (joy < sdl_total_sticks)
    {
        Joy_x = SDL_JoystickGetAxis (sdl_joysticks[joy], 0);
        Joy_y = SDL_JoystickGetAxis (sdl_joysticks[joy], 1);
    } else {
        Joy_x = 0;
        Joy_y = 0;
    }

    *xp = Joy_x;
    *yp = Joy_y;
}

void JoyStick_Vals (void)
{

}


//******************************************************************************
//
// INL_GetJoyDelta () - Returns the relative movement of the specified
//                     joystick (from +/-127)
//
//******************************************************************************

void INL_GetJoyDelta (word joy, int *dx, int *dy)
{
    word        x, y;
    JoystickDef *def;

    IN_GetJoyAbs (joy, &x, &y);
    def = JoyDefs + joy;

    if (x < def->threshMinX)
    {
        if (x < def->joyMinX)
            x = def->joyMinX;

        x = -(x - def->threshMinX);
        x *= def->joyMultXL;
        x >>= JoyScaleShift;
        *dx = (x > 127)? -127 : -x;
    }
    else if (x > def->threshMaxX)
    {
        if (x > def->joyMaxX)
            x = def->joyMaxX;

        x = x - def->threshMaxX;
        x *= def->joyMultXH;
        x >>= JoyScaleShift;
        *dx = (x > 127)? 127 : x;
    }
    else
        *dx = 0;

    if (y < def->threshMinY)
    {
        if (y < def->joyMinY)
            y = def->joyMinY;

        y = -(y - def->threshMinY);
        y *= def->joyMultYL;
        y >>= JoyScaleShift;
        *dy = (y > 127)? -127 : -y;
    }
    else if (y > def->threshMaxY)
    {
        if (y > def->joyMaxY)
            y = def->joyMaxY;

        y = y - def->threshMaxY;
        y *= def->joyMultYH;
        y >>= JoyScaleShift;
        *dy = (y > 127)? 127 : y;
    }
    else
        *dy = 0;

}



//******************************************************************************
//
// INL_GetJoyButtons () - Returns the button status of the specified
//                        joystick
//
//******************************************************************************

word INL_GetJoyButtons (word joy)
{
    word  result = 0;

    if (joy < sdl_total_sticks)
        result = sdl_stick_button_state[joy];

    return result;
}

//******************************************************************************
//
// INL_StartMouse () - Detects and sets up the mouse
//
//******************************************************************************

boolean INL_StartMouse (void)
{

    boolean retval = false;

    /* no-op. */
    retval = true;

    return (retval);
}



//******************************************************************************
//
// INL_SetJoyScale () - Sets up scaling values for the specified joystick
//
//******************************************************************************

void INL_SetJoyScale (word joy)
{
    JoystickDef *def;

    def = &JoyDefs[joy];
    def->joyMultXL = JoyScaleMax / (def->threshMinX - def->joyMinX);
    def->joyMultXH = JoyScaleMax / (def->joyMaxX - def->threshMaxX);
    def->joyMultYL = JoyScaleMax / (def->threshMinY - def->joyMinY);
    def->joyMultYH = JoyScaleMax / (def->joyMaxY - def->threshMaxY);
}

//******************************************************************************
//
// IN_SetupJoy () - Sets up thresholding values and calls INL_SetJoyScale()
//                  to set up scaling values
//
//******************************************************************************

void IN_SetupJoy (word joy, word minx, word maxx, word miny, word maxy)
{
    word     d,r;
    JoystickDef *def;

    def = &JoyDefs[joy];

    def->joyMinX = minx;
    def->joyMaxX = maxx;
    r = maxx - minx;
    d = r / 3;
    def->threshMinX = ((r / 2) - d) + minx;
    def->threshMaxX = ((r / 2) + d) + minx;

    def->joyMinY = miny;
    def->joyMaxY = maxy;
    r = maxy - miny;
    d = r / 3;
    def->threshMinY = ((r / 2) - d) + miny;
    def->threshMaxY = ((r / 2) + d) + miny;

    INL_SetJoyScale (joy);
}


//******************************************************************************
//
// INL_StartJoy () - Detects & auto-configures the specified joystick
//                   The auto-config assumes the joystick is centered
//
//******************************************************************************


boolean INL_StartJoy (word joy)
{
    word x,y;

#if USE_SDL
    if (!SDL_WasInit(SDL_INIT_JOYSTICK))
    {
        SDL_Init(SDL_INIT_JOYSTICK);
        sdl_total_sticks = SDL_NumJoysticks();
        if (sdl_total_sticks > MaxJoys) sdl_total_sticks = MaxJoys;

        if ((sdl_stick_button_state == NULL) && (sdl_total_sticks > 0))
        {
            sdl_stick_button_state = (word *) malloc(sizeof (word) * sdl_total_sticks);
            if (sdl_stick_button_state == NULL)
                SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
            else
                memset(sdl_stick_button_state, '\0', sizeof (word) * sdl_total_sticks);
        }
        SDL_JoystickEventState(SDL_ENABLE);
    }

    if (joy >= sdl_total_sticks) return (false);
    sdl_joysticks[joy] = SDL_JoystickOpen (joy);
#endif

    IN_GetJoyAbs (joy, &x, &y);

    if
    (
        ((x == 0) || (x > MaxJoyValue - 10))
        || ((y == 0) || (y > MaxJoyValue - 10))
    )
        return(false);
    else
    {
        IN_SetupJoy (joy, 0, x * 2, 0, y * 2);
        return (true);
    }
}



//******************************************************************************
//
// INL_ShutJoy() - Cleans up the joystick stuff
//
//******************************************************************************

void INL_ShutJoy (word joy)
{
    JoysPresent[joy] = false;
    if (joy < sdl_total_sticks) SDL_JoystickClose (sdl_joysticks[joy]);
}



//******************************************************************************
//
// IN_Startup() - Starts up the Input Mgr
//
//******************************************************************************


void IN_Startup (void)
{
    boolean checkjoys,
            checkmouse;

    word    i;

    if (IN_Started==true)
        return;

    /*
      all keys are now mapped to the wolf3d-style names,
      except where no such name is available.
     */
    //calloc(scancodes, sizeof(unsigned int) * 513);

    scancodes = malloc(sizeof(HashTable));

    InitHashTable(scancodes, SDL_NUM_SCANCODES);

    Insert(scancodes, SDLK_ESCAPE, sc_Escape);
    Insert(scancodes, SDLK_1, sc_1);
    Insert(scancodes, SDLK_2, sc_2);
    Insert(scancodes, SDLK_3, sc_3);
    Insert(scancodes, SDLK_4, sc_4);
    Insert(scancodes, SDLK_5, sc_5);
    Insert(scancodes, SDLK_6, sc_6);
    Insert(scancodes, SDLK_7, sc_7);
    Insert(scancodes, SDLK_8, sc_8);
    Insert(scancodes, SDLK_9, sc_9);
    Insert(scancodes, SDLK_0, sc_0);

    Insert(scancodes, SDLK_EQUALS, sc_Equals);

    Insert(scancodes, SDLK_BACKSPACE, sc_BackSpace);
    Insert(scancodes, SDLK_TAB, sc_Tab);
    Insert(scancodes, SDLK_q, sc_Q);
    Insert(scancodes, SDLK_w, sc_W);
    Insert(scancodes, SDLK_e, sc_E);
    Insert(scancodes, SDLK_r, sc_R);
    Insert(scancodes, SDLK_t, sc_T);
    Insert(scancodes, SDLK_y, sc_Y);
    Insert(scancodes, SDLK_u, sc_U);
    Insert(scancodes, SDLK_i, sc_I);
    Insert(scancodes, SDLK_o, sc_O);
    Insert(scancodes, SDLK_p, sc_P);
    Insert(scancodes, SDLK_LEFTBRACKET, sc_OpenBracket);
    Insert(scancodes, SDLK_RIGHTBRACKET, sc_CloseBracket);
    Insert(scancodes, SDLK_RETURN, sc_Return);
    Insert(scancodes, SDLK_LCTRL, sc_Control);

    Insert(scancodes, SDLK_PAGEUP, sc_PgUp);
    Insert(scancodes, SDLK_s, sc_S);
    Insert(scancodes, SDLK_d, sc_D);
    Insert(scancodes, SDLK_f, sc_F);
    Insert(scancodes, SDLK_g, sc_G);
    Insert(scancodes, SDLK_h, sc_H);
    Insert(scancodes, SDLK_j, sc_J);
    Insert(scancodes, SDLK_k, sc_K);
    Insert(scancodes, SDLK_l, sc_L);
    Insert(scancodes, SDLK_SEMICOLON, 0x27);
    Insert(scancodes, SDLK_QUOTE, 0x28);
    Insert(scancodes, SDLK_BACKQUOTE, 0x29);

    Insert(scancodes, SDLK_LSHIFT, sc_RShift);

    Insert(scancodes, SDLK_BACKSLASH, 0x2B);
    Insert(scancodes, SDLK_z, sc_Z);
    Insert(scancodes, SDLK_x, sc_X);
    Insert(scancodes, SDLK_c, sc_C);
    Insert(scancodes, SDLK_v, sc_V);
    Insert(scancodes, SDLK_b, sc_B);
    Insert(scancodes, SDLK_n, sc_N);
    Insert(scancodes, SDLK_m, sc_M);
    Insert(scancodes, SDLK_COMMA, sc_Comma);
    Insert(scancodes, SDLK_PERIOD, sc_Period);
    Insert(scancodes, SDLK_SLASH, 0x35);
    Insert(scancodes, SDLK_RSHIFT, sc_RShift);
    Insert(scancodes, SDLK_KP_DIVIDE, 0x35);

    Insert(scancodes, SDLK_LALT, sc_Alt);
    Insert(scancodes, SDLK_RALT, sc_Alt);
    Insert(scancodes, SDLK_MODE, sc_Alt);
    Insert(scancodes, SDLK_RCTRL, sc_Control);
    Insert(scancodes, SDLK_SPACE, sc_Space);
    Insert(scancodes, SDLK_CAPSLOCK, sc_CapsLock);
    Insert(scancodes, SDLK_F1, sc_F1);
    Insert(scancodes, SDLK_F2, sc_F2);
    Insert(scancodes, SDLK_F3, sc_F3);
    Insert(scancodes, SDLK_F4, sc_F4);
    Insert(scancodes, SDLK_KP_PLUS, sc_Plus);
    Insert(scancodes, SDLK_PLUS, sc_Plus);
    Insert(scancodes, SDLK_F5, sc_F5);
    Insert(scancodes, SDLK_F6, sc_F6);
    Insert(scancodes, SDLK_F7, sc_F7);
    Insert(scancodes, SDLK_F8, sc_F8);
    Insert(scancodes, SDLK_F9, sc_F9);
    Insert(scancodes, SDLK_F10, sc_F10);
    Insert(scancodes, SDLK_F11, sc_F11);
    Insert(scancodes, SDLK_F12, sc_F12);
    Insert(scancodes, SDLK_NUMLOCKCLEAR, 0x45);
    Insert(scancodes, SDLK_SCROLLLOCK, 0x46);

    Insert(scancodes, SDLK_MINUS, sc_Minus);

    Insert(scancodes, SDLK_KP_PERIOD, sc_Delete);
    Insert(scancodes, SDLK_KP_7, sc_Home);
    Insert(scancodes, SDLK_KP_8, sc_UpArrow);
    Insert(scancodes, SDLK_KP_9, sc_PgUp);
    Insert(scancodes, SDLK_HOME, sc_Home);
    Insert(scancodes, SDLK_UP, sc_UpArrow);
    Insert(scancodes, SDLK_a, sc_A);
    Insert(scancodes, SDLK_KP_MINUS, sc_Minus);
    Insert(scancodes, SDLK_KP_4, sc_LeftArrow);
    Insert(scancodes, SDLK_KP_5, 0x4C);
    Insert(scancodes, SDLK_KP_6, sc_RightArrow);
    Insert(scancodes, SDLK_LEFT, sc_LeftArrow);
    Insert(scancodes, SDLK_RIGHT, sc_RightArrow);

    Insert(scancodes, SDLK_KP_1, sc_End);
    Insert(scancodes, SDLK_KP_2, sc_DownArrow);
    Insert(scancodes, SDLK_KP_3, sc_PgDn);
    Insert(scancodes, SDLK_END, sc_End);
    Insert(scancodes, SDLK_DOWN, sc_DownArrow);
    Insert(scancodes, SDLK_PAGEDOWN, sc_PgDn);
    Insert(scancodes, SDLK_DELETE, sc_Delete);
    Insert(scancodes, SDLK_KP_0, sc_Insert);
    Insert(scancodes, SDLK_INSERT, sc_Insert);
    Insert(scancodes, SDLK_KP_ENTER, sc_Return);

    checkjoys        = true;
    checkmouse       = true;

    for (i = 1; i < _argc; i++)
    {
        switch (US_CheckParm (_argv[i], ParmStrings))
        {
        case 0:
            checkjoys = false;
            break;

        case 1:
            checkmouse = false;
            break;
        }
    }

    MousePresent = checkmouse ? INL_StartMouse() : false;

    if (!MousePresent)
        mouseenabled = false;
    else
    {
        if (!quiet)
            printf("IN_Startup: Mouse Present\n");
    }

    for (i = 0; i < MaxJoys; i++)
    {
        JoysPresent[i] = checkjoys ? INL_StartJoy(i) : false;
        if (INL_StartJoy(i))
        {
            if (!quiet)
                printf("IN_Startup: Joystick Present\n");
        }
    }

    IN_Started = true;
}

void ClearScanCodes()
{
    ClearHashTable(scancodes);
}

//******************************************************************************
//
// IN_Shutdown() - Shuts down the Input Mgr
//
//******************************************************************************

void IN_Shutdown (void)
{
    word  i;

    if (IN_Started==false)
        return;

//   INL_ShutMouse();

    for (i = 0; i < MaxJoys; i++)
        INL_ShutJoy(i);

    IN_Started = false;
}


//******************************************************************************
//
// IN_ClearKeysDown() - Clears the keyboard array
//
//******************************************************************************

void IN_ClearKeysDown (void)
{
    LastScan = sc_None;
    memset ((void *)Keyboard, 0, sizeof (Keyboard));
}


//******************************************************************************
//
// IN_ReadControl() - Reads the device associated with the specified
//    player and fills in the control info struct
//
//******************************************************************************

void IN_ReadControl (int player, ControlInfo *info)
{
    boolean     realdelta;
    word        buttons;
    int         dx,dy;
    Motion      mx,my;
    ControlType type;

    KeyboardDef *def;

    dx = dy = 0;
    mx = my = motion_None;
    buttons = 0;


    switch (type = Controls[player])
    {
    case ctrl_Keyboard:
        def = &KbdDefs;

        if (Keyboard[sc_UpArrow])
            my = motion_Up;
        else if (Keyboard[sc_DownArrow])
            my = motion_Down;

        if (Keyboard[sc_LeftArrow])
            mx = motion_Left;
        else if (Keyboard[sc_RightArrow])
            mx = motion_Right;

        if (Keyboard[def->button0])
            buttons += 1 << 0;
        if (Keyboard[def->button1])
            buttons += 1 << 1;
        realdelta = false;
        break;

    default:
        ;
    }

    if (realdelta)
    {
        mx = (dx < 0)? motion_Left : ((dx > 0)? motion_Right : motion_None);
        my = (dy < 0)? motion_Up : ((dy > 0)? motion_Down : motion_None);
    }
    else
    {
        dx = mx * 127;
        dy = my * 127;
    }

    info->x = dx;
    info->xaxis = mx;
    info->y = dy;
    info->yaxis = my;
    info->button0 = buttons & (1 << 0);
    info->button1 = buttons & (1 << 1);
    info->button2 = buttons & (1 << 2);
    info->button3 = buttons & (1 << 3);
    info->dir = DirTable[((my + 1) * 3) + (mx + 1)];
}


//******************************************************************************
//
// IN_WaitForKey() - Waits for a scan code, then clears LastScan and
//    returns the scan code
//
//******************************************************************************

ScanCode IN_WaitForKey (void)
{
    ScanCode result;

    while (!(result = LastScan))
        IN_PumpEvents();
    LastScan = 0;
    return (result);
}


//******************************************************************************
//
// IN_Ack() - waits for a button or key press.  If a button is down, upon
// calling, it must be released for it to be recognized
//
//******************************************************************************

boolean  btnstate[8];

void IN_StartAck (void)
{
    unsigned i,
             buttons = 0;

//
// get initial state of everything
//
    LastScan = 0;

    IN_ClearKeysDown ();
    memset (btnstate, 0, sizeof(btnstate));

    IN_PumpEvents();

    buttons = IN_JoyButtons () << 4;

    buttons |= IN_GetMouseButtons();

    for (i=0; i<8; i++,buttons>>=1)
        if (buttons&1)
            btnstate[i] = true;
}



//******************************************************************************
//
// IN_CheckAck ()
//
//******************************************************************************

boolean IN_CheckAck (void)
{
    unsigned i,
             buttons = 0;

//
// see if something has been pressed
//
    if (LastScan)
        return true;

    IN_PumpEvents();

    buttons = IN_JoyButtons () << 4;

    buttons |= IN_GetMouseButtons();

    for (i=0; i<8; i++,buttons>>=1)
        if ( buttons&1 )
        {
            if (!btnstate[i])
                return true;
        }
        else
            btnstate[i]=false;

    return false;
}



//******************************************************************************
//
// IN_Ack ()
//
//******************************************************************************

void IN_Ack (void)
{
    IN_StartAck ();

    while (!IN_CheckAck ())
        ;
}



//******************************************************************************
//
// IN_UserInput() - Waits for the specified delay time (in ticks) or the
//    user pressing a key or a mouse button. If the clear flag is set, it
//    then either clears the key or waits for the user to let the mouse
//    button up.
//
//******************************************************************************

boolean IN_UserInput (long delay)
{
    long lasttime;

    lasttime = GetTicCount();

    IN_StartAck ();
    do
    {
        if (IN_CheckAck())
            return true;
    } while ((GetTicCount() - lasttime) < delay);

    return (false);
}

//===========================================================================


/*
===================
=
= IN_JoyButtons
=
===================
*/

byte IN_JoyButtons (void)
{
    unsigned joybits = 0;

    joybits = sdl_sticks_joybits;

    return (byte) joybits;
}


//******************************************************************************
//
// IN_UpdateKeyboard ()
//
//******************************************************************************

/* HACK HACK HACK */
static int queuegotit=0;

void IN_UpdateKeyboard (void)
{
    int tail;
    int key;

    if (!queuegotit)
        IN_PumpEvents();

    queuegotit=0;

    if (Keytail != Keyhead)
    {
        tail = Keytail;

        while (Keyhead != tail)
        {
            if (KeyboardQueue[Keyhead] & 0x80)        // Up event
            {
                key = KeyboardQueue[Keyhead] & 0x7F;   // AND off high bit

//            if (keysdown[key])
//            {
//               KeyboardQueue[Keytail] = KeyboardQueue[Keyhead];
//               Keytail = (Keytail+1)&(KEYQMAX-1);
//            }
//            else
                Keyboard[key] = 0;
            }
            else                                      // Down event
            {
                Keyboard[KeyboardQueue[Keyhead]] = 1;
//            keysdown[KeyboardQueue[Keyhead]] = 1;
            }

            Keyhead = (Keyhead+1)&(KEYQMAX-1);

        }        // while
    }           // if

    // Carry over movement keys from the last refresh
//   keysdown[sc_RightArrow] = Keyboard[sc_RightArrow];
//   keysdown[sc_LeftArrow]  = Keyboard[sc_LeftArrow];
//   keysdown[sc_UpArrow]    = Keyboard[sc_UpArrow];
//   keysdown[sc_DownArrow]  = Keyboard[sc_DownArrow];
}



//******************************************************************************
//
// IN_InputUpdateKeyboard ()
//
//******************************************************************************

int IN_InputUpdateKeyboard (void)
{
    int key;
    int returnval = 0;
    boolean done = false;

//   _disable ();

    if (Keytail != Keyhead)
    {
        int tail = Keytail;

        while (!done && (Keyhead != tail))
        {
            if (KeyboardQueue[Keyhead] & 0x80)        // Up event
            {
                key = KeyboardQueue[Keyhead] & 0x7F;   // AND off high bit

                Keyboard[key] = 0;
            }
            else                                      // Down event
            {
                Keyboard[KeyboardQueue[Keyhead]] = 1;
                returnval = KeyboardQueue[Keyhead];
                done = true;
            }

            Keyhead = (Keyhead+1)&(KEYQMAX-1);
        }
    }           // if

//   _enable ();

    return (returnval);
}


//******************************************************************************
//
// IN_ClearKeyboardQueue ()
//
//******************************************************************************

void IN_ClearKeyboardQueue (void)
{
    return;

//   IN_ClearKeysDown ();

//   Keytail = Keyhead = 0;
//   memset (KeyboardQueue, 0, sizeof (KeyboardQueue));
//   I_SendKeyboardData(0xf6);
//   I_SendKeyboardData(0xf4);
}

//******************************************************************************
//
// QueueLetterInput ()
//
//******************************************************************************

void QueueLetterInput (void)
{
    int head = Keyhead;
    int tail = Keytail;
    char c;
    int scancode;
    boolean send = false;

    /* HACK HACK HACK */
    /*
      OK, we want the new keys NOW, and not when the update gets them.
      Since this called before IN_UpdateKeyboard in PollKeyboardButtons,
      we shall update here.  The hack is there to prevent IN_UpdateKeyboard
      from stealing any keys... - SBF
     */
    IN_PumpEvents();
    head = Keyhead;
    tail = Keytail;
    queuegotit=1;
    /* HACK HACK HACK */

    while (head != tail)
    {
        if (!(KeyboardQueue[head] & 0x80))        // Down event
        {
            scancode = KeyboardQueue[head];

            if (Keyboard[sc_RShift] || Keyboard[sc_LShift])
            {
                c = ShiftedScanChars[scancode];
            }
            else
            {
                c = ScanChars[scancode];
            }

            // If "is printable char", queue the character
            if (c)
            {
                LetterQueue[LastLetter] = c;
                LastLetter = (LastLetter+1)&(MAXLETTERS-1);

                // If typing a message, update the text with 'c'

                if ( MSG.messageon )
                {
                    Keystate[scancode]=0;
                    KeyboardQueue[head] = 0;
                    if ( MSG.inmenu )
                    {
                        if ( ( c == 'A' ) || ( c == 'a' ) )
                        {
                            MSG.towho = MSG_DIRECTED_TO_ALL;
                            send      = true;
                        }

                        if ( ( gamestate.teamplay ) &&
                                ( ( c == 'T' ) || ( c == 't' ) ) )
                        {
                            MSG.towho = MSG_DIRECTED_TO_TEAM;
                            send      = true;
                        }

                        if ( ( c >= '0' ) && ( c <= '9' ) )
                        {
                            int who;

                            if ( c == '0' )
                            {
                                who = 10;
                            }
                            else
                            {
                                who = c - '1';
                            }

                            // Skip over local player
                            if ( who >= consoleplayer )
                            {
                                who++;
                            }

                            if ( who < numplayers )
                            {
                                MSG.towho = who;
                                send      = true;
                            }
                        }

                        if ( send )
                        {
                            MSG.messageon = false;
                            KeyboardQueue[ head ] = 0;
                            Keyboard[ scancode ]  = 0;
                            LastScan              = 0;
                            FinishModemMessage( MSG.textnum, true );
                        }
                    }
                    else if ( ( scancode >= sc_1 ) && ( scancode <= sc_0 ) &&
                              ( Keyboard[ sc_Alt ] ) )
                    {
                        int msg;

                        msg = scancode - sc_1;

                        if ( CommbatMacros[ msg ].avail )
                        {
                            MSG.length = strlen( CommbatMacros[ msg ].macro ) + 1;
                            strncpy( Messages[ MSG.textnum ].text,
                                    CommbatMacros[ msg ].macro, MSG.length) ;

                            MSG.messageon = false;
                            FinishModemMessage( MSG.textnum, true );
                            KeyboardQueue[ head ] = 0;
                            Keyboard[ sc_Enter ]  = 0;
                            Keyboard[ sc_Escape ] = 0;
                            LastScan              = 0;
                        }
                        else
                        {
                            MSG.messageon = false;
                            MSG.directed  = false;

                            FinishModemMessage( MSG.textnum, false );
                            AddMessage( "No macro.", MSG_MACRO );
                            KeyboardQueue[ head ] = 0;
                            Keyboard[ sc_Enter ]  = 0;
                            Keyboard[ sc_Escape ] = 0;
                            LastScan              = 0;
                        }
                    }
                    else if ( MSG.length < MAXMESSAGELENGTH )
                    {
                        UpdateModemMessage (MSG.textnum, c);
                    }
                }
            }
            else
            {
                // If typing a message, check for special characters

                if ( MSG.messageon && MSG.inmenu )
                {
                    if ( scancode == sc_Escape )
                    {
                        MSG.messageon = false;
                        MSG.directed  = false;
                        FinishModemMessage( MSG.textnum, false );
                        KeyboardQueue[head] = 0;
                        Keyboard[sc_Enter]  = 0;
                        Keyboard[sc_Escape] = 0;
                        LastScan            = 0;
                    }
                }
                else if ( MSG.messageon && !MSG.inmenu )
                {
                    if ( ( scancode >= sc_F1 ) &&
                            ( scancode <= sc_F10 ) )
                    {
                        MSG.remoteridicule = scancode - sc_F1;
                        MSG.messageon = false;
                        FinishModemMessage(MSG.textnum, true);
                        KeyboardQueue[head] = 0;
                        Keyboard[sc_Enter]  = 0;
                        Keyboard[sc_Escape] = 0;
                        LastScan            = 0;
                    }

                    switch (scancode)
                    {
                    case sc_BackSpace:
                        KeyboardQueue[head] = 0;
                        if (MSG.length > 1)
                        {
                            ModemMessageDeleteChar (MSG.textnum);
                        }
                        Keystate[scancode]=0;
                        break;

                    case sc_Enter:
                        MSG.messageon = false;
                        FinishModemMessage(MSG.textnum, true);
                        KeyboardQueue[head] = 0;
                        Keyboard[sc_Enter]  = 0;
                        Keyboard[sc_Escape] = 0;
                        LastScan            = 0;
                        Keystate[scancode]=0;
                        break;

                    case sc_Escape:
                        MSG.messageon = false;
                        MSG.directed  = false;
                        FinishModemMessage(MSG.textnum, false);
                        KeyboardQueue[head] = 0;
                        Keyboard[sc_Enter]  = 0;
                        Keyboard[sc_Escape] = 0;
                        LastScan            = 0;
                        break;
                    }
                }
            }
        }

        head = (head+1)&(KEYQMAX-1);
    }        // while
}
