
#include <stdio.h>
#include <inttypes.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>

#include "types.h"
#include "z_ucs.h"


static char in_filename[21] = "../locales/xx_XX.txt";
static char out_filename[10] = "locales.c";
//static char format_string[7] = "0x%0_x";
static char format_string[7] = "0x%02x";
/*@dependent@*/ static FILE *in;
/*@dependent@*/ static FILE *out;
static uint16_t output_index;

struct locale_file
{
  char *locale_code;
  char *filename;
  int number_of_messages;
};

struct locale_module
{
  char *dirname;
  char *module_code;
  struct locale_file **locale_files;
  int number_of_file_structs;
  int max_number_of_file_structs;
};

struct locale_module **locale_modules = NULL;
int number_of_module_structs = 0;
int max_number_of_module_structs = 0;

int total_number_of_locales = 0;
int maximum_number_of_messages = 0;
  

static z_ucs input_char()
{
  z_ucs input;

  if ((input = parse_utf_8_char(in)) == UEOF)
  {
    fprintf(stderr, "Premature end of file %s.\n", in_filename);
    exit(EXIT_FAILURE);
  }

  return (z_ucs)input;
}


static void output_string(char *output)
{
  if ((fputs(output, out)) == EOF)
  {
    fprintf(stderr, "Error writing to %s.\n", out_filename);
    exit(EXIT_FAILURE);
  }
}


static void output_hex_word(z_ucs output)
{
  if (output_index == 0)
  {
    output_string("   ");
  }
  else
  {
    if ((output_index % 6) == 0)
    {
      output_string(",\n   ");
    }
    else
    {
      output_string(", ");
    }
  }

  /*@-formatconst@*/
  if ((fprintf(
          out,
          format_string,
          (unsigned int)output)
      ) /*@+formatconst@*/
      == EOF)
  {
    fprintf(stderr, "Error writing to %s.\n", out_filename);
    exit(EXIT_FAILURE);
  }

  output_index++;
}


