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

/* Voyager/braille.c - Braille display driver for Tieman Voyager displays.
 *
 * Written by Stéphane Doyon  <s.doyon@videotron.ca>
 *
 * It is being tested on Voyager 44, should also support Voyager 70.
 * It is designed to be compiled in BRLTTY version 4.1.
 *
 * History:
 * 0.21, January 2005:
 *       Remove gcc4 signedness/unsignedness incompatibilities.
 * 0.20, June 2004:
 *       Add statuscells parameter.
 *       Rename brlinput parameter to inputmode.
 *       Change default inputmode to no.
 *       Chorded functions work without chording when inputmode is no.
 *       Move complex routing key combinations to front/dot keys.
 *       Duplicate status key bindings on front/dot keys.
 *       Execute on first release rather than on all released.
 *       Add support for the part232 serial adapter.
 * 0.10, March 2004: Use BRLTTY core repeat functions. Add brlinput parameter
 *   and toggle to disallow braille typing.
 * 0.01, January 2004: fork from the original driver which relied on an
 *   in-kernel USB driver.
 */

#include "prologue.h"

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

#include "log.h"
#include "async_wait.h"
#include "ascii.h"
#include "bitfield.h"

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


BEGIN_KEY_NAME_TABLE(all)
  KEY_GROUP_ENTRY(VO_GRP_RoutingKeys, "RoutingKey"),

  KEY_NAME_ENTRY(VO_KEY_Dot1, "Dot1"),
  KEY_NAME_ENTRY(VO_KEY_Dot2, "Dot2"),
  KEY_NAME_ENTRY(VO_KEY_Dot3, "Dot3"),
  KEY_NAME_ENTRY(VO_KEY_Dot4, "Dot4"),
  KEY_NAME_ENTRY(VO_KEY_Dot5, "Dot5"),
  KEY_NAME_ENTRY(VO_KEY_Dot6, "Dot6"),
  KEY_NAME_ENTRY(VO_KEY_Dot7, "Dot7"),
  KEY_NAME_ENTRY(VO_KEY_Dot8, "Dot8"),

  KEY_NAME_ENTRY(VO_KEY_Thumb1, "Thumb1"),
  KEY_NAME_ENTRY(VO_KEY_Thumb2, "Thumb2"),
  KEY_NAME_ENTRY(VO_KEY_Left, "Left"),
  KEY_NAME_ENTRY(VO_KEY_Up, "Up"),
  KEY_NAME_ENTRY(VO_KEY_Down, "Down"),
  KEY_NAME_ENTRY(VO_KEY_Right, "Right"),
  KEY_NAME_ENTRY(VO_KEY_Thumb3, "Thumb3"),
  KEY_NAME_ENTRY(VO_KEY_Thumb4, "Thumb4"),
END_KEY_NAME_TABLE

BEGIN_KEY_NAME_TABLE(bp)
  KEY_GROUP_ENTRY(VO_GRP_RoutingKeys, "RoutingKey"),

  KEY_NAME_ENTRY(BP_KEY_Dot1, "Dot1"),
  KEY_NAME_ENTRY(BP_KEY_Dot2, "Dot2"),
  KEY_NAME_ENTRY(BP_KEY_Dot3, "Dot3"),
  KEY_NAME_ENTRY(BP_KEY_Dot4, "Dot4"),
  KEY_NAME_ENTRY(BP_KEY_Dot5, "Dot5"),
  KEY_NAME_ENTRY(BP_KEY_Dot6, "Dot6"),

  KEY_NAME_ENTRY(BP_KEY_Shift, "Shift"),
  KEY_NAME_ENTRY(BP_KEY_Space, "Space"),
  KEY_NAME_ENTRY(BP_KEY_Control, "Control"),

  KEY_NAME_ENTRY(BP_KEY_JoystickEnter, "JoystickEnter"),
  KEY_NAME_ENTRY(BP_KEY_JoystickLeft, "JoystickLeft"),
  KEY_NAME_ENTRY(BP_KEY_JoystickRight, "JoystickRight"),
  KEY_NAME_ENTRY(BP_KEY_JoystickUp, "JoystickUp"),
  KEY_NAME_ENTRY(BP_KEY_JoystickDown, "JoystickDown"),

  KEY_NAME_ENTRY(BP_KEY_ScrollLeft, "ScrollLeft"),
  KEY_NAME_ENTRY(BP_KEY_ScrollRight, "ScrollRight"),
