
#ifndef i18n_c_INCLUDED
#define i18n_c_INCLUDED

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

#include "i18n.h"
#include "tracelog.h"
#include "fizmo.h"
#include "config.h"
#include "locales.h"
#include "streams.h"
#include "misc.h"
#include "types.h"
#include "z_ucs.h"
#include "zpu.h"
#include "splint.h"

#define LATIN1_TO_Z_UCS_BUFFER_SIZE 64

struct locale *active_locale = NULL;

/*@null@*/ //struct locale *active_locale = NULL;
//z_ucs *z_ucs_strbuffer = NULL;
//int z_ucs_strbuffer_size = 0;


/*
int i18n_locale_exists(char *locale_name, char *module_name)
{
  int i;

  if (locale_name == NULL)
    return -1;

  TRACE_LOG("Looking for locale '%s'.\n", locale_name);

  for (i=0; i<NUMBER_OF_LOCALES; i++)
    if (
        (strcmp(locales[i].language_code, locale_name) == 0)
        &&
        (strcmp(locales[i].module_name, module_name) == 0)
       )
      return i;

  return -1;
}


void i18n_set_locale(char *locale_name, char *module_name)
{
  int index;

  if (locale_name == NULL)
  {
    locale_name = DEFAULT_LOCALE;
  }

  if ((index = i18n_locale_exists(locale_name, module_name)) == -1)
  {
    fprintf(
        stderr,
        "\nRequested locale \"%s\" could not be found.\n\n",
        locale_name);

    abort_interpreter(-1, NULL);
  }

  active_locale = &(locales[index]);
}
*/


static int i18n_send_output(
    /*@in@*/ /*@dependent@*/ /*@null@*/ z_ucs *z_ucs_data,
    int output_mode,
    z_ucs **string_target)
{
  if (z_ucs_data == NULL)
    return 0;

  if (output_mode == i18n_OUTPUT_MODE_STREAMS)
  {
    return streams_z_ucs_output(z_ucs_data);
  }
  else if (output_mode == i18n_OUTPUT_MODE_STRING)
  {
    (void)z_ucs_cpy(*string_target, z_ucs_data);
    *string_target += z_ucs_len(z_ucs_data);
    return 0;
  }
  else if (output_mode == i18n_OUTPUT_MODE_DEV_NULL)
  {
    return 0;
  }
  else
  {
    return -1;
  }
}


