/** PSP helper library ***************************************/
/**                                                         **/
/**                          kybd.c                         **/
/**                                                         **/
/** This file contains virtual keyboard implementation      **/
/** routines                                                **/
/**                                                         **/
/** Copyright (C) Akop Karapetyan 2007                      **/
/**     You are not allowed to distribute this software     **/
/**     commercially. Please, notify me, if you make any    **/
/**     changes to this file.                               **/
/*************************************************************/
#include <time.h>
#include <psptypes.h>
#include <psprtc.h>
#include <malloc.h>
#include <pspgu.h>
#include <string.h>

#include "video.h"
#include "image.h"
#include "font.h"
#include "kybd.h"

#define PSP_KYBD_BUTTONS   6
#define PSP_KYBD_DELAY     400
#define PSP_KYBD_THRESHOLD 50

#define PSP_KYBD_BUTTON_BG        RGBA(0, 0, 0, 0x88)
#define PSP_KYBD_BUTTON_BORDER    PSP_VIDEO_WHITE
#define PSP_KYBD_FONT_COLOR       RGBA(0xFF, 0xFF, 0xFF, 0xFF)
#define PSP_KYBD_STUCK_COLOR      RGBA(0xFF, 0, 0, 0x33)
#define PSP_KYBD_SELECTED_COLOR   RGBA(0xFF, 0xFF, 0, 0x88)

static u64 PushTime[PSP_KYBD_BUTTONS];
static const int ButtonMap[PSP_KYBD_BUTTONS] = 
{
  PSP_CTRL_UP,
  PSP_CTRL_DOWN,
  PSP_CTRL_LEFT,
  PSP_CTRL_RIGHT,
  PSP_CTRL_CIRCLE,
  PSP_CTRL_TRIANGLE
};

/* TODO */
static struct PspKeyboardButton MSXKeys[] = 
{
  { "F1",     0x12,  0, 0, 40, 30 }, { "F2",   0x13, 40, 0, 40, 30 }, 
  { "F3",     0x14, 80, 0, 40, 30 }, { "F4",   0x15,120, 0, 40, 30 }, 
  { "F5",     0x16,160, 0, 40, 30 }, { "STOP", 0x11,250, 0, 40, 30 },
  { "SELECT", 0x0B,290, 0, 40, 30 }, { "HOME", 0x0C,330, 0, 40, 30 },
  { "INS",    0x0F,370, 0, 40, 30 }, { "DEL",  0x0E,410, 0, 40, 30 },

  { "ESC",   0x1B,  0, 35, 30, 30 },   
  { "!\n1",  0x31, 30, 35, 30, 30 }, { "@\n2", 0x32, 60, 35, 30, 30 }, 
  { "#\n3",  0x33, 90, 35, 30, 30 }, { "$\n4", 0x34,120, 35, 30, 30 }, 
  { "%\n5",  0x35,150, 35, 30, 30 }, { "^\n6", 0x36,180, 35, 30, 30 }, 
  { "&\n7",  0x37,210, 35, 30, 30 }, { "*\n8", 0x38,240, 35, 30, 30 }, 
  { "(\n9",  0x39,270, 35, 30, 30 }, { ")\n0", 0x30,300, 35, 30, 30 },
  { "_\n-",  0x2D,330, 35, 30, 30 }, { "+\n=", 0x3D,360, 35, 30, 30 },
  { "|\n\\", 0x5C,390, 35, 30, 30 }, { "BS",   0x08,420, 35, 30, 30 }, 

  { "TAB", 0x09,  0, 65, 40, 30 }, { "Q",     0x51, 40, 65, 30, 30 }, 
  { "W",   0x57, 70, 65, 30, 30 }, { "E",     0x45,100, 65, 30, 30 }, 
  { "R",   0x52,130, 65, 30, 30 }, { "T",     0x54,160, 65, 30, 30 }, 
  { "Y",   0x59,190, 65, 30, 30 }, { "U",     0x55,220, 65, 30, 30 }, 
  { "I",   0x49,250, 65, 30, 30 }, { "O",     0x4F,280, 65, 30, 30 }, 
  { "P",   0x50,310, 65, 30, 30 }, { "{\n[",  0x5B,340, 65, 30, 30 }, 
  { "}\n]",0x5D,370, 65, 30, 30 }, { "RETURN",0x0D,400, 65, 50, 30 },