END_KEY_NAME_TABLE

BEGIN_KEY_NAME_TABLES(all)
  KEY_NAME_TABLE(all),
END_KEY_NAME_TABLES

BEGIN_KEY_NAME_TABLES(bp)
  KEY_NAME_TABLE(bp),
END_KEY_NAME_TABLES

DEFINE_KEY_TABLE(all)
DEFINE_KEY_TABLE(bp)

BEGIN_KEY_TABLE_LIST
  &KEY_TABLE_DEFINITION(all),
  &KEY_TABLE_DEFINITION(bp),
END_KEY_TABLE_LIST


#define READY_BEEP_DURATION 200
#define MAXIMUM_CELL_COUNT 70 /* arbitrary max for allocations */

static unsigned char forceWrite;
static unsigned char cellCount;
#define IS_TEXT_RANGE(key1,key2) (((key1) <= (key2)) && ((key2) < cellCount))
#define IS_TEXT_KEY(key) IS_TEXT_RANGE((key), (key))

/* Structure to remember which keys are pressed */
typedef struct {
  uint16_t navigation;
  unsigned char routing[MAXIMUM_CELL_COUNT];
} Keys;

static Keys pressedKeys;
static char keysInitialized;

static void
initializeKeys (void) {
  if (!keysInitialized) {
    memset(&pressedKeys, 0, sizeof(pressedKeys));
    keysInitialized = 1;
  }
}

static void
updateKeys (BrailleDisplay *brl, const unsigned char *packet) {
  Keys currentKeys;

  unsigned char navigationPresses[0X10];
  int navigationPressCount = 0;

  unsigned char routingPresses[6];
  int routingPressCount = 0;

  initializeKeys();
  memset(&currentKeys, 0, sizeof(currentKeys));
  currentKeys.navigation = (packet[1] << 8) | packet[0];

  {
    unsigned char key = 0;
    uint16_t bit = 0X1;

    while (key < 0X10) {
      if ((pressedKeys.navigation & bit) && !(currentKeys.navigation & bit)) {
        enqueueKeyEvent(brl, VO_GRP_NavigationKeys, key, 0);
      } else if (!(pressedKeys.navigation & bit) && (currentKeys.navigation & bit)) {
        navigationPresses[navigationPressCount++] = key;
      }

      bit <<= 1;
      key += 1;
    }
  }
  
  {
    int i;

    for (i=2; i<8; i+=1) {
      unsigned char key = packet[i];
      if (!key) break;

      if ((key < 1) || (key > cellCount)) {
        logMessage(LOG_NOTICE, "invalid routing key number: %u", key);
        continue;
      }
      key -= 1;

      currentKeys.routing[key] = 1;
      if (!pressedKeys.routing[key]) routingPresses[routingPressCount++] = key;
    }
  }

  {
    unsigned char key;

    for (key=0; key<cellCount; key+=1)
      if (pressedKeys.routing[key] && !currentKeys.routing[key])
        enqueueKeyEvent(brl, VO_GRP_RoutingKeys, key, 0);
  }

  while (navigationPressCount)
    enqueueKeyEvent(brl, VO_GRP_NavigationKeys, navigationPresses[--navigationPressCount], 1);

  while (routingPressCount)
    enqueueKeyEvent(brl, VO_GRP_RoutingKeys, routingPresses[--routingPressCount], 1);

  pressedKeys = currentKeys;
}