// "string_code" is one of the codes defined in "utf8.h".
// "ap" is the va_list initialized in the various i18n-methods.
// "output_mode" is either "i18n_OUTPUT_MODE_DEV_NULL" for no output
//  at all (useful for string length measuring), "i18n_OUTPUT_MODE_STREAMS"
//  for sending output to "streams_utf8_output" and "i18n_OUTPUT_MODE_STRING"
//  to write the output to a string.
static long i18n_translate_from_va_list(int string_code, va_list ap,
    int output_mode, /*@null@*/ z_ucs *string_target)
{
  z_ucs *index;
  char parameter_types[11]; // May each contain 's', 'z' or 'd'. Using 11
                            // instead of ten so that a null byte may be
                            // placed after a char to print the error
                            // message as string.
  char *string_parameters[10]; // pointers to the parameters.
  z_ucs *z_ucs_string_parameters[10];
  char formatted_parameters[10][MAXIMUM_FORMATTED_PARAMTER_LENGTH + 1];
  z_ucs *start;
  uint8_t match_stage;
  int i,k;
  int n;
  z_ucs buf;
  size_t length;
  z_ucs *start_index;
  char index_char;
  z_ucs z_ucs_buffer[LATIN1_TO_Z_UCS_BUFFER_SIZE];
  char *ptr;

  if (active_locale == NULL)
    return -1;

  index = active_locale->messages[string_code];

  if (index == NULL)
    return -1;

  TRACE_LOG("Translating string code %d.\n", string_code);

  n = 0;
  while ((index = z_ucs_chr(index, (z_ucs)'\\')) != NULL)
  {
    index++;
    start_index = index;
    index_char = z_ucs_to_latin1(*index);

    if (index_char == '{')
    {
      TRACE_LOG("'{' found.\n");
      index++;
      index_char = z_ucs_to_latin1(*index);
    }
    else
    {
      index = start_index;
      continue;
    }

    if ((index_char >= '0') && (index_char <= '9'))
    {
      TRACE_LOG("'[0-9]' found.\n");
      index++;
      index_char = z_ucs_to_latin1(*index);
    }
    else
    {
      index = start_index;
      continue;
    }

    if (
        (index_char == 's')
        ||
        (index_char == 'z')
        ||
        (index_char == 'd')
        ||
        (index_char == 'x')
       )
    {
      TRACE_LOG("'[szdx]' found.\n");
      parameter_types[n] = index_char;
      index++;
      index_char = z_ucs_to_latin1(*index);
    }
    else
    {
      index = start_index;
      continue;
    }

    if (index_char == '}')
    {
      TRACE_LOG("'}' found.\n");
      index++;
      index_char = z_ucs_to_latin1(*index);
      n++;
    }
    else
    {
      index = start_index;
      continue;
    }
  }

  TRACE_LOG("Found %d parameter codes.\n", n);

  if (n == 0)
  {
    TRACE_LOG("No parameter.\n");
    // In case we don't have a single parameter, we can just print
    // everything right away and quit.

    if (i18n_send_output(
          active_locale->messages[string_code],
          output_mode,
          (string_target != NULL ? &string_target : NULL)) != 0)
      return -1;
    else
      return z_ucs_len(active_locale->messages[string_code]);
  }

  length = 0;

  for (i=0; i<n; i++)
  {
    // parameter_types[0-n] are always defined, thus using "usedef" is okay.

    if (/*@-usedef@*/ parameter_types[i] == 's' /*@+usedef@*/)
    {
      string_parameters[i] = va_arg(ap, char*);
      TRACE_LOG("p#%d: %s\n", i, string_parameters[i]);
      length += strlen(string_parameters[i]);
    }
    else if (/*@-usedef@*/ parameter_types[i] == 'z' /*@+usedef@*/)
    {
      z_ucs_string_parameters[i] = va_arg(ap, z_ucs*);
      length += z_ucs_len(z_ucs_string_parameters[i]);
    }
    else if (/*@-usedef@*/ parameter_types[i] == 'd' /*@+usedef@*/)
    {
      (void)snprintf(formatted_parameters[i],
          MAXIMUM_FORMATTED_PARAMTER_LENGTH,
          "%ld",
          (long)va_arg(ap, long));
      TRACE_LOG("p#%d: %s\n", i, formatted_parameters[i]);
      length += strlen(formatted_parameters[i]);
    }
    else if (/*@-usedef@*/ parameter_types[i] == 'x' /*@+usedef@*/)
    {
      (void)snprintf(formatted_parameters[i],
          MAXIMUM_FORMATTED_PARAMTER_LENGTH,
          "%lx",
          (unsigned long)va_arg(ap, long));
      length += strlen(formatted_parameters[i]);
    }
    else
    {
      TRACE_LOG("Invalid parameter type: %c.\n", parameter_types[i]);
      parameter_types[i+1] = '\0';
      i18n_translate_and_exit(
          i18n_fizmo_INVALID_PARAMETER_TYPE_P0S,
          -1,
          parameter_types+i);
    }
  }

  TRACE_LOG("Length: %zd.\n", length);

  start = active_locale->messages[string_code];
  i = 0;
  match_stage = 0;

  while (start[i] != 0)
  {
    if (match_stage == 1)
    {
      // We've already found a leading backslash.

      if ((start[i] == Z_UCS_BACKSLASH) && (match_stage == 1))
      {
        // Found another backslash, so output.
        (void)latin1_string_to_z_ucs(
            z_ucs_buffer,
            "\\",
            LATIN1_TO_Z_UCS_BUFFER_SIZE);

        if (i18n_send_output(z_ucs_buffer,
              output_mode,
              (string_target != NULL ? &string_target : NULL))
            != 0)
          i18n_translate_and_exit(
              i18n_fizmo_FUNCTION_CALL_P0S_ABORTED_DUE_TO_ERROR,
              -1,
              "i18n_send_output");

        match_stage = 0;
        length++;
        i++;
      }

      else if (start[i] == (z_ucs)'{')
      {
        // Here we've found a parameter. First, output everything up to
        // the parameter excluding "\{". In order to achive that, we'll
        // replace the first byte that shouldn't be printed with the
        // string-terminating 0 and restore it after that for the next
        // use of this message.
        buf = start[i-1];
        start[i-1] = 0;
        if (i18n_send_output(
              start,
              output_mode,
              (string_target != NULL ? &string_target : NULL))
            != 0)
          i18n_translate_and_exit(
              i18n_fizmo_FUNCTION_CALL_P0S_ABORTED_DUE_TO_ERROR,
              -1,
              "i18n_send_output");

        length += z_ucs_len(start);
        start[i-1] = buf;

        // After that, output parameter (subtract 0x30 == ASCII:'0')
        k = (int)(start[i+1] - 0x30);

        if (/*@-usedef@*/ parameter_types[k] == 's' /*@+usedef@*/)
        {
          ptr = /*@-usedef@*/ string_parameters[k] /*@+usedef@*/;
          TRACE_LOG("%s\n", ptr);
          while (ptr != NULL)
          {
            ptr = latin1_string_to_z_ucs(
                z_ucs_buffer,
                ptr,
                LATIN1_TO_Z_UCS_BUFFER_SIZE);

            if (i18n_send_output(
                  z_ucs_buffer,
                  output_mode,
                  (string_target != NULL ? &string_target : NULL))
                !=0 )
              i18n_translate_and_exit(
                  i18n_fizmo_FUNCTION_CALL_P0S_ABORTED_DUE_TO_ERROR,
                  -1,
                  "i18n_send_output");
          }
        }
        else if (parameter_types[k] == 'z')
        {
          if (i18n_send_output(
                /*@-usedef@*/ z_ucs_string_parameters[k]/*@-usedef@*/,
                output_mode,
                (string_target != NULL ? &string_target : NULL))
              != 0)
            i18n_translate_and_exit(
                i18n_fizmo_FUNCTION_CALL_P0S_ABORTED_DUE_TO_ERROR,
                -1,
                "i18n_send_output");
        }
        else
        {
          ptr = formatted_parameters[k];
          while (ptr != NULL)
          {
            ptr = latin1_string_to_z_ucs(
                z_ucs_buffer,
                /*@-compdef@*/ ptr /*@+compdef@*/,
                LATIN1_TO_Z_UCS_BUFFER_SIZE);

            if (i18n_send_output(
                  z_ucs_buffer,
                  output_mode,
                  (string_target != NULL ? &string_target : NULL))
                != 0)
              i18n_translate_and_exit(
                  i18n_fizmo_FUNCTION_CALL_P0S_ABORTED_DUE_TO_ERROR,
                  -1,
                  "i18n_send_output");
          }
        }

        start += i + 4;
        i = 0;
        match_stage = 0;
      }
      else
      {
        i18n_translate_and_exit(
            i18n_fizmo_INVALID_BACKSLASH_SEQUENCE_IN_LOCALIZATION_DATA,
            -1);
      }
    }
    else
    {
      if ((start[i] == Z_UCS_BACKSLASH) && (match_stage == 0))
      {
        // Found leading backslash;
        match_stage = 1;
        i++;
      }
      else
      {
        // Found nothing, next char (non memchar since operating on z_ucs)
        i++;
      }
    }
  }

  if (i != 0)
  {
    if (i18n_send_output(
          start,
          output_mode,
          (string_target != NULL ? &string_target : NULL))
        != 0)
      i18n_translate_and_exit(
          i18n_fizmo_FUNCTION_CALL_P0S_ABORTED_DUE_TO_ERROR,
          -1,
          "i18n_send_output");
  }

  length += z_ucs_len(start);

  TRACE_LOG("Final length:%zd.\n", length);

  return length;
}