  { "CTRL",  0x06,  0, 95, 50, 30 }, { "A",     0x41, 50, 95, 30, 30 }, 
  { "S",     0x53, 80, 95, 30, 30 }, { "D",     0x44,110, 95, 30, 30 }, 
  { "F",     0x46,140, 95, 30, 30 }, { "G",     0x47,170, 95, 30, 30 }, 
  { "H",     0x48,200, 95, 30, 30 }, { "J",     0x4A,230, 95, 30, 30 }, 
  { "K",     0x4B,260, 95, 30, 30 }, { "L",     0x4C,290, 95, 30, 30 }, 
  { ":\n;",  0x3B,320, 95, 30, 30 }, { "\"\n'", 0x27,350, 95, 30, 30 },
  { "~\n`",  0x60,380, 95, 30, 30 }, { "RETURN",0x0D,410, 95, 40, 30 }, 

  { "SHIFT", 0x05,  0,125, 60, 30 }, { "Z",    0x5A, 60,125, 30, 30 }, 
  { "X",     0x58, 90,125, 30, 30 }, { "V",    0x56,120,125, 30, 30 }, 
  { "C",     0x43,150,125, 30, 30 }, { "B",    0x42,180,125, 30, 30 }, 
  { "N",     0x4E,210,125, 30, 30 }, { "M",    0x4D,240,125, 30, 30 },
  { "<\n,",  0x2C,270,125, 30, 30 }, { ">\n.", 0x2E,300,125, 30, 30 }, 
  { "?\n/",  0x2F,330,125, 30, 30 }, { "SHIFT",0x05,360,125, 90, 30 }, 

  { "CAPS",  0x0A, 70,155, 35, 30 }, { "GRAPH", 0x07,105,155, 35, 30 },
  { " ",     0x20,140,155,180, 30 }, { "CTRY", 0x10,320,155, 30, 30 },
};

static struct PspStickyButton MSXStickies[] = 
{
  { 0x05, 0, NULL, 0 }, { 0x06, 0, NULL, 0 }, { 0x07, 0, NULL, 0 }
};

void _pspKybdFilterRepeats(SceCtrlData *pad);
void _pspKybdRenderKeyboard(PspKeyboardLayout *layout);

void pspKybdReinit(PspKeyboardLayout *layout)
{
  int i;
  for (i = 0; i < PSP_KYBD_BUTTONS; i++)
    PushTime[i] = 0;

  layout->HeldDown = 0;

  for (i = 0; i < layout->StickyCount; i++)
    layout->StickyKeys[i].Status = layout->ReadCallback(layout->StickyKeys[i].Code);
}

void pspKybdRender(const PspKeyboardLayout *layout)
{
  int off_x, off_y, i, j, fh;
  const struct PspKeyboardButton *button;

  /* Render the virtual keyboard */
  pspVideoCallList(layout->CallList);

  fh = pspFontGetLineHeight(&PspStockFont);
  off_x = SCR_WIDTH / 2 - layout->MatrixWidth / 2;
  off_y = SCR_HEIGHT / 2 - layout->MatrixHeight / 2;

  /* Highlight sticky buttons */
  for (i = 0; i < layout->StickyCount; i++)
  {
    if (layout->StickyKeys[i].Status)
    {
      for (j = 0; j < layout->StickyKeys[i].IndexCount; j++)
      {
        button = &(layout->Keys[layout->StickyKeys[i].KeyIndex[j]]);
        pspVideoFillRect(off_x + button->X + 1, 
          off_y + button->Y + 1, 
          off_x + button->X + button->W, 
          off_y + button->Y + button->H,
          PSP_KYBD_STUCK_COLOR);

        pspVideoPrint(&PspStockFont, (off_x + button->X + button->W / 2) - button->TextWidth / 2, 
          (off_y + button->Y + button->H / 2) - button->TextHeight / 2,
          button->Caption, PSP_KYBD_FONT_COLOR);
      }
    }
  }

  /* Highlight selected button */
  button = &(layout->Keys[layout->Selected]);
  pspVideoFillRect(off_x + button->X + 1, 
    off_y + button->Y + 1, 
    off_x + button->X + button->W, 
    off_y + button->Y + button->H,
    PSP_KYBD_SELECTED_COLOR);
  pspVideoPrint(&PspStockFont, (off_x + button->X + button->W / 2) - button->TextWidth / 2, 
    (off_y + button->Y + button->H / 2) - button->TextHeight / 2,
    button->Caption, PSP_KYBD_FONT_COLOR);
}