typedef struct {
  int (*getCellCount) (BrailleDisplay *brl, unsigned char *length);
  int (*logSerialNumber) (BrailleDisplay *brl);
  int (*logHardwareVersion) (BrailleDisplay *brl);
  int (*logFirmwareVersion) (BrailleDisplay *brl);
  int (*setDisplayVoltage) (BrailleDisplay *brl, unsigned char voltage);
  int (*getDisplayVoltage) (BrailleDisplay *brl, unsigned char *voltage);
  int (*getDisplayCurrent) (BrailleDisplay *brl, unsigned char *current);
  int (*setDisplayState) (BrailleDisplay *brl, unsigned char state);
  int (*writeBraille) (BrailleDisplay *brl, const unsigned char *cells, unsigned char count, unsigned char start);
  int (*updateKeys) (BrailleDisplay *brl);
  int (*soundBeep) (BrailleDisplay *brl, unsigned char duration);
} ProtocolOperations;

static const ProtocolOperations *protocol;


#define SERIAL_BAUD 38400
#define SERIAL_READY_DELAY 400
#define SERIAL_INPUT_TIMEOUT 100
#define SERIAL_WAIT_TIMEOUT 200

#define BLUETOOTH_CHANNEL_NUMBER 1
#define BLUETOOTH_READY_DELAY 800

static int
tellResource (
  BrailleDisplay *brl,
  uint8_t request, uint16_t value, uint16_t index,
  const unsigned char *data, uint16_t size
) {
  ssize_t result = gioTellResource(brl->gioEndpoint, UsbControlRecipient_Endpoint, UsbControlType_Vendor,
                                   request, value, index, data, size);
  return result != -1;
}

static int
askResource (
  BrailleDisplay *brl,
  uint8_t request, uint16_t value, uint16_t index,
  unsigned char *buffer, uint16_t size
) {
  ssize_t result = gioAskResource(brl->gioEndpoint, UsbControlRecipient_Endpoint, UsbControlType_Vendor,
                                  request, value, index, buffer, size);
  int ok = result != -1;

  if (ok) {
    logInputPacket(buffer, result);
  }

  return ok;
}


static const char *const serialDeviceNames[] = {"Serial Adapter", "Base Unit"};

static int
writeSerialPacket (BrailleDisplay *brl, unsigned char code, unsigned char *data, unsigned char count) {
  unsigned char buffer[2 + (count * 2)];
  unsigned char size = 0;
  unsigned char index;

  buffer[size++] = ASCII_ESC;
  buffer[size++] = code;

  for (index=0; index<count; ++index)
    if ((buffer[size++] = data[index]) == buffer[0])
      buffer[size++] = buffer[0];

  return writeBraillePacket(brl, NULL, buffer, size);
}

static int
readSerialPacket (BrailleDisplay *brl, unsigned char *packet, int size) {
  int started = 0;
  int escape = 0;
  int offset = 0;
  int length = 0;

  while (1) {
    unsigned char byte;

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

    if (byte == ASCII_ESC) {
      if ((escape = !escape)) continue;
    } else if (escape) {
      escape = 0;

      if (offset > 0) {
        logShortPacket(packet, offset);
        offset = 0;
        length = 0;
      } else {
        started = 1;
      }
    }

    if (!started) {
      logIgnoredByte(byte);
      continue;
    }

    if (offset < size) {
      if (offset == 0) {
        switch (byte) {
          case 0X43:
          case 0X47:
            length = 2;
            break;

          case 0X4C:
            length = 3;
            break;

          case 0X46:
          case 0X48:
            length = 5;
            break;

          case 0X4B:
            length = 9;
            break;

          case 0X53:
            length = 10;
            break;

          default:
            logUnknownPacket(byte);
            started = 0;
            continue;
        }
      }

      packet[offset] = byte;
    } else {
      if (offset == size) logTruncatedPacket(packet, offset);
      logDiscardedByte(byte);
    }

    if (++offset == length) {
      if (offset > size) {
        offset = 0;
        length = 0;
        started = 0;
        continue;
      }

      logInputPacket(packet, offset);
      return length;
    }
  }
}