/*@noreturn@*/ void i18n_translate_and_exit(
    int string_code,
    int exit_code,
    ...)
{
  va_list ap;
  size_t message_length;
  z_ucs *error_message;

  TRACE_LOG("Exiting with message.\n");

  va_start(ap, exit_code);
  message_length = i18n_translate_from_va_list(
      string_code,
      ap,
      i18n_OUTPUT_MODE_DEV_NULL,
      NULL);
  va_end(ap);

  TRACE_LOG("Message length: %zu.\n", message_length);

  error_message = (z_ucs*)fizmo_malloc((message_length+3) * sizeof(z_ucs));

  TRACE_LOG("Error message at: %p.\n",
      /*@-compdef@*/ error_message/*@+compdef@*/ );

  va_start(ap, exit_code);
  // Translate message into "error_message":
  if (i18n_translate_from_va_list(
        string_code,
        ap,
        i18n_OUTPUT_MODE_STRING,
        /*@-compdef@*/ error_message /*@+compdef@*/)
      < 0)
  {
    TRACE_LOG("Code<0\n");
    free(error_message);
    abort_interpreter(exit_code, NULL);
  }
  va_end(ap);

  TRACE_LOG("Exit message: \"");
  // error_message is now defined:
  TRACE_LOG_Z_UCS(/*@-compdef@*/ error_message /*@+compdef@*/ );
  TRACE_LOG("\".\n");

  error_message[message_length] = Z_UCS_NEWLINE;
  error_message[message_length+1] = 0;

  abort_interpreter(exit_code, error_message);
}


