/*
 * 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-2014 by The BRLTTY Developers.
 *
 * BRLTTY comes with ABSOLUTELY NO WARRANTY.
 *
 * This is free software, placed under the terms of the
 * GNU General Public License, as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any
 * later version. Please see the file LICENSE-GPL for details.
 *
 * Web Page: http://mielke.cc/brltty/
 *
 * This software is maintained by Dave Mielke <dave@mielke.cc>.
 */

#include "prologue.h"

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

#include "options.h"
#include "log.h"
#include "file.h"

#include "ttb.h"
#include "ttb_internal.h"

#include "atb.h"
#include "atb_internal.h"

#include "ctb.h"
#include "ctb_internal.h"

BEGIN_OPTION_TABLE(programOptions)
END_OPTION_TABLE

typedef struct {
  void *object;
  const unsigned char *bytes;
  size_t size;
} TableData;

typedef struct {
  const char *extension;
  int (*load) (const char *path, TableData *data);
  void (*unload) (TableData *data);
} TableEntry;

static int
loadTextTable (const char *path, TableData *data) {
  TextTable *table = compileTextTable(path);
  if (!table) return 0;

  data->object = table;
  data->bytes = table->header.bytes;
  data->size = table->size;
  return 1;
}

static void
unloadTextTable (TableData *data) {
  destroyTextTable(data->object);
}

static int
loadAttributesTable (const char *path, TableData *data) {
  AttributesTable *table = compileAttributesTable(path);
  if (!table) return 0;

  data->object = table;
  data->bytes = table->header.bytes;
  data->size = table->size;
  return 1;
}

static void
unloadAttributesTable (TableData *data) {
  destroyAttributesTable(data->object);
}

static int
loadContractionTable (const char *path, TableData *data) {
  ContractionTable *table = compileContractionTable(path);
  if (!table) return 0;

  data->object = table;
  data->bytes = table->data.internal.header.bytes;
  data->size = table->data.internal.size;
  return 1;
}

static void
unloadContractionTable (TableData *data) {
  destroyContractionTable(data->object);
}

static const TableEntry tableEntries[] = {
  {
    .extension = TEXT_TABLE_EXTENSION,
    .load = loadTextTable,
    .unload = unloadTextTable
  }
  ,
  {
    .extension = ATTRIBUTES_TABLE_EXTENSION,
    .load = loadAttributesTable,
    .unload = unloadAttributesTable
  }
  ,
  {
    .extension = CONTRACTION_TABLE_EXTENSION,
    .load = loadContractionTable,
    .unload = unloadContractionTable
  }
  ,
  {
    .extension = NULL
  }
};

static const TableEntry *
findTableEntry (const char *extension) {
  const TableEntry *entry = tableEntries;

  while (entry->extension) {
    if (strcmp(entry->extension, extension) == 0) return entry;
    entry += 1;
  }

  logMessage(LOG_ERR, "unrecognized file extension: %s", extension);
  return NULL;
}

int
dumpBytes (FILE *stream, const unsigned char *bytes, size_t count) {
  const unsigned char *byte = bytes;
  const unsigned char *end = byte + count;
  int first = 1;
  int digits;

  if (count) {
    char buffer[0X10];
    digits = snprintf(buffer, sizeof(buffer), "%X", (unsigned int)count-1);
  } else {
    digits = 1;
  }

  while (byte < end) {
    while (!*byte && (byte < (end - 1))) byte += 1;

    {
      unsigned int counter = 0;
      unsigned int maximum = 8;

      if ((byte + maximum) != end) {
        while (maximum > 1) {
          if (byte[maximum-1]) break;
          maximum -= 1;
        }
      }

      while (byte < end) {
        if (first) {
          first = 0;
        } else {
          fprintf(stream, ",");
          if (ferror(stream)) goto outputError;

          if (!counter) {
            fprintf(stream, "\n");
            if (ferror(stream)) goto outputError;
          }
        }

        if (!counter) {
          fprintf(stream, "[0X%0*X] =", digits, (unsigned int)(byte-bytes));
          if (ferror(stream)) goto outputError;
        }

        fprintf(stream, " 0X%02X", *byte++);
        if (ferror(stdout)) goto outputError;

        if (++counter == maximum) break;
      }
    }
  }

  if (!first) {
    fprintf(stream, "\n");
    if (ferror(stream)) goto outputError;
  }

  return 1;

outputError:
  logMessage(LOG_ERR, "table write error: %s", strerror(errno));
  return 0;
}

int
main (int argc, char *argv[]) {
  ProgramExitStatus exitStatus;
  char *path;

  {
    static const OptionsDescriptor descriptor = {
      OPTION_TABLE(programOptions),
      .applicationName = "tbl2hex",
      .argumentsSummary = "table-file"
    };
    PROCESS_OPTIONS(descriptor, argc, argv);
  }

  if (argc == 0) {
    logMessage(LOG_ERR, "missing table file.");
    return PROG_EXIT_SYNTAX;
  }
  path = *argv++, argc--;

  {
    const char *extension = locatePathExtension(path);

    if (extension) {
      const TableEntry *entry = findTableEntry(extension);

      if (entry) {
        TableData data;
        if (entry->load(path, &data)) {
          if (dumpBytes(stdout, data.bytes, data.size)) {
            exitStatus = PROG_EXIT_SUCCESS;
          } else {
            exitStatus = PROG_EXIT_FATAL;
          }

          entry->unload(&data);
        } else {
          exitStatus = PROG_EXIT_FATAL;
        }
      } else {
        exitStatus = PROG_EXIT_SEMANTIC;
      }
    } else {
      logMessage(LOG_ERR, "no file extension");
      exitStatus = PROG_EXIT_SEMANTIC;
    }
  }

  return exitStatus;
}
