/* setlocal.c (emx+gcc) -- Copyright (c) 1994-1996 by Eberhard Mattes */

#include <stdlib.h>
#include <locale.h>
#include <string.h>
#include <emx/locale.h>

static char *lcn_collate  = "C";
static char *lcn_ctype    = "C";
static char *lcn_monetary = "C";
static char *lcn_numeric  = "C";
static char *lcn_time     = "C";

/* Store strings for strftime() here.  We cannot directly used the
   strings provided in the locales table entry because we have to
   translate accented character for the current code page. */

static char time_buf[320];
static int time_buf_ptr;

/* Table are used for translating accented characters (specified in
   TeX notation) to the current code page.  This is an entry of such a
   table.  `inp' is the base character and `out' is the translated
   character.  The last entry of a table has inp == 0. */

struct accent
{
  unsigned char inp;
  unsigned char out;
};

/* This structure describes a code page.  It holds pointers to the
   accent translation tables. */

struct codepage
{
  struct accent *acute;
  struct accent *grave;
  struct accent *circumflex;
  struct accent *dieresis;
};


/* This table translates acute accents to code page 850. */

static struct accent cp850_acute[] =
{
  {'e', 130},
  {0, 0}
};

/* This table translates grave accents to code page 850. */

static struct accent cp850_grave[] =
{
  {'a', 133},
  {'i', 141},
  {0, 0}
};

/* This table translates circumflex accents to code page 850. */

static struct accent cp850_circumflex[] =
{
  {'u', 150},
  {0, 0}
};

/* This table translates umlaut accents to code page 850. */

static struct accent cp850_dieresis[] =
{
  {'a', 132},
  {0, 0}
};


/* This is the description of code page 850. */

static struct codepage cp_850 =
{
  cp850_acute,
  cp850_grave,
  cp850_circumflex,
  cp850_dieresis
};


/* The current code page. */

static struct codepage *cur_cp = &cp_850;


/* Add the character C to the end of `time_buf' and update
   `time_buf_ptr'.  If there is no space left, nothing is done. */

#define TIME_BUF_CHAR(C) \
  (time_buf_ptr < sizeof (time_buf) \
    ? (void)(time_buf[time_buf_ptr++] = (C)) : (void)0)


/* Translate an accented character and put the result into `time_buf'.
   P is the translation table, C is the base character.  Return true
   if the translation is successful. */

static int time_buf_accent (const struct accent *p, unsigned char c)
{
  if (p == NULL)
    return 0;
  while (p->inp != 0)
    {
      if (p->inp == c)
        {
          TIME_BUF_CHAR (p->out);
          return 1;
        }
      ++p;
    }
  return 0;
}


/* Translate a string according to the current code page and put it
   into `time_buf'.  Return a pointer to the start of the string in
   `time_buf'.  If there is not enough space left in `time_buf',
   return a pointer to the string "?". */

static char *set_time_buf (const char *s)
{
  unsigned char c;
  char *result;

  result = time_buf + time_buf_ptr;
  while ((c = *s++) != 0)
    if (c == '\\')
      switch ((c = *s))
        {
        case 0:
          break;
        case '\\':
          ++s;
          TIME_BUF_CHAR ('\\');
          break;
        case '\'':
          ++s;
          if (*s != 0)
            {
              c = *s++;
              if (!time_buf_accent (cur_cp->acute, c))
                TIME_BUF_CHAR (c);
            }
          break;
        case '`':
          ++s;
          if (*s != 0)
            {
              c = *s++;
              if (!time_buf_accent (cur_cp->grave, c))
                TIME_BUF_CHAR (c);
            }
          break;
        case '^':
          ++s;
          if (*s != 0)
            {
              c = *s++;
              if (!time_buf_accent (cur_cp->circumflex, c))
                TIME_BUF_CHAR (c);
            }
          break;
        case '"':
          ++s;
          if (*s != 0)
            {
              c = *s++;
              if (!time_buf_accent (cur_cp->dieresis, c))
                {
                  TIME_BUF_CHAR (c);
                  if (c == 'a' || c == 'o' || c == 'u'
                      || c == 'A' || c == 'E' || c == 'U')
                    TIME_BUF_CHAR ('e');
                }
            }
          break;
        default:
          ++s;
          TIME_BUF_CHAR ('?');
          break;
          }
    else
      TIME_BUF_CHAR (c);
  if (time_buf_ptr < sizeof (time_buf))
    time_buf[time_buf_ptr++] = 0;
  else
    result = "?";
  return result;
}


