/*
 * 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>.
 */

#include "prologue.h"

#include <string.h>
#include <errno.h>

#include "log.h"

#include "brl_driver.h"
#include "brldefs-np.h"

#define PROBE_RETRY_LIMIT 3
#define PROBE_INPUT_TIMEOUT 1000
#define MAXIMUM_RESPONSE_SIZE 3
#define MAXIMUM_CELL_COUNT (NP_KEY_ROUTING_MAX - NP_KEY_ROUTING_MIN + 1)

BEGIN_KEY_NAME_TABLE(navigation)
  KEY_NAME_ENTRY(NP_KEY_Brl1, "Brl1"),
  KEY_NAME_ENTRY(NP_KEY_Brl2, "Brl2"),
  KEY_NAME_ENTRY(NP_KEY_Brl3, "Brl3"),
  KEY_NAME_ENTRY(NP_KEY_Brl4, "Brl4"),
  KEY_NAME_ENTRY(NP_KEY_Brl5, "Brl5"),
  KEY_NAME_ENTRY(NP_KEY_Brl6, "Brl6"),
  KEY_NAME_ENTRY(NP_KEY_Brl7, "Brl7"),
  KEY_NAME_ENTRY(NP_KEY_Brl8, "Brl8"),

  KEY_NAME_ENTRY(NP_KEY_Enter,     "Enter"),
  KEY_NAME_ENTRY(NP_KEY_Space,     "Space"),
  KEY_NAME_ENTRY(NP_KEY_PadCenter, "PadCenter"),
  KEY_NAME_ENTRY(NP_KEY_PadLeft,   "PadLeft"),
  KEY_NAME_ENTRY(NP_KEY_PadRight,  "PadRight"),
  KEY_NAME_ENTRY(NP_KEY_PadUp,     "PadUp"),
  KEY_NAME_ENTRY(NP_KEY_PadDown,   "PadDown"),
  KEY_NAME_ENTRY(NP_KEY_NavLeft,   "NavLeft"),
  KEY_NAME_ENTRY(NP_KEY_NavRight,  "NavRight"),

  KEY_GROUP_ENTRY(NP_GRP_RoutingKeys, "RoutingKey"),
END_KEY_NAME_TABLE

BEGIN_KEY_NAME_TABLES(all)
  KEY_NAME_TABLE(navigation),
END_KEY_NAME_TABLES

DEFINE_KEY_TABLE(all)

BEGIN_KEY_TABLE_LIST
  &KEY_TABLE_DEFINITION(all),
END_KEY_TABLE_LIST

struct BrailleDataStruct {
  unsigned char forceRewrite;
  unsigned char textCells[MAXIMUM_CELL_COUNT];
};

static int
writeBytes (BrailleDisplay *brl, const unsigned char *bytes, size_t count) {
  return writeBraillePacket(brl, NULL, bytes, count);
}

static size_t
readPacket (BrailleDisplay *brl, void *packet, size_t size) {
  unsigned char *bytes = packet;
  size_t offset = 0;
  size_t length = 0;

  while (1) {
    unsigned char byte;

    {
      int started = offset > 0;

      if (!gioReadByte(brl->gioEndpoint, &byte, started)) {
        if (started) logPartialPacket(bytes, offset);
        return 0;
      }
    }

  gotByte:
    if (offset == 0) {
      switch (byte) {
        case 0XFC:
          length = 2;
          break;

        case 0XFD:
          length = 2;
          break;

        default:
          logIgnoredByte(byte);
          continue;
      }
    } else {
      int unexpected = 0;

      if (offset == 1) {
        if (bytes[0] == 0XFD) {
          switch (byte) {
            case 0X2F:
              length = 3;
              break;

            default:
              unexpected = 1;
              break;
          }
        }
      }

      if (unexpected) {
        logShortPacket(bytes, offset);
        offset = 0;
        length = 0;
        goto gotByte;
      }
    }

    if (offset < size) {
      bytes[offset] = byte;

      if (offset == (length - 1)) {
        logInputPacket(bytes, length);
        return length;
      }
    } else {
      if (offset == size) logTruncatedPacket(bytes, offset);
      logDiscardedByte(byte);
    }

    offset += 1;
  }
}

