/*
 * BRLTTY - A background process providing access to the console screen (when in
 *          text mode) for a blind person using a refreshable braille display.
 *
 * Copyright (C) 1995-2024 by The BRLTTY Developers.
 *
 * BRLTTY comes with ABSOLUTELY NO WARRANTY.
 *
 * This is free software, placed under the terms of the
 * GNU Lesser General Public License, as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any
 * later version. Please see the file LICENSE-LGPL for details.
 *
 * Web Page: http://brltty.app/
 *
 * This software is maintained by Dave Mielke <dave@mielke.cc>.
 */

#define BRLAPI_NO_SINGLE_SESSION
#define BRLAPI_NO_DEPRECATED

#include <windows.h>
#include <stdbool.h>
#include <stdio.h>
#include "brlapi.h"

#include "webrloem.h"

#define C(cmd) (BRLAPI_KEY_CMD_##cmd & BRLAPI_KEY_CMD_ARG_MASK)

static const unsigned command_button[] = {
  [ C(LNUP) ] =			1  /* go up one line */,
  [ C(LNDN) ] =			2  /* go down one line */,
  [ C(WINUP) ] =		3  /* go up several lines */,
  [ C(WINDN) ] =		4  /* go down several lines */,
  [ C(PRDIFLN) ] =		5  /* go up to nearest line with different content */,
  [ C(NXDIFLN) ] =		6  /* go down to nearest line with different content */,
  [ C(ATTRUP) ] =		7  /* go up to nearest line with different highlighting */,
  [ C(ATTRDN) ] =		8  /* go down to nearest line with different highlighting */,
  [ C(TOP) ] =			9  /* go to top line */,
  [ C(BOT) ] =			10 /* go to bottom line */,
  [ C(TOP_LEFT) ] =		11 /* go to beginning of top line */,
  [ C(BOT_LEFT) ] =		12 /* go to beginning of bottom line */,
  [ C(PRPGRPH) ] =		13 /* go up to last line of previous paragraph */,
  [ C(NXPGRPH) ] =		14 /* go down to first line of next paragraph */,
  [ C(PRPROMPT) ] =		15 /* go up to previous command prompt */,
  [ C(NXPROMPT) ] =		16 /* go down to next command prompt */,
  [ C(CHRLT) ] =		17 /* go left one character */,
  [ C(CHRRT) ] =		18 /* go right one character */,
  [ C(HWINLT) ] =		19 /* go left half a window */,
  [ C(HWINRT) ] =		20 /* go right half a window */,
  [ C(FWINLT) ] =		21 /* go left one window */,
  [ C(FWINRT) ] =		22 /* go right one window */,
  [ C(FWINLTSKIP) ] =		23 /* go left to nearest non-blank window */,
  [ C(FWINRTSKIP) ] =		24 /* go right to nearest non-blank window */,
  [ C(LNBEG) ] =		25 /* go to beginning of line */,
  [ C(LNEND) ] =		26 /* go to end of line */,
  [ C(HOME) ] =			27 /* go to cursor */,
  [ C(BACK) ] =			28 /* go back (undo unexpected cursor tracking motion) */,
  [ C(FREEZE) ] =		29 /* toggle screen mode frozen/live */,
  [ C(DISPMD) ] =		30 /* toggle display mode attributes/text */,
  [ C(SIXDOTS) ] =		31 /* toggle text style 6-dot/8-dot */,
  [ C(SLIDEWIN) ] =		32 /* toggle sliding window on/off */,
  [ C(SKPIDLNS) ] =		33 /* toggle skipping of lines with identical content on/off */,
  [ C(SKPBLNKWINS) ] =		34 /* toggle skipping of blank windows on/off */,
  [ C(CSRVIS) ] =		35 /* toggle cursor visibility on/off */,
  [ C(CSRTRK) ] =		36 /* toggle cursor tracking on/off */,
  [ C(CSRSIZE) ] =		37 /* toggle cursor style block/underline */,
  [ C(CSRBLINK) ] =		38 /* toggle cursor blinking on/off */,
  [ C(ATTRVIS) ] =		39 /* toggle attribute underlining on/off */,
  [ C(ATTRBLINK) ] =		40 /* toggle attribute blinking on/off */,
  [ C(CAPBLINK) ] =		41 /* toggle capital letter blinking on/off */,
  [ C(TUNES) ] =		42 /* toggle alert tunes on/off */,
  [ C(AUTOREPEAT) ] =		43 /* toggle autorepeat on/off */,
  [ C(AUTOSPEAK) ] =		44 /* toggle autospeak on/off */,
  [ C(HELP) ] =			45 /* enter/leave help display */,
  [ C(INFO) ] =			46 /* enter/leave status display */,
  [ C(LEARN) ] =		47 /* enter/leave command learn mode */,
  [ C(PREFMENU) ] =		48 /* enter/leave preferences menu */,
  [ C(PREFSAVE) ] =		49 /* save current preferences */,
  [ C(PREFLOAD) ] =		50 /* restore saved preferences */,
  [ C(MUTE) ] =			51 /* stop speaking immediately */,
  [ C(SPKHOME) ] =		52 /* go to current (most recent) speech position */,
  [ C(SAY_LINE) ] =		53 /* speak current line */,
  [ C(SAY_ABOVE) ] =		54 /* speak from top of screen through current line */,
  [ C(SAY_BELOW) ] =		55 /* speak from current line through bottom of screen */,
  [ C(SAY_SLOWER) ] =		56 /* decrease speech rate */,
  [ C(SAY_FASTER) ] =		57 /* increase speech rate */,
  [ C(SAY_SOFTER) ] =		58 /* decrease speech volume */,
  [ C(SAY_LOUDER) ] =		59 /* increase speech volume */,
  [ C(SWITCHVT_PREV) ] =	60 /* switch to previous virtual terminal */,
  [ C(SWITCHVT_NEXT) ] =	61 /* switch to next virtual terminal */,
  [ C(CSRJMP_VERT) ] =		62 /* bring cursor to line (no horizontal motion) */,
  [ C(RESTARTBRL) ] =		63 /* reinitialize braille driver */,
  [ C(RESTARTSPEECH) ] =	64 /* reinitialize speech driver */,
};