void _pspKybdFilterRepeats(SceCtrlData *pad)
{
  SceCtrlData p = *pad;
  int i;
  u64 tick;
  u32 tick_res;

  /* Get current tick count */
  sceRtcGetCurrentTick(&tick);
  tick_res = sceRtcGetTickResolution();

  /* Check each button */
  for (i = 0; i < PSP_KYBD_BUTTONS; i++)
  {
    if (p.Buttons & ButtonMap[i])
    {
      if (!PushTime[i] || tick >= PushTime[i])
      {
        /* Button was pushed for the first time, or time to repeat */
        pad->Buttons |= ButtonMap[i];
        /* Compute next press time */
        PushTime[i] = tick + ((PushTime[i]) ? PSP_KYBD_THRESHOLD : PSP_KYBD_DELAY)
          * (tick_res / 1000);
      }
      else
      {
        /* No need to repeat yet */
        pad->Buttons &= ~ButtonMap[i];
      }
    }
    else
    {
      /* Button was released */
      pad->Buttons &= ~ButtonMap[i];
      PushTime[i] = 0;        
    }
  }
}

void pspKybdReleaseAll(PspKeyboardLayout *layout)
{
  /* Release 'held down' key */
  if (layout->HeldDown)
  {
    layout->WriteCallback(layout->HeldDown, 0);
    layout->HeldDown = 0;
  }

  /* Unset all sticky keys */
  int i;
  for (i = 0; i < layout->StickyCount; i++)
  {
    layout->StickyKeys[i].Status = 0;
    layout->WriteCallback(layout->StickyKeys[i].Code, 0);
  }
}