/* Translate COUNT strings according to the current code page and put
   them into `time_buf'.  Put pointers to the strings in `time_buf'
   into the array pointed to by DST.  SRC points to the source
   strings, separated by null characters. */

static void time_buf_array (char **dst, const char *src, int count)
{
  int i;

  for (i = 0; i < count; ++i)
    {
      dst[i] = set_time_buf (src);
      src = strchr (src, 0) + 1;
    }
}


/* Find LOCALE in the table of locales.  If no matching entry is
   found, return NULL.  Otherwise return a pointer to the table
   entry. */

static const struct lc_data *findloc (const char *locale)
{
  const struct lc_data *dp;

  for (dp = _lc_table; dp->name != NULL; ++dp)
    if (_stricmp (dp->name, locale) == 0)
      return dp;
  return NULL;
}


/* Update the data associated with category CATEGORY for locale
   LOCALE.  LOCALE specifies a single locale.  Return the name of the
   locale or NULL. */

static char *setloc1 (int category, const char *locale)
{
  const struct lc_data *dp;

  dp = findloc (locale);
  if (dp == NULL)
    return NULL;
  if (category == LC_COLLATE || category == LC_ALL)
    {
      lcn_collate = dp->name;
    }
  if (category == LC_CTYPE || category == LC_ALL)
    {
      lcn_ctype = dp->name;
      /* Set _mb_cur_max and _cur_mbyte. */
    }
  if (category == LC_MONETARY || category == LC_ALL)
    {
      lcn_monetary = dp->name;
      _cur_lconv.int_curr_symbol   = dp->monetary->int_curr_symbol;
      _cur_lconv.currency_symbol   = dp->monetary->currency_symbol;
      _cur_lconv.mon_decimal_point = dp->monetary->mon_decimal_point;
      _cur_lconv.mon_thousands_sep = dp->monetary->mon_thousands_sep;
      _cur_lconv.mon_grouping      = dp->monetary->mon_grouping;
      _cur_lconv.positive_sign     = dp->monetary->positive_sign;
      _cur_lconv.negative_sign     = dp->monetary->negative_sign;
      _cur_lconv.int_frac_digits   = dp->monetary->int_frac_digits;
      _cur_lconv.frac_digits       = dp->monetary->frac_digits;
      _cur_lconv.p_cs_precedes     = dp->monetary->p_cs_precedes;
      _cur_lconv.p_sep_by_space    = dp->monetary->p_sep_by_space;
      _cur_lconv.n_cs_precedes     = dp->monetary->n_cs_precedes;
      _cur_lconv.n_sep_by_space    = dp->monetary->n_sep_by_space;
      _cur_lconv.p_sign_posn       = dp->monetary->p_sign_posn;
      _cur_lconv.n_sign_posn       = dp->monetary->n_sign_posn;
    }
  if (category == LC_NUMERIC || category == LC_ALL)
    {
      lcn_numeric = dp->name;
      _cur_lconv.decimal_point = dp->numeric->decimal_point;
      _cur_lconv.thousands_sep = dp->numeric->thousands_sep;
      _cur_lconv.grouping      = dp->numeric->grouping;
    }
  if (category == LC_TIME || category == LC_ALL)
    {
      lcn_time = dp->name;
      time_buf_ptr = 0;
      time_buf_array (_cur_lcf_time.months1, dp->time->months1, 12);
      time_buf_array (_cur_lcf_time.months2, dp->time->months2, 12);
      time_buf_array (_cur_lcf_time.wdays1, dp->time->wdays1, 7);
      time_buf_array (_cur_lcf_time.wdays2, dp->time->wdays2, 7);
      _cur_lcf_time.date_time_fmt = set_time_buf (dp->time->date_time_fmt);
      _cur_lcf_time.date_fmt = set_time_buf (dp->time->date_fmt);
      _cur_lcf_time.time_fmt = set_time_buf (dp->time->time_fmt);
      _cur_lcf_time.am = set_time_buf (dp->time->am);
      _cur_lcf_time.pm = set_time_buf (dp->time->pm);

    }
  return dp->name;
}