int raw;

bool WEBrailleOpen(int portType, int portNumber, DWORD *handle)
{
  brlapi_handle_t *brl_handle = malloc(brlapi_getHandleSize());
  brlapi_range_t ranges[] = {
    {
      .first = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_ROUTE,
      .last = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_ROUTE | BRLAPI_KEY_CMD_ARG_MASK,
    }
  };
  int i,j;
  brlapi_range_t *cmd_ranges;

  if (brlapi__openConnection(brl_handle, NULL, NULL) == INVALID_HANDLE_VALUE)
    goto error;

  raw = portNumber == 2;
  
  if (raw) {
    char name[BRLAPI_MAXNAMELENGTH+1];
    if (brlapi__getDriverName(brl_handle, name, sizeof(name)) == -1)
      goto error;
    if (brlapi__enterTtyModeWithPath(brl_handle, NULL, 0, name) == -1)
      goto error;
  } else {
    if (brlapi__enterTtyModeWithPath(brl_handle, NULL, 0, NULL) == -1)
      goto error;
  }
  if (brlapi__ignoreAllKeys(brl_handle) == -1)
    goto error;
  if (brlapi__acceptKeyRanges(brl_handle, ranges, sizeof(ranges)/sizeof(ranges[0])) == -1)
    goto error;

  cmd_ranges = malloc(64 * sizeof(cmd_ranges[0]));
  for (i = 0, j = 0; i < sizeof(command_button)/sizeof(command_button[0]); i++) {
    if (command_button[i]) {
      cmd_ranges[j].first = cmd_ranges[j].last = BRLAPI_KEY_TYPE_CMD | (BRLAPI_KEY_CMD(0) + command_button[i]);
      j++;
    }
  }
  if (brlapi__acceptKeyRanges(brl_handle, cmd_ranges, j) == -1)
    goto error;
  free(cmd_ranges);

  *handle = (DWORD) brl_handle;
  return TRUE;

error:
  free(brl_handle);
  return FALSE;
}

bool WEBrailleClose(DWORD handle)
{
  brlapi_handle_t *brl_handle = (void*) handle;
  brlapi__closeConnection(brl_handle);
  return TRUE;
}

bool WEGetBrailleDisplayInfo(DWORD handle, int *numberOfCells, int *numberOfStatusCells)
{
  brlapi_handle_t *brl_handle = (void*) handle;
  unsigned int x, y;
  
  if (brlapi__getDisplaySize(brl_handle, &x, &y) == -1)
    return FALSE;

  *numberOfCells = x * y;
  *numberOfStatusCells = 0;
  return TRUE;
}

static bool keysent = FALSE;

bool WEGetBrailleKey(DWORD handle, DWORD *key1, DWORD *key2, int *routingKey, int *doubleRoutingKey, bool *routingOverStatusCell)
{
  brlapi_handle_t *brl_handle = (void*) handle;
  brlapi_keyCode_t code;
  unsigned cmd;

  *key1 = 0;
  *key2 = 0;
  *routingKey = 0;
  *doubleRoutingKey = 0;
  *routingOverStatusCell = FALSE;

  if (keysent) {
    keysent = FALSE;
    return TRUE;
  }

  if (brlapi__readKey(brl_handle, 0, &code) != 1)
    return FALSE;

  if (raw) {
    keysent = TRUE;
    *key1 = code & 0xffffffffu;
    *key2 = code >> 32;
    return TRUE;
  }

  if ((code & BRLAPI_KEY_TYPE_MASK) != BRLAPI_KEY_TYPE_CMD)
    return FALSE;

  cmd = code & BRLAPI_KEY_CMD_BLK_MASK;

  if (cmd == BRLAPI_KEY_CMD_ROUTE) {
    *routingKey = (code & BRLAPI_KEY_CMD_ARG_MASK) + 1;
  } else {
    unsigned button;
    unsigned arg = code & BRLAPI_KEY_CMD_ARG_MASK;

    if (arg >= sizeof(command_button)/sizeof(command_button[0]))
      return FALSE;

    button = command_button[arg];
    if ((button > 64) || (button == 0))
      return FALSE;
    if (button > 32)
      *key2 = 1 << (button - 1 - 32);
    else
      *key1 = 1 << (button - 1);
  }

  keysent = TRUE;
  return TRUE;
}

bool WEUpdateBrailleDisplay(DWORD handle, BYTE *pMainCells, int mainCellsCount, BYTE *pStatusCells, int statusCellsCount)
{
  brlapi_handle_t *brl_handle = (void*) handle;
  int numCells, numStatCells;

  if (!WEGetBrailleDisplayInfo(handle, &numCells, &numStatCells))
    return FALSE;

  {
    unsigned char dots[numCells];
    memcpy(dots, pMainCells, mainCellsCount);
    memset(dots + mainCellsCount, 0, numCells - mainCellsCount);
    if (brlapi__writeDots(brl_handle, dots) == -1)
      return FALSE;
  }

  return TRUE;
}