void pspKybdNavigate(PspKeyboardLayout *layout, SceCtrlData *pad)
{
  int i;

  _pspKybdFilterRepeats(pad);

  if (pad->Buttons & PSP_CTRL_SQUARE && !layout->HeldDown)
  {
    /* Button pressed */
    layout->HeldDown = layout->Keys[layout->Selected].Code;
    layout->WriteCallback(layout->HeldDown, 1);
  }
  else if (!(pad->Buttons & PSP_CTRL_SQUARE) && layout->HeldDown)
  {
    /* Button released */
    layout->WriteCallback(layout->HeldDown, 0);
    layout->HeldDown = 0;
  }

  if (pad->Buttons & PSP_CTRL_RIGHT)
  {
    if (layout->Selected + 1 < layout->KeyCount
      && layout->Keys[layout->Selected].Y == layout->Keys[layout->Selected + 1].Y)
        layout->Selected++;
  }
  else if (pad->Buttons & PSP_CTRL_LEFT)
  {
    if (layout->Selected > 0 
      && layout->Keys[layout->Selected].Y == layout->Keys[layout->Selected - 1].Y)
        layout->Selected--; 
  }
  else if (pad->Buttons & PSP_CTRL_DOWN)
  {
    /* Find first button on the next row */
    for (i = layout->Selected + 1; i < layout->KeyCount && layout->Keys[i].Y == layout->Keys[layout->Selected].Y; i++);

    if (i < layout->KeyCount)
    {
      /* Find button below the current one */
      int r = i;
      for (; i < layout->KeyCount && layout->Keys[i].Y == layout->Keys[r].Y; i++)
        if (layout->Keys[i].X + layout->Keys[i].W / 2 >= layout->Keys[layout->Selected].X + layout->Keys[layout->Selected].W / 2) 
          break;

      layout->Selected = (i < layout->KeyCount && layout->Keys[r].Y == layout->Keys[i].Y) ? i : i - 1;
    }
  }
  else if (pad->Buttons & PSP_CTRL_UP)
  {
    /* Find first button on the previous row */
    for (i = layout->Selected - 1; i >= 0 && layout->Keys[i].Y == layout->Keys[layout->Selected].Y; i--);

    if (i >= 0)
    {
      /* Find button above the current one */
      int r = i;
      for (; i >= 0 && layout->Keys[i].Y == layout->Keys[r].Y; i--)
        if (layout->Keys[i].X + layout->Keys[i].W / 2 <= layout->Keys[layout->Selected].X + layout->Keys[layout->Selected].W / 2) 
          break;

      layout->Selected = (i >= 0 && layout->Keys[r].Y == layout->Keys[i].Y) ? i : i + 1;
    }
  }

  if (pad->Buttons & PSP_CTRL_CIRCLE && layout->Keys[layout->Selected].IsSticky)
  {
    for (i = 0; i < layout->StickyCount; i++)
    {
      /* Sticky key; toggle status */
      if (layout->Keys[layout->Selected].Code == layout->StickyKeys[i].Code)
      {
        layout->StickyKeys[i].Status = !layout->StickyKeys[i].Status;
        layout->WriteCallback(layout->StickyKeys[i].Code, layout->StickyKeys[i].Status);
        break;
      }
    }
  }
  else if (pad->Buttons & PSP_CTRL_TRIANGLE)
  {
    /* Unset all sticky keys */
    for (i = 0; i < layout->StickyCount; i++)
    {
      layout->StickyKeys[i].Status = 0;
      layout->WriteCallback(layout->StickyKeys[i].Code, 0);
    }
  }

  /* Unset used buttons */
  for (i = 0; i < PSP_KYBD_BUTTONS; i++)
    pad->Buttons &= ~ButtonMap[i];
}

void pspKybdDestroyLayout(PspKeyboardLayout *layout)
{
  int i;
  for (i = 0; i < layout->StickyCount; i++)
    free(layout->StickyKeys[i].KeyIndex);

  /* TODO: Destroy layout loaded in pspKybdLoadLayout */
  free(layout);
}

void _pspKybdRenderKeyboard(PspKeyboardLayout *layout)
{
  int i;

  /* Render the virtual keyboard to a call list */
  memset(layout->CallList, 0, sizeof(layout->CallList));

  sceGuStart(GU_CALL, layout->CallList);

  const struct PspKeyboardButton *button;
  int off_x, off_y, txt_w, fh;
  int sx, sy, color;

  fh = pspFontGetLineHeight(&PspStockFont);
  off_x = SCR_WIDTH / 2 - layout->MatrixWidth / 2;
  off_y = SCR_HEIGHT / 2 - layout->MatrixHeight / 2;

  for (i = 0; i < layout->KeyCount; i++)
  {
    button = &(layout->Keys[i]);

    /* Draw the button background */
    pspVideoFillRectRaw(off_x + button->X,
      off_y + button->Y,
      off_x + button->X + button->W,
      off_y + button->Y + button->H,
      PSP_KYBD_BUTTON_BG);

    /* Draw the button border */
    pspVideoDrawRectRaw(off_x + button->X,
      off_y + button->Y,
      off_x + button->X + button->W,
      off_y + button->Y + button->H,
      PSP_KYBD_BUTTON_BORDER);

    /* Draw the button text */
    sx = (off_x + button->X + button->W / 2) - button->TextWidth / 2;
    sy = (off_y + button->Y + button->H / 2) - button->TextHeight / 2;
    color = PSP_KYBD_FONT_COLOR;

    pspVideoPrintRaw(&PspStockFont, sx, sy, button->Caption, color);
  }

  /* Draw the options background */
  char buttons[] = " \026\244\020 Press button\t\026\242\020 Stick button\t\026\243\020 Unstick all ";
  txt_w = pspFontGetTextWidth(&PspStockFont, buttons);
  off_x = SCR_WIDTH / 2 - txt_w / 2;
  off_y = SCR_HEIGHT - fh;

  pspVideoFillRectRaw(off_x,
    off_y,
    off_x + txt_w,
    off_y + fh,
    PSP_KYBD_BUTTON_BG);

  /* Draw the button captions */
  pspVideoPrintRaw(&PspStockFont, off_x, off_y, buttons, PSP_VIDEO_WHITE);
  sceGuFinish();
}

