#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/times.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#include <locale.h>

#include "riscos.h"
#include "module-territory.h"

void Territory::BuildTable (int code, int (*proc) (int))
{
  unsigned int *buf = (unsigned int *) ((uint32_t) pmem + (code * 32));
  int x = 0;
  for (int pos = 0; pos <= 8; pos++)
    {
      unsigned int t = 0;
      for (int y = 0; y <= 31; y++)
        {
          t |= ((*proc) (x) ? 1 : 0) << y;
          x++;
        }

      buf[pos] = t;
    }
}


void Territory::Initialise (void)
{
  // Allocate a page for private memory.
  pmem = (unsigned int *) memc->rma_heap->Alloc (0x1000);

  // Tables are stored in that page.
  BuildTable (0, iscntrl);
  BuildTable (1, isupper);
  BuildTable (2, islower);
  BuildTable (3, isalpha);
  BuildTable (4, ispunct);
  BuildTable (5, isspace);
  BuildTable (6, isdigit);
  BuildTable (7, isxdigit);

  ltrans = (unsigned char *) ((uint32_t) pmem + 0x200);
  utrans = (unsigned char *) ((uint32_t) pmem + 0x400);
  for (int x = 0; x <= 255; x++)
    {
      ltrans[x] = tolower (x);
      utrans[x] = toupper (x);
    }

  misc = (void *) ((uint32_t) pmem + 0x800);
}

bool Territory::SWI (unsigned int swinum, bool xbit)
{
  switch (swinum)
    {
    case Territory_Number: Number (); break;
    case Territory_NameToNumber: NameToNumber (); break;
    case Territory_NumberToName: NumberToName (); break;
    case Territory_ReadCurrentTimeZone: ReadCurrentTimeZone (); break;
    case Territory_ReadTimeZones: ReadTimeZones (); break;
    case Territory_ConvertTimeToUTCOrdinals: ConvertTimeToUTCOrdinals (); break;
    case Territory_ConvertTimeToOrdinals: ConvertTimeToOrdinals (); break;
    case Territory_ConvertOrdinalsToTime: ConvertOrdinalsToTime (); break;
    case Territory_CharacterPropertyTable: CharacterPropertyTable (); break;
    case Territory_LowerCaseTable: LowerCaseTable (); break;
    case Territory_UpperCaseTable: UpperCaseTable (); break;
    case Territory_Collate: Collate (); break;
    case Territory_ReadSymbols: ReadSymbols (); break;
    default:
      return false;
      break;
    }
  return true;
}

void Territory::Number (void)
{
  WRITE_REG (0, 1);
}

void Territory::NameToNumber (void)
{
  WRITE_REG (0, 1);
}

void Territory::NumberToName (void)
{
  char *addr = (char *) READ_REG (1);
  strcpy (addr, "UK");
}

void Territory::ReadCurrentTimeZone (void)
{
  struct tm t;
  time_t c = time (NULL);
  
  localtime_r (&c, &t);
  strcpy ((char *) misc, t.tm_zone);
  WRITE_REG (0, (uint32_t) misc);
  WRITE_REG (1, t.tm_gmtoff * 100);
}

void Territory::ReadTimeZones (void)
{
  struct tm t;
  time_t c = time (NULL);
  
  localtime_r (&c, &t);
  strcpy ((char *) misc, tzname[0]);
  strcpy ((char *) ((uint32_t) misc + 0x10), tzname[1]);
  WRITE_REG (0, (uint32_t) misc);
  WRITE_REG (1, (uint32_t) misc + 0x10);
  WRITE_REG (2, (t.tm_gmtoff - 3600) * 100);
  WRITE_REG (3, t.tm_gmtoff * 100);
}

void Territory::ConvertTimeToUTCOrdinals (void)
{
  struct tm *t;

  // Converts a 5-byte UTC time to UTC time ordinals.
  {
    unsigned char *addr = (unsigned char *) READ_REG (1);
    int high = addr[4];
    int low = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0];
    time_t unixtime = cvt_riscos_time (high, low);
    t = gmtime (&unixtime);
  }

  {
    unsigned int *addr = (unsigned int *) READ_REG (2);
    *addr ++ = 0;
    *addr ++ = t->tm_sec;
    *addr ++ = t->tm_min;
    *addr ++ = t->tm_hour;
    *addr ++ = t->tm_mday;
    *addr ++ = t->tm_mon + 1;
    *addr ++ = t->tm_year;
    *addr ++ = t->tm_wday;
    *addr ++ = t->tm_yday;
  }
}

void Territory::ConvertTimeToOrdinals (void)
{
  struct tm *t;

  {
    // Converts a 5-byte UTC time to UTC time ordinals.
    unsigned char *addr = (unsigned char *) READ_REG (1);
    int high = addr[4];
    int low = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0];
    time_t unixtime = cvt_riscos_time (high, low);
    t = localtime (&unixtime);
  }

  {
    unsigned int *addr = (unsigned int *) READ_REG (2);
    *addr ++ = 0;
    *addr ++ = t->tm_sec;
    *addr ++ = t->tm_min;
    *addr ++ = t->tm_hour;
    *addr ++ = t->tm_mday;
    *addr ++ = t->tm_mon + 1;
    *addr ++ = t->tm_year;
    *addr ++ = t->tm_wday;
    *addr ++ = t->tm_yday;
  }
}

