#include "main.h"

static WNDPROC orig_wndproc;
static HWND orig_wnd;

struct key_state key_table[256];
uint8_t key_table_old[256], key_table_old_prev[256];


static void process_key(int down, int vkey, int repeat, int scancode, int extended, HWND wnd)
{
   if(down && KEY_DOWN(vkey))
      return;     /* ignore key repeat, bad states */
   if(!down && KEY_UP(vkey))
      return;     /* ignore bad states */

   /* disable hotkeys while chatting, or if the game is not active */
   if(samp_is_chatting() || GetForegroundWindow() != wnd)
   {
      /* we still want to process released keys */
      if(!down && KEY_DOWN(vkey))
         key_table[vkey].count++;   /* XXX check for wrap @ 63? */
      return;
   }

   /* XXX check for wrap @ 63? */
   key_table[vkey].count++;
}


static LRESULT CALLBACK wnd_proc(HWND wnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{
   switch(umsg)
   {
   /*case WM_ACTIVATE:
      {
         static int first = 1;

         if(first && LOWORD(wparam) == WA_ACTIVE)
            first = 0;
         else if(LOWORD(wparam) != WA_INACTIVE)
            break;
         PostMessage(wnd, umsg, MAKEWORD(WA_ACTIVE, 0), 0);
      }
      break;

   case WM_KILLFOCUS:
      PostMessage(wnd, WM_SETFOCUS, 0, 0);
      break;

   case WM_ACTIVATEAPP:
      {
         static int first = 1;

         if(first && wparam)
            first = 0;
         else if(wparam == FALSE)
            break;
         PostMessage(wnd, umsg, TRUE, GetCurrentThreadId());
      }
      break;*/

   case WM_LBUTTONDOWN:
   case WM_LBUTTONUP:
      process_key((umsg == WM_LBUTTONDOWN), VK_LBUTTON, 0, 0, 0, wnd);
      break;

   case WM_RBUTTONDOWN:
   case WM_RBUTTONUP:
      process_key((umsg == WM_RBUTTONDOWN), VK_RBUTTON, 0, 0, 0, wnd);
      break;

   case WM_MBUTTONDOWN:
   case WM_MBUTTONUP:
      process_key((umsg == WM_MBUTTONDOWN), VK_MBUTTON, 0, 0, 0, wnd);
      break;

   /* :D */
   case WM_SYSKEYDOWN:
   case WM_SYSKEYUP:
   case WM_KEYDOWN:
   case WM_KEYUP:
      {
         unsigned long p = (unsigned long)lparam;
         int down = (umsg == WM_KEYDOWN || umsg == WM_SYSKEYDOWN);
         int vkey = (int)wparam;
         unsigned int repeat = (p >> 0)  & 0x7FFF;
         unsigned int scancode = (p >> 16) & 0x00FF;
         unsigned int extended = (p >> 24) & 0x0001;

         if(cheat_state == NULL)
            break;

         /* :D :D :D :D :D */
         switch(vkey)
         {
         case VK_SHIFT:
            if(scancode == MapVirtualKey(VK_LSHIFT, 0))
               vkey = VK_LSHIFT;
            else if(scancode == MapVirtualKey(VK_RSHIFT, 0))
               vkey = VK_RSHIFT;
            break;
         case VK_CONTROL:
            if(scancode == MapVirtualKey(VK_LCONTROL, 0))
               vkey = VK_LCONTROL;
            else if(scancode == MapVirtualKey(VK_RCONTROL, 0))
               vkey = VK_RCONTROL;
            break;
         case VK_MENU:
            if(scancode == MapVirtualKey(VK_LMENU, 0))
               vkey = VK_LMENU;
            else if(scancode == MapVirtualKey(VK_RMENU, 0))
               vkey = VK_RMENU;
            break;
         }

         /* :D */
         if(KEY_DOWN(VK_LMENU) && vkey == VK_LMENU && down)
            break;
         if(KEY_UP(VK_LMENU) && vkey == VK_LMENU && !down)
            break;

         process_key(down, vkey, repeat, scancode, extended, wnd);

         /* XXX move this */
         if(cheat_state->debug_enabled &&
            ((vkey >= VK_NUMPAD1  && vkey <= VK_NUMPAD9) ||
              vkey == VK_DIVIDE   || vkey == VK_MULTIPLY ||
              vkey == VK_SUBTRACT || vkey == VK_ADD))
         {
            return 0;
         }

         /* XXX move this */
         if(cheat_state->generic.menu &&
            (vkey == set.key_menu_up    ||
             vkey == set.key_menu_right ||
             vkey == set.key_menu_down  ||
             vkey == set.key_menu_left  ||
             vkey == set.key_menu_select))
         {
            return 0;
         }
      }
      break;
   }

   return CallWindowProc(orig_wndproc, wnd, umsg, wparam, lparam);
}


void keyhook_maybe_install(HWND wnd)
{
   static int old_init;

   if(set.old_keyhook && !old_init)
   {
      memset(key_table_old, 0, sizeof(key_table_old));
      memset(key_table_old_prev, 0, sizeof(key_table_old));
      old_init = 1;
      return;
   }

   if(orig_wndproc == NULL || wnd != orig_wnd)
   {
      keyhook_uninstall();
      log_debug("Installing keyhook...");
      memset(key_table, 0, sizeof(struct key_state) * 256);
      orig_wndproc = (WNDPROC)(UINT_PTR)SetWindowLong(wnd, GWL_WNDPROC, (LONG)(UINT_PTR)wnd_proc);
      orig_wnd = wnd;
   }
}


void keyhook_uninstall(void)
{
   if(set.old_keyhook)
      return;

   if(orig_wnd != NULL)
   {
      log_debug("Removing keyhook...");
      SetWindowLong(orig_wnd, GWL_WNDPROC, (LONG)(UINT_PTR)orig_wndproc);
      orig_wnd = NULL;
   }
}


void keyhook_run(void)
{
   int i;

   if(set.old_keyhook)
   {
      memcpy(key_table_old_prev, key_table_old, sizeof(key_table_old_prev));
      return;
   }

   for(i=0; i<256; i++)
   {
      key_table[i].consume = 0;
      key_table[i].pstate = KEY_DOWN(i);

      if(key_table[i].count > 0)
      {
         key_table[i].flip ^= 1;
         key_table[i].count--;
      }
   }
}


void keyhook_old_run(void)
{
   if(!set.old_keyhook)
      return;

   GetKeyboardState(key_table_old);

   if(samp_is_chatting())
      memset(key_table_old, 0, sizeof(key_table_old));
}


int keyhook_key_down(int v)
{
   if(set.old_keyhook)
      return key_table_old[v] & 0x80;
   else if(key_table[v].consume)
      return 0;
   else
      return (key_table[v].count & 1) ^ key_table[v].flip;
}


int keyhook_key_up(int v)
{
   return !KEY_DOWN(v);
}


int keyhook_key_pressed(int v)
{
   if(set.old_keyhook)
      return KEY_DOWN(v) && !(key_table_old_prev[v] & 0x80);
   else if(key_table[v].consume)
      return 0;
   else
      return KEY_DOWN(v) && !key_table[v].pstate;
}


int keyhook_key_released(int v)
{
   if(set.old_keyhook)
      return KEY_UP(v) && (key_table_old_prev[v] & 0x80);
   else
      return KEY_UP(v) && key_table[v].pstate;
}


void keyhook_key_consume(int v)
{
   if(!set.old_keyhook)
      key_table[v].consume = 1;
}