PspKeyboardLayout* pspKybdLoadLayout(const char *path, 
  int(*read_callback)(unsigned int),
  void(*write_callback)(unsigned int, int))
{
  /* Reset physical button status */
  int i, j;
  for (i = 0; i < PSP_KYBD_BUTTONS; i++)
    PushTime[i] = 0;

  /* Create layout struct */
  PspKeyboardLayout *layout;
  if (!(layout = (PspKeyboardLayout*)malloc(sizeof(PspKeyboardLayout))))
    return NULL;

  /* Initialize */
  layout->KeyCount = layout->StickyCount = 0;
  layout->Keys = NULL;
  layout->StickyKeys = NULL;

  /* TODO: Here, we would load a layout from a file */
  layout->KeyCount = 69;
  layout->Keys = MSXKeys;
  layout->StickyCount = 3;
  layout->StickyKeys = MSXStickies;
  layout->MatrixWidth = layout->MatrixHeight = 0;

  /* Determine keyboard matrix bounds & button properties */ 
  for (i = 0; i < layout->KeyCount; i++)
  {
    layout->Keys[i].IsSticky = 0;
    for (j = 0; j < layout->StickyCount; j++)
    {
      if (layout->Keys[i].Code == layout->StickyKeys[j].Code)
      {
        layout->Keys[i].IsSticky = 1;
        break;
      }
    }

    if (layout->Keys[i].X + layout->Keys[i].W > layout->MatrixWidth)
      layout->MatrixWidth = layout->Keys[i].X + layout->Keys[i].W;
    if (layout->Keys[i].Y + layout->Keys[i].H > layout->MatrixHeight)
      layout->MatrixHeight = layout->Keys[i].Y + layout->Keys[i].H;

    layout->Keys[i].TextWidth = pspFontGetTextWidth(&PspStockFont, layout->Keys[i].Caption);
    layout->Keys[i].TextHeight = pspFontGetTextHeight(&PspStockFont, layout->Keys[i].Caption);
  }

  _pspKybdRenderKeyboard(layout);

  /* Init key status */
  layout->Selected = 0;
  layout->HeldDown = 0;

  struct PspStickyButton *sticky;
  int count;

  /* Init sticky status */
  for (i = 0; i < layout->StickyCount; i++)
  {
    sticky = &(layout->StickyKeys[i]);

    sticky->Status = 0;
    sticky->IndexCount = 0;
    sticky->KeyIndex = NULL;

    /* Determine number of sticky keys */
    for (j = 0; j < layout->KeyCount; j++)
      if (layout->Keys[j].Code == sticky->Code)
        sticky->IndexCount++;

    /* Allocate space */
    if (sticky->IndexCount > 0)
      sticky->KeyIndex = (unsigned short*)malloc(sizeof(unsigned short) * sticky->IndexCount);

    /* Save indices */
    for (j = 0, count = 0; j < layout->KeyCount; j++)
      if (layout->Keys[j].Code == sticky->Code)
        sticky->KeyIndex[count++] = j;
  }

  /* Init callbacks */
  layout->ReadCallback = read_callback;
  layout->WriteCallback = write_callback;

  return layout;
}