int main()
{
  DIR *module_dir;
  DIR *locale_dir;
  struct dirent* module_entry;
  struct dirent* locale_entry;
  int messages_processed = 0;
  z_ucs input;
  int in_char;
  struct locale_module *module;
  struct locale_file *file;
  char *string;
  int module_index, locale_index;

  if ((module_dir = opendir("..")) == NULL)
  {
    fprintf(stderr, "Could not open directory \"..\".");
    exit(EXIT_FAILURE);
  }

  while ((module_entry = readdir(module_dir)) != NULL)
  {
    if (strstr(module_entry->d_name, "locales-") != module_entry->d_name)
      continue;

    if (number_of_module_structs == max_number_of_module_structs)
    {
      max_number_of_module_structs += 10;

      if ((locale_modules = (struct locale_module**)realloc(
              locale_modules,
              max_number_of_module_structs * sizeof(struct locale_module*)
              )) == NULL)
      {
        fprintf(stderr, "realloc() returned NULL.\n");
        exit(EXIT_FAILURE);
      }
    }

    if ((module = (struct locale_module*)malloc(
            sizeof(struct locale_module))) == NULL)
    {
      fprintf(stderr, "malloc() returned NULL.\n");
      exit(EXIT_FAILURE);
    }

    printf("Found locale-module in directory \"%s\".\n", module_entry->d_name);

    if ((string = (char*)malloc(4 + strlen(module_entry->d_name))) == NULL)
    {
      fprintf(stderr, "malloc() returned NULL.\n");
      exit(EXIT_FAILURE);
    }

    strcpy(string, "../");
    strcpy(string + 3, module_entry->d_name);

    module->dirname = string;

    if ((string = (char*)malloc(strlen(module_entry->d_name) - 8)) == NULL)
    {
      fprintf(stderr, "malloc() returned NULL.\n");
      exit(EXIT_FAILURE);
    }

    strcpy(string, module_entry->d_name + 8);

    module->module_code = string;

    module->locale_files = NULL;
    module->number_of_file_structs = 0;
    module->max_number_of_file_structs = 0;

    locale_modules[number_of_module_structs] = module;

    if ((locale_dir = opendir(module->dirname)) == NULL)
    {
      fprintf(stderr, "Could not open directory \"%s\".", module->dirname);
      exit(EXIT_FAILURE);
    }

    while ((locale_entry = readdir(locale_dir)) != NULL)
    {
      if (strlen(locale_entry->d_name) != 9)
        continue;

      if (
          (((int)locale_entry->d_name[0] < 0x61) ||
           ((int)locale_entry->d_name[0] > 0x7a))
          ||
          (((int)locale_entry->d_name[1] < 0x61) ||
           ((int)locale_entry->d_name[1] > 0x7a))
          ||
          ( (int)locale_entry->d_name[2] != 0x5f)
          ||
          (((int)locale_entry->d_name[3] < 0x41) ||
           ((int)locale_entry->d_name[3] > 0x5a))
          ||
          (((int)locale_entry->d_name[4] < 0x41) ||
           ((int)locale_entry->d_name[4] > 0x5a))
          ||
          (strcmp(locale_entry->d_name+5, ".txt") != 0)
         )
        continue;

      printf("Found locale-file \"%s\".\n", locale_entry->d_name);

      if (module->number_of_file_structs
          == module->max_number_of_file_structs)
      {
        module->max_number_of_file_structs += 10;

        if ((module->locale_files = (struct locale_file**)realloc(
                module->locale_files,
                module->max_number_of_file_structs
                * sizeof(struct locale_file*)
                )) == NULL)
        {
          fprintf(stderr, "realloc() returned NULL.\n");
          exit(EXIT_FAILURE);
        }
      }

      if ((file = (struct locale_file*)malloc(
              sizeof(struct locale_file))) == NULL)
      {
        fprintf(stderr, "malloc() returned NULL.\n");
        exit(EXIT_FAILURE);
      }

      if ((string = (char*)malloc(6)) == NULL)
      {
        fprintf(stderr, "malloc() returned NULL.\n");
        exit(EXIT_FAILURE);
      }

      strncpy(string, locale_entry->d_name, 5);

      file->locale_code = string;
      file->filename = strdup(locale_entry->d_name);

      module->locale_files[module->number_of_file_structs] = file;
      module->number_of_file_structs++;
    }

    number_of_module_structs++;
  }

  if ((out = fopen(out_filename, "wb")) == NULL)
  {
    fprintf(stderr, "Could not open file \"%s\".\n", out_filename);
    exit(EXIT_FAILURE);
  }

  fprintf(out, "\n#ifndef locales_c_INCLUDED\n");
  fprintf(out, "#define locales_c_INCLUDED\n");
  fprintf(out, "\n#include <inttypes.h>\n");
  fprintf(out, "#include <stdio.h>\n");
  fprintf(out, "\n#include \"locales.h\"\n\n");

  for (module_index=0;
      module_index < number_of_module_structs;
      module_index++)
  {
    module = locale_modules[module_index];

    for (locale_index=0;
        locale_index < module->number_of_file_structs;
        locale_index++)
    {
      messages_processed = 0;

      file = module->locale_files[locale_index];

      printf("Processing \"%s\" in \"%s\".\n", file->filename, module->dirname);

      if ((string = (char*)malloc(
              strlen(module->dirname) + strlen(file->filename) + 2)) == NULL)
      {
        fprintf(stderr, "malloc() returned NULL.\n");
        exit(EXIT_FAILURE);
      }

      strcpy(string, module->dirname);
      strcat(string, "/");
      strcat(string, file->filename);

      if ((in = fopen(string, "r")) == NULL)
      {
        fprintf(stderr, "Could not open file \"%s\".\n", string);
        exit(EXIT_FAILURE);
      }

      in_char = fgetc(in);
      while (in_char != EOF)
      {
        // Found a new line.
        ungetc(in_char, in);

        fprintf(
            out,
            "static z_ucs i18n_%s_%s_message_%d[] = {\n",
            module->module_code,
            file->locale_code,
            messages_processed + 1);

        output_index = 0;

        for (;;)
        {
          input = input_char();

          if (input == Z_UCS_BACKSLASH)
          {
            input = input_char();

            if (input == Z_UCS_BACKSLASH)
            {
              output_hex_word(Z_UCS_BACKSLASH);
            }
            else if (input == 'n')
            {
              output_hex_word(Z_UCS_NEWLINE);
            }
            else if (input == (z_ucs)'{')
            {
              output_hex_word(Z_UCS_BACKSLASH);

              output_hex_word((z_ucs)'{');

              input = input_char();

              if ((input < 0x30) && (input > 0x39))
              {
                fprintf(stderr, "Variable number expected in \"%s\".\n",
                    in_filename);
                exit(EXIT_FAILURE);
              }

              output_hex_word(input);

              input = input_char();

              if (
                  (input != (z_ucs)'s')
                  &&
                  (input != (z_ucs)'d')
                  &&
                  (input != (z_ucs)'x')
                  &&
                  (input != (z_ucs)'z')
                 )
              {
                fprintf(stderr, "Invalid parameter type in \"%s\".\n",
                    in_filename);
                exit(EXIT_FAILURE);
              }

              output_hex_word(input);

              input = input_char();

              if (input != (z_ucs)'}')
              {
                fprintf(stderr, "Expected '}' in \"%s\".\n", in_filename);
                exit(EXIT_FAILURE);
              }

              output_hex_word((z_ucs)'}');
            }
            else
            {
              fprintf(stderr, "Undefined control sequence \\%c.\n",(char)input);
              exit(EXIT_FAILURE);
            }
          }
          else if (input == Z_UCS_NEWLINE)
          {
            output_hex_word(0);
            break;
          }
          else
          {
            // Here we've found some "normal" output.

            output_hex_word(input);

            if ((input & 0x80) != 0)
            {
              // In this case the UTF-8 char consists of more than a
              // single byte.
              for (;;)
              {
                input = input_char();

                if ((input & 0xc0) != 0x80)
                {
                  if ((ungetc((int)input, in)) == EOF)
                  {
                    fprintf(stderr, "Could not execute ungetc.\n");
                    exit(EXIT_FAILURE);
                  }

                  break;
                }

                output_hex_word(input);
              }
            }
          }
        }

        output_string(" };\n\n");
        messages_processed++;
        in_char = fgetc(in);
      }

      total_number_of_locales++;
      file->number_of_messages = messages_processed;

      if (fclose(in) != 0)
      {
        fprintf(stderr, "Could not close file \"%s\".\n", in_filename);
        exit(EXIT_FAILURE);
      }
    }

    if (messages_processed > maximum_number_of_messages)
      maximum_number_of_messages = messages_processed;
  }

  fprintf(out, "\nstruct locale locales[] = {\n");

  for (module_index=0;
      module_index < number_of_module_structs;
      module_index++)
  {
    module = locale_modules[module_index];

    for (locale_index=0;
        locale_index < module->number_of_file_structs;
        locale_index++)
    {
      file = module->locale_files[locale_index];

      if ( (locale_index != 0) || (module_index != 0) )
        fprintf(out, ",\n");
      fprintf(out, "  {\n");
      fprintf(out, "    \"%s\",\n", module->module_code);
      fprintf(out, "    \"%s\",\n", file->locale_code);
      fprintf(out, "    %d,\n", file->number_of_messages);
      fprintf(out, "    {\n");
      fprintf(out, "      /*@-nullassign@*/ NULL /*@+nullassign@*/");

      for (messages_processed = 0;
          messages_processed < file->number_of_messages;
          messages_processed++)
        fprintf(
            out,
            ",\n      i18n_%s_%s_message_%d",
            module->module_code,
            file->locale_code,
            messages_processed + 1);
      fprintf(out, "\n    }\n  }");
    }
  }
  fprintf(out, "\n};\n\n");

  fprintf(out, "#endif /* locales_c_INCLUDED */\n\n");

  if (fclose(out) != 0)
  {
    fprintf(stderr, "Could not close file \"%s\".\n", out_filename);
    exit(EXIT_FAILURE);
  }

  out_filename[8] = 'h';

  if ((out = fopen(out_filename, "wb")) == NULL)
  {
    fprintf(stderr, "Could not open file \"%s\".\n", out_filename);
    exit(EXIT_FAILURE);
  }

  fprintf(out, "\n#ifndef locales_h_INCLUDED\n");
  fprintf(out, "#define locales_h_INCLUDED\n\n");

  fprintf(out, "#include \"types.h\"\n\n");

  fprintf(out, "#define NUMBER_OF_LOCALES %d\n\n", total_number_of_locales);

  fprintf(out, "typedef z_ucs *i18n_message_ptr;\n\n");

  fprintf(out, "struct locale\n");
  fprintf(out, "{\n");
  fprintf(out, "  char *module_name;\n");
  fprintf(out, "  char *language_code;\n");
  fprintf(out, "  int number_of_messages;\n");
  fprintf(out, "  // FIXME: Wastes space for small locales.\n");
  fprintf(out, "  i18n_message_ptr messages[%d];\n",
      maximum_number_of_messages + 1);
  fprintf(out, "};\n");

  fprintf(out, "\n#ifndef locales_c_INCLUDED\n");
  fprintf(out, "extern struct locale locales[];\n");
  fprintf(out, "#endif /* locales_c_INCLUDED */\n\n");
  fprintf(out, "#endif /* locales_h_INCLUDED */\n\n");

  exit(EXIT_SUCCESS);
}

