/*
 * 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-2025 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 "log.h"
#include "report.h"
#include "api_control.h"
#include "brl_utils.h"
#include "brl_dots.h"
#include "async_wait.h"
#include "ktb.h"

void
drainBrailleOutput (BrailleDisplay *brl, int minimumDelay) {
  int duration = brl->writeDelay + 1;

  brl->writeDelay = 0;
  if (duration < minimumDelay) duration = minimumDelay;
  asyncWait(duration);
}

void
announceBrailleOffline (void) {
  logMessage(LOG_DEBUG, "braille is offline");
  api.updateParameter(BRLAPI_PARAM_DEVICE_ONLINE, 0);
  report(REPORT_BRAILLE_DEVICE_OFFLINE, NULL);
}

void
announceBrailleOnline (void) {
  logMessage(LOG_DEBUG, "braille is online");
  api.updateParameter(BRLAPI_PARAM_DEVICE_ONLINE, 0);
  report(REPORT_BRAILLE_DEVICE_ONLINE, NULL);
}

void
setBrailleOffline (BrailleDisplay *brl) {
  if (!brl->isOffline) {
    brl->isOffline = 1;
    announceBrailleOffline();

    {
      KeyTable *keyTable = brl->keyTable;

      if (keyTable) releaseAllKeys(keyTable);
    }
  }
}

void
setBrailleOnline (BrailleDisplay *brl) {
  if (brl->isOffline) {
    brl->isOffline = 0;
    announceBrailleOnline();

    brl->writeDelay = 0;
  }
}

int
cellsHaveChanged (
  unsigned char *cells, const unsigned char *new, unsigned int count,
  unsigned int *from, unsigned int *to, unsigned char *force
) {
  unsigned int first = 0;

  if (force && *force) {
    *force = 0;
  } else if (memcmp(cells, new, count) != 0) {
    if (to) {
      while (count) {
        unsigned int last = count - 1;
        if (cells[last] != new[last]) break;
        count = last;
      }
    }

    if (from) {
      while (first < count) {
        if (cells[first] != new[first]) break;
        first += 1;
      }
    }
  } else {
    return 0;
  }

  if (from) *from = first;
  if (to) *to = count;

  memcpy(cells+first, new+first, count-first);
  return 1;
}

int
textHasChanged (
  wchar_t *text, const wchar_t *new, unsigned int count,
  unsigned int *from, unsigned int *to, unsigned char *force
) {
  unsigned int first = 0;

  if (force && *force) {
    *force = 0;
  } else if (wmemcmp(text, new, count) != 0) {
    if (to) {
      while (count) {
        unsigned int last = count - 1;
        if (text[last] != new[last]) break;
        count = last;
      }
    }

    if (from) {
      while (first < count) {
        if (text[first] != new[first]) break;
        first += 1;
      }
    }
  } else {
    return 0;
  }

  if (from) *from = first;
  if (to) *to = count;

  wmemcpy(text+first, new+first, count-first);
  return 1;
}

int
cursorHasChanged (int *cursor, int new, unsigned char *force) {
  if (force && *force) {
    *force = 0;
  } else if (new == *cursor) {
    return 0;
  }

  *cursor = new;
  return 1;
}

unsigned char
toLowerDigit (unsigned char upper) {
  unsigned char lower = 0;
  if (upper & BRL_DOT_1) lower |= BRL_DOT_3;
  if (upper & BRL_DOT_2) lower |= BRL_DOT_7;
  if (upper & BRL_DOT_4) lower |= BRL_DOT_6;
  if (upper & BRL_DOT_5) lower |= BRL_DOT_8;
  return lower;
}

/* Dots for landscape (counterclockwise-rotated) digits. */
const DigitsTable landscapeDigits = {
  [ 0] = BRL_DOT_1 | BRL_DOT_5 | BRL_DOT_2,
  [ 1] = BRL_DOT_4,
  [ 2] = BRL_DOT_4 | BRL_DOT_1,
  [ 3] = BRL_DOT_4 | BRL_DOT_5,
  [ 4] = BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_2,
  [ 5] = BRL_DOT_4 | BRL_DOT_2,
  [ 6] = BRL_DOT_4 | BRL_DOT_1 | BRL_DOT_5,
  [ 7] = BRL_DOT_4 | BRL_DOT_1 | BRL_DOT_5 | BRL_DOT_2,
  [ 8] = BRL_DOT_4 | BRL_DOT_1 | BRL_DOT_2,
  [ 9] = BRL_DOT_1 | BRL_DOT_5,
  [10] = BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_4 | BRL_DOT_5
};