static int
nextSerialPacket (BrailleDisplay *brl, unsigned char code, unsigned char *buffer, int size, int wait) {
  int length;

  if (wait)
    if (!awaitBrailleInput(brl, SERIAL_WAIT_TIMEOUT))
      return 0;

  while ((length = readSerialPacket(brl, buffer, size))) {
    if (buffer[0] == code) return length;
    logUnexpectedPacket(buffer, length);
  }

  return 0;
}

static int
getSerialCellCount (BrailleDisplay *brl, unsigned char *count) {
  const unsigned int code = 0X4C;
  if (writeSerialPacket(brl, code, NULL, 0)) {
    unsigned char buffer[3];
    if (nextSerialPacket(brl, code, buffer, sizeof(buffer), 1)) {
      *count = buffer[2];
      return 1;
    }
  }
  return 0;
}

static int
logSerialSerialNumber (BrailleDisplay *brl) {
  unsigned char device;

  for (device=0; device<ARRAY_COUNT(serialDeviceNames); ++device) {
    const unsigned char code = 0X53;
    unsigned char buffer[10];

    if (!writeSerialPacket(brl, code, &device, 1)) return 0;
    if (!nextSerialPacket(brl, code, buffer, sizeof(buffer), 1)) return 0;
    logMessage(LOG_INFO, "%s Serial Number: %02X%02X%02X%02X%02X%02X%02X%02X",
               serialDeviceNames[buffer[1]],
               buffer[2], buffer[3], buffer[4], buffer[5],
               buffer[6], buffer[7], buffer[8], buffer[9]);
  }

  return 1;
}

static int
logSerialHardwareVersion (BrailleDisplay *brl) {
  unsigned char device;

  for (device=0; device<ARRAY_COUNT(serialDeviceNames); ++device) {
    const unsigned char code = 0X48;
    unsigned char buffer[5];

    if (!writeSerialPacket(brl, code, &device, 1)) return 0;
    if (!nextSerialPacket(brl, code, buffer, sizeof(buffer), 1)) return 0;
    logMessage(LOG_INFO, "%s Hardware Version: %c.%c.%c", 
               serialDeviceNames[buffer[1]],
               buffer[2], buffer[3], buffer[4]);
  }

  return 1;
}

static int
logSerialFirmwareVersion (BrailleDisplay *brl) {
  unsigned char device;

  for (device=0; device<ARRAY_COUNT(serialDeviceNames); ++device) {
    const unsigned char code = 0X46;
    unsigned char buffer[5];

    if (!writeSerialPacket(brl, code, &device, 1)) return 0;
    if (!nextSerialPacket(brl, code, buffer, sizeof(buffer), 1)) return 0;
    logMessage(LOG_INFO, "%s Firmware Version: %c.%c.%c", 
               serialDeviceNames[buffer[1]],
               buffer[2], buffer[3], buffer[4]);
  }

  return 1;
}

static int
setSerialDisplayVoltage (BrailleDisplay *brl, unsigned char voltage) {
  return writeSerialPacket(brl, 0X56, &voltage, 1);
}

static int
getSerialDisplayVoltage (BrailleDisplay *brl, unsigned char *voltage) {
  const unsigned char code = 0X47;
  if (writeSerialPacket(brl, code, NULL, 0)) {
    unsigned char buffer[2];
    if (nextSerialPacket(brl, code, buffer, sizeof(buffer), 1)) {
      *voltage = buffer[1];
      return 1;
    }
  }
  return 0;
}

static int
getSerialDisplayCurrent (BrailleDisplay *brl, unsigned char *current) {
  const unsigned int code = 0X43;
  if (writeSerialPacket(brl, code, NULL, 0)) {
    unsigned char buffer[2];
    if (nextSerialPacket(brl, code, buffer, sizeof(buffer), 1)) {
      *current = buffer[1];
      return 1;
    }
  }
  return 0;
}