size_t _i18n_va_translate(int string_code, va_list ap)
{
  return i18n_translate_from_va_list(
      string_code,
      ap,
      i18n_OUTPUT_MODE_STREAMS,
      NULL);
}


size_t i18n_translate(int string_code, ...)
{
  va_list ap;
  size_t result;
  
  va_start(ap, string_code);
  result = _i18n_va_translate(string_code, ap); 
  va_end(ap);

  return result;
}


size_t i18n_message_length(int string_code, ...)
{
  va_list ap;
  size_t result;

  va_start(ap, string_code);

  result
    = i18n_translate_from_va_list(
        string_code,
        ap,
        i18n_OUTPUT_MODE_DEV_NULL,
        NULL);

  va_end(ap);

  return result;
}


/*@null@*/ z_ucs *i18n_translate_to_string(int string_code, ...)
{
  va_list ap;
  size_t message_length;
  z_ucs *result;

  va_start(ap, string_code);
  if ((message_length = i18n_translate_from_va_list(
        string_code,
        ap,
        i18n_OUTPUT_MODE_DEV_NULL,
        NULL)) < 1)
    return NULL;
  va_end(ap);

  result = (z_ucs*)fizmo_malloc((message_length+1) * sizeof(z_ucs));

  va_start(ap, string_code);
  // The "i18n_translate_from_va_list"-call defines "result".
  if (i18n_translate_from_va_list(
        string_code,
        ap,
        i18n_OUTPUT_MODE_STRING,
        /*@-compdef@*/ result /*@+compdef@*/ ) == -1)
  {
    free(result);
    return NULL;
  }
  va_end(ap);

  return /*@-compdef@*/ result /*@+compdef@*/;
}


#ifdef STRICT_Z
void i18n_translate_warning(int string_code, char *opcode_name, ...)
{
  va_list ap;

  i18n_translate(
      i18n_fizmo_WARNING_FOR_P0S_AT_P0X,
      opcode_name,
      (unsigned int)(current_instruction_location - z_mem));
  streams_latin1_output(" ");
  va_start(ap, opcode_name);
  i18n_translate_from_va_list(string_code, ap, true, NULL);
  streams_latin1_output("\n");
  va_end(ap);
}
#endif // STRICT_Z

#endif /* i18n_c_INCLUDED */