static int
connectResource (BrailleDisplay *brl, const char *identifier) {
  GioDescriptor descriptor;
  gioInitializeDescriptor(&descriptor);

  descriptor.bluetooth.channelNumber = 1;

  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
    return 1;
  }

  return 0;
}

static int
writeIdentifyRequest (BrailleDisplay *brl) {
  return 1;
}
  
static BrailleResponseResult
isIdentityResponse (BrailleDisplay *brl, const void *packet, size_t size) {
  const unsigned char *bytes = packet;
    
  return ((size == 3) && (bytes[0] == 0XFD) && (bytes[1] == 0X2F))?
         BRL_RSP_DONE:
         BRL_RSP_UNEXPECTED;
}
                        
static int
brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
  if ((brl->data = malloc(sizeof(*brl->data)))) {
    memset(brl->data, 0, sizeof(*brl->data));

    if (connectResource(brl, device)) {
      unsigned char response[MAXIMUM_RESPONSE_SIZE];

      if (probeBrailleDisplay(brl, PROBE_RETRY_LIMIT, NULL, PROBE_INPUT_TIMEOUT,
                              writeIdentifyRequest,
                              readPacket, &response, sizeof(response),
                              isIdentityResponse)) {
        setBrailleKeyTable(brl, &KEY_TABLE_DEFINITION(all));
        MAKE_OUTPUT_TABLE(0X01, 0X04, 0X10, 0X02, 0X08, 0X20, 0X40, 0X80);

        brl->textColumns = MAXIMUM_CELL_COUNT;
        brl->data->forceRewrite = 1;
        return 1;
      }

      disconnectBrailleResource(brl, NULL);
    }

    free(brl->data);
  } else {
    logMallocError();
  }

  return 0;
}

static void
brl_destruct (BrailleDisplay *brl) {
  disconnectBrailleResource(brl, NULL);

  if (brl->data) {
    free(brl->data);
    brl->data = NULL;
  }
}

static int
brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
  if (cellsHaveChanged(brl->data->textCells, brl->buffer, brl->textColumns, NULL, NULL, &brl->data->forceRewrite)) {
    unsigned char bytes[(brl->textColumns * 2) + 2];
    unsigned char *byte = bytes;

    {
      int i;

      for (i=brl->textColumns-1; i>=0; i-=1) {
        *byte++ = 0XFC;
        *byte++ = translateOutputCell(brl->data->textCells[i]);
      }
    }

    *byte++ = 0XFD;
    *byte++ = 0X10;

    if (!writeBytes(brl, bytes, byte-bytes)) return 0;
  }

  return 1;
}

static int
brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
  unsigned char packet[MAXIMUM_RESPONSE_SIZE];
  size_t size;

  while ((size = readPacket(brl, packet, sizeof(packet)))) {
    switch (packet[0]) {
      case 0XFD:
        switch (packet[1]) {
          case 0X2F:
            continue;

          default:
            break;
        }
        break;

      case 0XFC: {
        unsigned int key = packet[1];
        if ((key >= NP_KEY_ROUTING_MIN) && (key <= NP_KEY_ROUTING_MAX)) {
          enqueueKey(brl, NP_GRP_RoutingKeys, (key - NP_KEY_ROUTING_MIN));
          continue;
        } else {
          int press = !!(key & NP_KEY_NAVIGATION_PRESS);
          if (press) key &= ~NP_KEY_NAVIGATION_PRESS;
          enqueueKeyEvent(brl, NP_GRP_NavigationKeys, key, press);
          continue;
        }
        break;
      }
    }

    logUnexpectedPacket(packet, size);
  }

  return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL;
}