static int
setSerialDisplayState (BrailleDisplay *brl, unsigned char state) {
  return writeSerialPacket(brl, 0X44, &state, 1);
}

static int
writeSerialBraille (BrailleDisplay *brl, const unsigned char *cells, unsigned char count, unsigned char start) {
  unsigned char buffer[2 + count];
  unsigned char size = 0;
  buffer[size++] = start;
  buffer[size++] = count;
  memcpy(&buffer[size], cells, count);
  size += count;
  return writeSerialPacket(brl, 0X42, buffer, size);
}

static int
updateSerialKeys (BrailleDisplay *brl) {
  const unsigned char code = 0X4B;
  unsigned char packet[9];

  while (nextSerialPacket(brl, code, packet, sizeof(packet), 0)) {
    updateKeys(brl, &packet[1]);
  }

  return errno == EAGAIN;
}

static int
soundSerialBeep (BrailleDisplay *brl, unsigned char duration) {
  return writeSerialPacket(brl, 0X41, &duration, 1);
}

static const ProtocolOperations serialProtocolOperations = {
  .getCellCount = getSerialCellCount,
  .logSerialNumber = logSerialSerialNumber,
  .logHardwareVersion = logSerialHardwareVersion,
  .logFirmwareVersion = logSerialFirmwareVersion,
  .setDisplayVoltage = setSerialDisplayVoltage,
  .getDisplayVoltage = getSerialDisplayVoltage,
  .getDisplayCurrent = getSerialDisplayCurrent,
  .setDisplayState = setSerialDisplayState,
  .writeBraille = writeSerialBraille,
  .updateKeys = updateSerialKeys,
  .soundBeep = soundSerialBeep
};


static int
getUsbCellCount (BrailleDisplay *brl, unsigned char *count) {
  unsigned char buffer[2];

  if (!askResource(brl, 0X06, 0, 0, buffer, sizeof(buffer))) return 0;
  *count = buffer[1];
  return 1;
}

static wchar_t *
getUsbString (BrailleDisplay *brl, uint8_t request) {
  UsbDescriptor descriptor;

  if (askResource(brl, request, 0, 0, descriptor.bytes, sizeof(descriptor.bytes))) {
    size_t count = (descriptor.string.bLength - 2) / sizeof(descriptor.string.wData[0]);
    wchar_t *string = malloc((count + 1) * sizeof(*string));

    if (string) {
      string[count] = 0;

      while (count) {
        count -= 1;
        string[count] = getLittleEndian16(descriptor.string.wData[count]);
      }

      return string;
    } else {
      logMallocError();
    }
  }

  return NULL;
}

static int
logUsbString (BrailleDisplay *brl, uint8_t request, const char *description) {
  wchar_t *string = getUsbString(brl, request);

  if (string) {
    logMessage(LOG_INFO, "%s: %" PRIws, description, string);
    free(string);
    return 1;
  }

  return 0;
}

static int
logUsbSerialNumber (BrailleDisplay *brl) {
  return logUsbString(brl, 0X03, "Serial Number");
}

static int
logUsbHardwareVersion (BrailleDisplay *brl) {
  unsigned char buffer[2];

  if (!askResource(brl, 0X04, 0, 0, buffer, sizeof(buffer))) return 0;
  logMessage(LOG_INFO, "Hardware Version: %u.%u",
             buffer[0], buffer[1]);
  return 1;
}

static int
logUsbFirmwareVersion (BrailleDisplay *brl) {
  return logUsbString(brl, 0X05, "Firmware Version");
}

static int
setUsbDisplayVoltage (BrailleDisplay *brl, unsigned char voltage) {
  return tellResource(brl, 0X01, voltage, 0, NULL, 0);
}

static int
getUsbDisplayVoltage (BrailleDisplay *brl, unsigned char *voltage) {
  unsigned char buffer[1];

  if (!askResource(brl, 0X02, 0, 0, buffer, sizeof(buffer))) return 0;
  *voltage = buffer[0];
  return 1;
}