void Territory::ConvertOrdinalsToTime (void)
{
  time_t unixtime;
  unsigned int high, low;
  struct tm t;
  target_addr_t addr = READ_REG (2);

  // Converts local time ordinals to a 5 byte UTC time.
  
  t.tm_sec = MEM_READ_WORD (addr + 4);
  t.tm_min = MEM_READ_WORD (addr + 8);
  t.tm_hour = MEM_READ_WORD (addr + 12);
  t.tm_mday = MEM_READ_WORD (addr + 16);
  t.tm_mon = MEM_READ_WORD (addr + 20) - 1;
  t.tm_year = MEM_READ_WORD (addr + 24) - 1900;
  t.tm_wday = MEM_READ_WORD (addr + 28);
  t.tm_yday = MEM_READ_WORD (addr + 32);
  unixtime = mktime (&t);
  cvt_unix_time (unixtime, &high, &low);

  addr = READ_REG (1);
  MEM_WRITE_BYTE (addr + 4, high & 0xff);
  MEM_WRITE_BYTE (addr + 3, (low & 0xff000000) >> 24);
  MEM_WRITE_BYTE (addr + 2, (low & 0x00ff0000) >> 16);
  MEM_WRITE_BYTE (addr + 1, (low & 0x0000ff00) >> 8);
  MEM_WRITE_BYTE (addr + 0, low & 0xff);
}

void Territory::CharacterPropertyTable (void)
{
  int codetable = READ_REG (1);
  unsigned int *buf = (unsigned int *) ((uint32_t) pmem + (codetable * 32));
  // Code tables have already been initialised, so we just
  // need to point to them.
  WRITE_REG (0, (uint32_t) buf);
}

void Territory::LowerCaseTable (void)
{
  WRITE_REG (0, (uint32_t) ltrans);
}

void Territory::UpperCaseTable (void)
{
  WRITE_REG (0, (uint32_t) utrans);
}

void Territory::ReadSymbols (void) // 0x4305E PRM 3-837
{
  int tnum = READ_REG (0);
  int reason_code = READ_REG (1);
  struct lconv *lc = localeconv ();

  switch (reason_code)
    {
    case 0:
      WRITE_REG (0, (uint32_t) lc->decimal_point);
      break;
    case 1:
      WRITE_REG (0, (uint32_t) lc->thousands_sep);
      break;
    case 2:
      WRITE_REG (0, (uint32_t) lc->grouping);
      break;
    case 3:
      WRITE_REG (0, (uint32_t) lc->int_curr_symbol);
      break;
    case 4:
      WRITE_REG (0, (uint32_t) lc->currency_symbol);
      break;
    case 5:
      WRITE_REG (0, (uint32_t) lc->mon_decimal_point);
      break;
    case 6:
      WRITE_REG (0, (uint32_t) lc->mon_thousands_sep);
      break;
    case 7:
      WRITE_REG (0, (uint32_t) lc->mon_grouping);
      break;
    case 8:
      WRITE_REG (0, (uint32_t) lc->positive_sign);
      break;
    case 9:
      WRITE_REG (0, (uint32_t) lc->negative_sign);
      break;
    case 10:
      WRITE_REG (0, (uint32_t) lc->int_frac_digits);
      break;
    case 11:
      WRITE_REG (0, (uint32_t) lc->frac_digits);
      break;
    case 12:
      WRITE_REG (0, (uint32_t) lc->p_cs_precedes);
      break;
    case 13:
      WRITE_REG (0, (uint32_t) lc->p_sep_by_space);
      break;
    case 14:
      WRITE_REG (0, (uint32_t) lc->n_cs_precedes);
      break;
    case 15:
      WRITE_REG (0, (uint32_t) lc->n_sep_by_space);
      break;
    case 16:
      WRITE_REG (0, (uint32_t) lc->p_sign_posn);
      break;
    case 17:
      WRITE_REG (0, (uint32_t) lc->n_sign_posn);
      break;
    default:
      printf ("Territory::ReadSymbols unknown reason %d\n", reason_code);
      break;
    }
}

void Territory::Collate (void)
{
  unsigned int r0;
  char *buf1 = (char *) READ_REG (1);
  char *buf2 = (char *) READ_REG (2);
  r0 = strcoll (buf1, buf2);
  WRITE_REG (0, r0);
  CLEAR_N ();
  CLEAR_Z ();
  CLEAR_C ();
  if (r0 < 0)
    SET_N ();
  else if (r0 == 0)
    SET_Z ();
  else if (r0 > 0)
    SET_C ();
}