/* Format landscape representation of numbers 0 through 99. */
unsigned char
makeLandscapeNumber (int x) {
  return landscapeDigits[(x / 10) % 10] | toLowerDigit(landscapeDigits[x % 10]);  
}

/* Format landscape flag state indicator. */
unsigned char
makeLandscapeFlag (int number, int on) {
  unsigned char dots = landscapeDigits[number % 10];
  if (on) dots |= toLowerDigit(landscapeDigits[10]);
  return dots;
}

/* Dots for seascape (clockwise-rotated) digits. */
const DigitsTable seascapeDigits = {
  [ 0] = BRL_DOT_5 | BRL_DOT_1 | BRL_DOT_4,
  [ 1] = BRL_DOT_2,
  [ 2] = BRL_DOT_2 | BRL_DOT_5,
  [ 3] = BRL_DOT_2 | BRL_DOT_1,
  [ 4] = BRL_DOT_2 | BRL_DOT_1 | BRL_DOT_4,
  [ 5] = BRL_DOT_2 | BRL_DOT_4,
  [ 6] = BRL_DOT_2 | BRL_DOT_5 | BRL_DOT_1,
  [ 7] = BRL_DOT_2 | BRL_DOT_5 | BRL_DOT_1 | BRL_DOT_4,
  [ 8] = BRL_DOT_2 | BRL_DOT_5 | BRL_DOT_4,
  [ 9] = BRL_DOT_5 | BRL_DOT_1,
  [10] = BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_4 | BRL_DOT_5
};

/* Format seascape representation of numbers 0 through 99. */
unsigned char
makeSeascapeNumber (int x) {
  return toLowerDigit(seascapeDigits[(x / 10) % 10]) | seascapeDigits[x % 10];  
}

/* Format seascape flag state indicator. */
unsigned char
makeSeascapeFlag (int number, int on) {
  unsigned char dots = toLowerDigit(seascapeDigits[number % 10]);
  if (on) dots |= seascapeDigits[10];
  return dots;
}

/* Dots for portrait digits - 2 numbers in one cells */
const DigitsTable portraitDigits = {
  [ 0] = BRL_DOT_2 | BRL_DOT_4 | BRL_DOT_5,
  [ 1] = BRL_DOT_1,
  [ 2] = BRL_DOT_1 | BRL_DOT_2,
  [ 3] = BRL_DOT_1 | BRL_DOT_4,
  [ 4] = BRL_DOT_1 | BRL_DOT_4 | BRL_DOT_5,
  [ 5] = BRL_DOT_1 | BRL_DOT_5,
  [ 6] = BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_4,
  [ 7] = BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_4 | BRL_DOT_5,
  [ 8] = BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_5,
  [ 9] = BRL_DOT_2 | BRL_DOT_4,
  [10] = BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_4 | BRL_DOT_5
};

/* Format portrait representation of numbers 0 through 99. */
unsigned char
makePortraitNumber (int x) {
  return portraitDigits[(x / 10) % 10] | toLowerDigit(portraitDigits[x % 10]);  
}

/* Format portrait flag state indicator. */
unsigned char
makePortraitFlag (int number, int on) {
  unsigned char dots = toLowerDigit(portraitDigits[number % 10]);
  if (on) dots |= portraitDigits[10];
  return dots;
}