static int
getUsbDisplayCurrent (BrailleDisplay *brl, unsigned char *current) {
  unsigned char buffer[1];

  if (!askResource(brl, 0X08, 0, 0, buffer, sizeof(buffer))) return 0;
  *current = buffer[0];
  return 1;
}

static int
setUsbDisplayState (BrailleDisplay *brl, unsigned char state) {
  return tellResource(brl, 0X00, state, 0, NULL, 0);
}

static int
writeUsbBraille (BrailleDisplay *brl, const unsigned char *cells, unsigned char count, unsigned char start) {
  return tellResource(brl, 0X07, 0, start, cells, count);
}

static int
updateUsbKeys (BrailleDisplay *brl) {
  while (1) {
    unsigned char packet[8];

    {
      ssize_t result = gioReadData(brl->gioEndpoint, packet, sizeof(packet), 0);
      if (!result) return 1;

      if (result < 0) {
        if (errno == ENODEV) {
          /* Display was disconnected */
          return 0;
        }

        logMessage(LOG_ERR, "USB read error: %s", strerror(errno));
        keysInitialized = 0;
        return 1;
      }

      if (result < sizeof(packet)) {
        /* The display should only ever deliver packets of exactly 8 bytes */
        logPartialPacket(packet, result);
        keysInitialized = 0;
        return 1;
      }

      logInputPacket(packet, result);
    }

    updateKeys(brl, packet);
  }
}

static int
soundUsbBeep (BrailleDisplay *brl, unsigned char duration) {
  return tellResource(brl, 0X09, duration, 0, NULL, 0);
}

static const ProtocolOperations usbProtocolOperations = {
  .getCellCount = getUsbCellCount,
  .logSerialNumber = logUsbSerialNumber,
  .logHardwareVersion = logUsbHardwareVersion,
  .logFirmwareVersion = logUsbFirmwareVersion,
  .setDisplayVoltage = setUsbDisplayVoltage,
  .getDisplayVoltage = getUsbDisplayVoltage,
  .getDisplayCurrent = getUsbDisplayCurrent,
  .setDisplayState = setUsbDisplayState,
  .writeBraille = writeUsbBraille,
  .updateKeys = updateUsbKeys,
  .soundBeep = soundUsbBeep
};


static int
connectResource (BrailleDisplay *brl, const char *identifier) {
  SerialParameters serialParameters;

  BEGIN_USB_CHANNEL_DEFINITIONS
    { /* all models */
      .vendor=0X0798, .product=0X0001, 
      .configuration=1, .interface=0, .alternative=0,
      .inputEndpoint=1
    },
  END_USB_CHANNEL_DEFINITIONS

  GioDescriptor descriptor;
  gioInitializeDescriptor(&descriptor);

  gioInitializeSerialParameters(&serialParameters);
  serialParameters.baud = SERIAL_BAUD;
  serialParameters.flowControl = SERIAL_FLOW_HARDWARE;

  descriptor.serial.parameters = &serialParameters;
  descriptor.serial.options.applicationData = &serialProtocolOperations;
  descriptor.serial.options.readyDelay = SERIAL_READY_DELAY;
  descriptor.serial.options.inputTimeout = SERIAL_INPUT_TIMEOUT;

  descriptor.usb.channelDefinitions = usbChannelDefinitions;
  descriptor.usb.options.applicationData = &usbProtocolOperations;

  descriptor.bluetooth.channelNumber = BLUETOOTH_CHANNEL_NUMBER;
  descriptor.bluetooth.options.applicationData = &serialProtocolOperations;
  descriptor.bluetooth.options.readyDelay = BLUETOOTH_READY_DELAY;
  descriptor.bluetooth.options.inputTimeout = SERIAL_INPUT_TIMEOUT;

  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
    protocol = gioGetApplicationData(brl->gioEndpoint);
    return 1;
  }

  return 0;
}


typedef struct {
  const char *name;
  const KeyTableDefinition *keyTable;
} ProductEntry;

