/*
 * 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 "ttb.h"
#include "ttb_internal.h"
#include "ttb_compile.h"

static int inUcsBlock;

static int
getUnicodeCharacter (DataFile *file, wchar_t *character, const char *description) {
  DataOperand string;

  if (getDataOperand(file, &string, description)) {
    if (string.length > 2) {
      if ((string.characters[0] == WC_C('U')) &&
          (string.characters[1] == WC_C('+'))) {
        const wchar_t *digit = &string.characters[2];
        int length = string.length - 2;

        *character = 0;
        while (length) {
          int value;
          int shift;
          if (!isHexadecimalDigit(*digit++, &value, &shift)) break;

          *character <<= shift;
          *character |= value;
          length -= 1;
        }
        if (!length) return 1;
      }
    }

    reportDataError(file, "invalid Unicode character: %.*" PRIws,
                    string.length, string.characters);
  }

  return 0;
}

static int
testBrailleRepresentation (DataFile *file, wchar_t representation, unsigned char *dots) {
  if ((representation & ~UNICODE_CELL_MASK) == UNICODE_BRAILLE_ROW) {
    *dots = representation & UNICODE_CELL_MASK;
    return 1;
  } else {
    reportDataError(file, "invalid braille representation");
  }

  return 0;
}

static DATA_OPERANDS_PROCESSOR(processEncodingOperands) {
  DataOperand encoding;

  if (getDataOperand(file, &encoding, "character encoding name")) {
    if (!isKeyword(WS_C("UTF-8"), encoding.characters, encoding.length)) {
      reportDataError(file, "unsupported character encoding: %.*" PRIws,
                      encoding.length, encoding.characters);
    }
  }

  return 1;
}

static DATA_OPERANDS_PROCESSOR(processDelegateOperands) {
  DataOperand type;

  if (getDataOperand(file, &type, "delegate type")) {
    if (isKeyword(WS_C("FILE"), type.characters, type.length)) {
      DataOperand name;

      if (getDataOperand(file, &name, "file name")) {
        return includeDataFile(file, name.characters, name.length);
      }
    } else {
      return includeDataFile(file, type.characters, type.length);
    }
  }

  return 1;
}

static DATA_OPERANDS_PROCESSOR(processUcsBlockOperands) {
  DataOperand action;

  if (getDataOperand(file, &action, "UCS block action")) {
    const wchar_t *expected = inUcsBlock? WS_C("END"): WS_C("START");

    if (isKeyword(expected, action.characters, action.length)) {
      inUcsBlock = !inUcsBlock;
    } else {
      reportDataError(file, "unexpected UCS block action: %.*" PRIws " (expecting %" PRIws ")",
                      action.length, action.characters, expected);
    }
  }

  return 1;
}

static DATA_OPERANDS_PROCESSOR(processUcsCharOperands) {
  TextTableData *ttd = data;
  DataOperand string;

  if (getDataOperand(file, &string, "character string")) {
    if (string.length == 1) {
      DataOperand representation;

      if (getDataOperand(file, &representation, "braille representation")) {
        if (representation.length == 1) {
          unsigned char dots;

          if (testBrailleRepresentation(file, representation.characters[0], &dots)) {
            if (!setTextTableCharacter(ttd, string.characters[0], dots)) return 0;
          }
        } else {
          reportDataError(file, "multi-cell braille representation not supported");
        }
      }
    } else {
      reportDataError(file, "multi-character string not supported");
    }
  }

  return 1;
}

static DATA_OPERANDS_PROCESSOR(processUnicodeCharOperands) {
  TextTableData *ttd = data;
  wchar_t character;

  if (getUnicodeCharacter(file, &character, "character")) {
    wchar_t representation;

    if (getUnicodeCharacter(file, &representation, "braille representation")) {
      unsigned char dots;

      if (testBrailleRepresentation(file, representation, &dots)) {
        if (!setTextTableCharacter(ttd, character, dots)) return 0;
      }
    }
  }

  return 1;
}

static DATA_OPERANDS_PROCESSOR(processGnomeBrailleOperands) {
  if (inUcsBlock) {
    BEGIN_DATA_DIRECTIVE_TABLE
      {.name=WS_C("UCS-BLOCK"), .processor=processUcsBlockOperands},
      {.name=NULL, .processor=processUcsCharOperands},
    END_DATA_DIRECTIVE_TABLE

    return processDirectiveOperand(file, &directives, "gnome braille UCS block directive", data);
  } else {
    BEGIN_DATA_DIRECTIVE_TABLE
      {.name=WS_C("ENCODING"), .processor=processEncodingOperands},
  //  {.name=WS_C("NAME"), .processor=processNameOperands},
  //  {.name=WS_C("LOCALES"), .processor=processLocalesOperands},
  //  {.name=WS_C("UCS-SUFFIX"), .processor=processUcsSuffixOperands},
      {.name=WS_C("DELEGATE"), .processor=processDelegateOperands},
  //  {.name=WS_C("UTF8-STRING"), .processor=processUtf8StringOperands},
      {.name=WS_C("UCS-BLOCK"), .processor=processUcsBlockOperands},
      {.name=WS_C("UCS-CHAR"), .processor=processUcsCharOperands},
      {.name=WS_C("UNICODE-CHAR"), .processor=processUnicodeCharOperands},
  //  {.name=WS_C("UNKNOWN-CHAR"), .processor=processUnknownCharOperands},
    END_DATA_DIRECTIVE_TABLE

    return processDirectiveOperand(file, &directives, "gnome braille main directive", data);
  }
}

TextTableData *
processGnomeBrailleStream (FILE *stream, const char *name) {
  TextTableData *ttd;

  inUcsBlock = 0;
  if ((ttd = processTextTableLines(stream, name, processGnomeBrailleOperands))) {
    if (inUcsBlock) {
      reportDataError(NULL, "unterminated UCS block");
    }
  };

  return ttd;
}