char *setlocale (int category, const char *locale)
{
  static char buf[64];
  char *pn;
  const char *s1, *s2;
  int store, i, len;
  static const int cat[5] = 
    {LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME};

  /* Check CATEGORY (fail if it's invalid) and set `pn' to the current
     locale associated with CATEGORY (or to NULL if CATEGORY is
     LC_ALL). */

  switch (category)
    {
    case LC_ALL:
      pn = NULL;
      break;
    case LC_COLLATE:
      pn = lcn_collate;
      break;
    case LC_CTYPE:
      pn = lcn_ctype;
      break;
    case LC_MONETARY:
      pn = lcn_monetary;
      break;
    case LC_NUMERIC:
      pn = lcn_numeric;
      break;
    case LC_TIME:
      pn = lcn_time;
      break;
    default:
      return NULL;
    }

  /* If LOCALE is NULL, return the current locale. */

  if (locale == NULL)
    {
      /* `pn' is non-NULL for all categories but LC_ALL. */

      if (pn != NULL)
        return pn;

      /* If the locales for all the categories are identical, return
         that locale. */

      if (_stricmp (lcn_collate, lcn_ctype) == 0
          && _stricmp (lcn_collate, lcn_monetary) == 0
          && _stricmp (lcn_collate, lcn_numeric) == 0
          && _stricmp (lcn_collate, lcn_time) == 0)
        return lcn_collate;

      /* Create a comma-separated list of locales for all the
         categories. */

      strcpy (buf, lcn_collate); strcat (buf, ",");
      strcat (buf, lcn_ctype); strcat (buf, ",");
      strcat (buf, lcn_monetary); strcat (buf, ",");
      strcat (buf, lcn_numeric); strcat (buf, ",");
      strcat (buf, lcn_time);
      return buf;
    }

  /* The default locale is taken from the LANG environment variable. */

  if (*locale == 0)
    {
      locale = getenv ("LANG");
      if (locale == NULL || *locale == 0)
        locale = "C";
    }

  /* LOCALE is treated specially if it contains commas and CATEGORY is
     LC_ALL.  Otherwise, it simply contains the name of the locale. */

  if (category != LC_ALL || strchr (locale, ',') == NULL)
    return setloc1 (category, locale);

  /* We have to copy LOCALE to `buf' for keeping the return value in
     case the caller changes LOCALE. */

  if (strlen (locale) >= sizeof (buf))
    return NULL;

  /* Make two passes: The first pass checks whether LOCALE is
     correctly formatted and contains valid locales, the second pass
     sets the data for all the categories. */

  for (store = 0; store < 2; ++store)
    {

      /* Loop over 5 comma-separated locales in LOCALE. */

      s1 = locale;
      for (i = 0; i < 5; ++i)
        {
          s2 = strchr (s1, ',');
          if (i == 4)
            {
              if (s2 != NULL)
                return NULL;
              s2 = strchr (s1, 0);
            }
          else
            {
              if (s2 == NULL)
                return NULL;
            }
          len = s2 - s1;
          memcpy (buf, s1, len);
          buf[len] = 0;
          if (store)
            setloc1 (cat[i], buf);
          else if (findloc (buf) == NULL)
            return NULL;
          s1 = s2 + 1;
        }
    }
  strcpy (buf, locale);
  return buf;
}