static const ProductEntry productEntry_Voyager = {
  .name = "Voyager",
  .keyTable = &KEY_TABLE_DEFINITION(all)
};

static const ProductEntry productEntry_BraillePen = {
  .name = "Braille Pen",
  .keyTable = &KEY_TABLE_DEFINITION(bp)
};


typedef struct {
  const ProductEntry *product;
  int (*writeBraille) (BrailleDisplay *brl, const unsigned char *cells, unsigned char count, unsigned char start);
  unsigned char reportedCellCount;
  unsigned char actualCellCount;
  unsigned partialUpdates:1;
} ModelEntry;

static const ModelEntry *model;

typedef struct {
  struct {
    const unsigned char *cells;
    unsigned char offset;
    unsigned char count;
  } from;

  struct {
    unsigned char *cells;
    unsigned char offset;
  } to;
} WriteBrailleData;

static void
addHiddenCells (WriteBrailleData *wbd, unsigned char size) {
  while (size) {
    wbd->to.cells[wbd->to.offset++] = 0;
    size -= 1;
  }
}

static void
addActualCells (WriteBrailleData *wbd, unsigned char size) {
  unsigned char count;

  if (!size) size = wbd->from.count;
  if ((count = size) > wbd->from.count) count = wbd->from.count;

  if (count) {
    memcpy(&wbd->to.cells[wbd->to.offset], &wbd->from.cells[wbd->from.offset], count);
    wbd->from.count -= count;
    wbd->from.offset += count;
    wbd->to.offset += count;
  }

  addHiddenCells(wbd, size-count);
}

static int
writeBraille0 (BrailleDisplay *brl, const unsigned char *cells, unsigned char count, unsigned char start) {
  return protocol->writeBraille(brl, cells, count, start);
}

static int
writeBraille2 (BrailleDisplay *brl, const unsigned char *cells, unsigned char count, unsigned char start) {
  if (!model->partialUpdates) {
    unsigned char buffer[count + 2];
    WriteBrailleData wbd = {
      .from = {
        .cells = cells,
        .offset = 0,
        .count = cellCount
      },

      .to = {
        .cells = buffer,
        .offset = 0
      }
    };

    addHiddenCells(&wbd, 2);
    addActualCells(&wbd, 0);
    return protocol->writeBraille(brl, buffer, sizeof(buffer), 0);
  }

  return protocol->writeBraille(brl, cells, count, start+2);
}

static int
writeBraille4 (BrailleDisplay *brl, const unsigned char *cells, unsigned char count, unsigned char start) {
  if (!model->partialUpdates) {
    unsigned char buffer[count + 4];
    WriteBrailleData wbd = {
      .from = {
        .cells = cells,
        .offset = 0,
        .count = cellCount
      },

      .to = {
        .cells = buffer,
        .offset = 0
      }
    };

    addHiddenCells(&wbd, 2);
    addActualCells(&wbd, 6);
    addHiddenCells(&wbd, 2);
    addActualCells(&wbd, 0);
    return protocol->writeBraille(brl, buffer, sizeof(buffer), 0);
  }

  if (start >= 6) {
    return protocol->writeBraille(brl, &cells[start], count, start+4);
  }

  if ((start + count) <= 6) {
    return protocol->writeBraille(brl, &cells[start], count, start+2);
  }

  {
    unsigned char buffer[count + 2];
    WriteBrailleData wbd = {
      .from = {
        .cells = cells,
        .offset = start,
        .count = count
      },

      .to = {
        .cells = buffer,
        .offset = 0
      }
    };

    addActualCells(&wbd, 6-start);
    addHiddenCells(&wbd, 2);
    addActualCells(&wbd, 0);
    return protocol->writeBraille(brl, buffer, sizeof(buffer), start+2);
  }
}

static const ModelEntry modelTable[] = {
  { .product = &productEntry_Voyager,
    .reportedCellCount = 48,
    .actualCellCount = 44,
    .writeBraille = writeBraille4,
    .partialUpdates = 1,
  },

  { .product = &productEntry_Voyager,
    .reportedCellCount = 72,
    .actualCellCount = 70,
    .writeBraille = writeBraille2,
    .partialUpdates = 1,
  },

  { .product = &productEntry_BraillePen,
    .reportedCellCount = 12,
    .actualCellCount = 12,
    .writeBraille = writeBraille0,
  },

  { .product = NULL }
};


/* Global variables */
static unsigned char *previousCells = NULL; /* previous pattern displayed */
static unsigned char *translatedCells = NULL; /* buffer to prepare new pattern */

/* Voltage: from 0->300V to 255->200V.
 * Presumably this is voltage for dot firmness.
 * Presumably 0 makes dots hardest, 255 makes them softest.
 * We are told 265V is normal operating voltage but we don't know the scale.
 */
static int
setBrailleFirmness (BrailleDisplay *brl, BrailleFirmness setting) {
  unsigned char voltage = 0XFF - (setting * 0XFF / BRL_FIRMNESS_MAXIMUM);
  logMessage(LOG_DEBUG, "setting display voltage: %02X", voltage);
  return protocol->setDisplayVoltage(brl, voltage);
}

static int
soundBeep (BrailleDisplay *brl, unsigned char duration) {
  if (!protocol->soundBeep(brl, duration)) return 0;
  asyncWait(duration);
  return 1;
}

static int
brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
  if (connectResource(brl, device)) {
    if (protocol->getCellCount(brl, &cellCount)) {
      model = modelTable;

      while (model->product) {
        if (model->reportedCellCount == cellCount) {
          const ProductEntry *product = model->product;
          logMessage(LOG_INFO, "Product: %s", product->name);

          cellCount = model->actualCellCount;
          logMessage(LOG_INFO, "Cell Count: %u", cellCount);

          protocol->logSerialNumber(brl);
          protocol->logHardwareVersion(brl);
          protocol->logFirmwareVersion(brl);

          /* translatedCells holds the status cells and the text cells.
           * We export directly to BRLTTY only the text cells.
           */
          brl->textColumns = cellCount;		/* initialize size of display */
          brl->textRows = 1;		/* always 1 */

          setBrailleKeyTable(brl, product->keyTable);
          brl->setBrailleFirmness = setBrailleFirmness;

          if ((previousCells = malloc(cellCount))) {
            if ((translatedCells = malloc(cellCount))) {
              if (protocol->setDisplayState(brl, 1)) {
                makeOutputTable(dotsTable_ISO11548_1);
                keysInitialized = 0;
                forceWrite = 1;

                soundBeep(brl, READY_BEEP_DURATION);
                return 1;
              }

              free(translatedCells);
              translatedCells = NULL;
            } else {
              logMallocError();
            }

            free(previousCells);
            previousCells = NULL;
          } else {
            logMallocError();
          }

          break;
        }

        model += 1;
      }

      if (!model->reportedCellCount) {
        logMessage(LOG_ERR, "unsupported cell count: %u", cellCount);
        model = NULL;
      }
    }

    disconnectBrailleResource(brl, NULL);
  }

  return 0;
}

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

  if (translatedCells) {
    free(translatedCells);
    translatedCells = NULL;
  }

  if (previousCells) {
    free(previousCells);
    previousCells = NULL;
  }
}

static int
brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
  unsigned int from = 0;
  unsigned int to = cellCount;
  int changed;

  if (model->partialUpdates) {
    changed = cellsHaveChanged(previousCells, brl->buffer, cellCount, &from, &to, &forceWrite);
  } else {
    changed = cellsHaveChanged(previousCells, brl->buffer, cellCount, NULL, NULL, &forceWrite);
  }

  if (changed) {
    translateOutputCells(&translatedCells[from], &brl->buffer[from], to-from);
    if (!model->writeBraille(brl, translatedCells, to-from, from)) return 0;
  }

  return 1;
}

static int
brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
  return protocol->updateKeys(brl)? EOF: BRL_CMD_RESTARTBRL;
}
