/* Copyright (C) 1991, 1992, 1993 Free Software Foundation, Inc.
This file is part of the GNU C Library.

The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.

The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with the GNU C Library; see the file COPYING.LIB.  If
not, write to the Free Software Foundation, Inc., 675 Mass Ave,
Cambridge, MA 02139, USA.  */

#include <ansidecl.h>
#include <localeinfo.h>
#include <errno.h>
#include <locale.h>
#include <string.h>
#include <stdlib.h>

#if defined(__linux__) && defined(USE_ISO_8859_1)
#include <ctype.h>
#include <fcntl.h>
#include <unistd.h>
#include <paths.h>

extern CONST unsigned short int __ctype_b_C[];
extern CONST unsigned char __ctype_tolower_C[];
extern CONST unsigned char __ctype_toupper_C[];
extern CONST struct ctype_info __ctype_C;

extern CONST struct collate_info __collate_C;
extern CONST struct monetary_info __monetary_C;
extern CONST struct numeric_info __numeric_C;
extern CONST struct response_info __response_C;
extern CONST struct time_info __time_C;

extern CONST unsigned short int __ctype_b_ISO_8859_1[];
extern CONST unsigned char __ctype_tolower_ISO_8859_1[];
extern CONST unsigned char __ctype_toupper_ISO_8859_1[];
extern CONST struct ctype_info __ctype_ISO_8859_1;

static int loc_open __P ((CONST unsigned char *, CONST unsigned char *));
static int guard_check __P ((CONST int, CONST unsigned char *));
static int loc_rdline __P ((CONST int, unsigned char *, CONST int, unsigned char **[]));

static int loc_collate __P ((CONST int, CONST unsigned char *));
static int loc_ctype __P ((CONST int, CONST unsigned char *));
static int loc_monetary __P ((CONST int, CONST unsigned char *));
static int loc_numeric __P ((CONST int, CONST unsigned char *));
static int loc_time __P ((CONST int, CONST unsigned char *));
static int loc_response __P ((CONST int, CONST unsigned char *));

#endif /* __linux__ && USE_ISO_8859_1 */


/* Switch to the locale called NAME in CATEGORY.
   Return a string describing the locale.  This string can
   be used as the NAME argument in a later call.
   If NAME is NULL, don't switch locales, but return the current one.
   If NAME is "", switch to a locale based on the environment variables,
   as per POSIX.  Return NULL on error.  */
char *
DEFUN (setlocale, (category, name), int category AND CONST char *name)
{
#if defined(__linux__) && defined(USE_ISO_8859_1)
  /* Don't ask me why I did this. H.J. */

  /* Fixed up setlocale() by Mitch (m.dsouza@mrc-apu.cam.ac.uk) */
  /* Nickolay Saukh <nms@ussr.EU.net> was here */

  static struct message_struct
    {
      int category;
      CONST char *name;
      CONST char *locale;
      char *saved_locale;
    }
  ms[] =
  {
    {
      LC_COLLATE, "LC_COLLATE", "C"
    }
    ,
    {
      LC_CTYPE, "LC_CTYPE", "C"
    }
    ,
    {
      LC_MONETARY, "LC_MONETARY", "C"
    }
    ,
    {
      LC_NUMERIC, "LC_NUMERIC", "C"
    }
    ,
    {
      LC_TIME, "LC_TIME", "C"
    }
    ,
    {
      LC_RESPONSE, "LC_RESPONSE", "C"
    }
    ,
    {
      LC_MESSAGES, "LC_MESSAGES", "C"
    }
    ,
/*
 * New categories go in between here.
 */
    {
      LC_ALL, "LC_ALL", "C"
    }
  };

  int i;
  char *ptr;

  if (name == NULL)		/* We just asking for current settings */
    {
      for (i = 0; i < sizeof (ms) / sizeof (struct message_struct); i++)
	{
	  if (category == ms[i].category)
	    return (char *) ms[i].locale;
	}
      errno = EINVAL;
      return NULL;		/* Not a valid category */
    }

  /*
   * What the category asked (excluding LC_ALL)?
   */
  if (category != LC_ALL)
    {
      for (i = 0; i < sizeof (ms) / sizeof (struct message_struct) - 1; i++)
	{
	  if (category != ms[i].category)
	    continue;

	  /* If "" is given as the locale then we check environment vars */
	  if (*name == '\0')
	    {
	      if ((ptr = getenv (ms[i].name)) == NULL &&
		  (ptr = getenv ("LANG")) == NULL)
		{
		  errno = ENOENT;
		  return NULL;	/* What the locale you asked? */
		}
	    }
	  else
	    ptr = (char *) name;

	  if (!strcmp (ptr, "POSIX") || !strcmp (ptr, ms[i].locale))
	    return (char *) ms[i].locale;

	  switch (category)
	    {
	      int fd;

	    case LC_COLLATE:
	      if (!strcmp (ptr, "C"))
		{
		  _collate_info = &__collate_C;
		  ms[i].locale = "C";
		  return (char *) ms[i].locale;
		}
	      else if ((fd = loc_open (ptr, ms[i].name)) >= 0 &&
		       loc_collate (fd, ptr))
		{
		  ms[i].locale = ptr;
		  return (char *) ms[i].locale;
		}
	      errno = ENOENT;
	      return NULL;	/* Unknown locale */

	    case LC_CTYPE:
	      if (!strcmp (ptr, "C"))
		{
		  __ctype_b = __ctype_b_C + 1;
		  __ctype_tolower = __ctype_tolower_C + 1;
		  __ctype_toupper = __ctype_toupper_C + 1;
		  _ctype_info = &__ctype_C;
		  ms[i].locale = "C";
		  return (char *) ms[i].locale;
		}
	      else if (!strcmp (ptr, "ISO-8859-1"))
		{
		  __ctype_b = __ctype_b_ISO_8859_1 + 1;
		  __ctype_tolower = __ctype_tolower_ISO_8859_1 + 1;
		  __ctype_toupper = __ctype_toupper_ISO_8859_1 + 1;
		  _ctype_info = &__ctype_ISO_8859_1;
		  ms[i].locale = "ISO-8859-1";
		  return (char *) ms[i].locale;
		}
	      else if ((fd = loc_open (ptr, ms[i].name)) >= 0 &&
		       loc_ctype (fd, ptr))
		{
		  ms[i].locale = ptr;
		  return (char *) ms[i].locale;
		}
	      errno = ENOENT;
	      return NULL;	/* Unknown locale */

	    case LC_MONETARY:
	      if (!strcmp (ptr, "C"))
		{
		  _monetary_info = &__monetary_C;
		  ms[i].locale = "C";
		  return (char *) ms[i].locale;
		}
	      else if ((fd = loc_open (ptr, ms[i].name)) >= 0 &&
		       loc_monetary (fd, ptr))
		{
		  ms[i].locale = ptr;
		  return (char *) ms[i].locale;
		}
	      errno = ENOENT;
	      return NULL;	/* Unknown locale */

	    case LC_NUMERIC:
	      if (!strcmp (ptr, "C"))
		{
		  _numeric_info = &__numeric_C;
		  ms[i].locale = "C";
		  return (char *) ms[i].locale;
		}
	      else if ((fd = loc_open (ptr, ms[i].name)) >= 0 &&
		       loc_numeric (fd, ptr))
		{
		  ms[i].locale = ptr;
		  return (char *) ms[i].locale;
		}
	      errno = ENOENT;
	      return NULL;	/* Unknown locale */

	    case LC_TIME:
	      if (!strcmp (ptr, "C"))
		{
		  _time_info = &__time_C;
		  ms[i].locale = "C";
		  return (char *) ms[i].locale;
		}
	      else if ((fd = loc_open (ptr, ms[i].name)) >= 0 &&
		       loc_time (fd, ptr))
		{
		  ms[i].locale = ptr;
		  return (char *) ms[i].locale;
		}
	      errno = ENOENT;
	      return NULL;	/* Unknown locale */

	    case LC_RESPONSE:
	      if (!strcmp (ptr, "C"))
		{
		  _response_info = &__response_C;
		  ms[i].locale = "C";
		  return (char *) ms[i].locale;
		}
	      else if ((fd = loc_open (ptr, ms[i].name)) >= 0 &&
		       loc_response (fd, ptr))
		{
		  ms[i].locale = ptr;
		  return (char *) ms[i].locale;
		}
	      errno = ENOENT;
	      return NULL;	/* Unknown locale */

	    case LC_MESSAGES:
	      if (ptr == NULL)
		{
		  errno = ENOENT;
		  return NULL;	/* Unknown locale */
		}

	      /* Some interaction with nls required to
	       * ensure correct settings
	       */
	      ms[i].locale = ptr;
	      return (char *) ms[i].locale;
	    }
	}

      /*
       * Invalid category requested
       */
      errno = EINVAL;
      return NULL;
    }

  /*
   * If we here, then LC_ALL was requested
   */

  for (i = 0; i < sizeof (ms) / sizeof (struct message_struct) - 1; i++)
    {
      /* Save current setting */
      ms[i].saved_locale = setlocale (ms[i].category, NULL);

      if (setlocale (ms[i].category, name) == NULL)
	{
	  /* Oops! Recover original state */
	  int j;
	  int se = errno;

	  for (j = 0; j < i; j++)
	    (void) setlocale (ms[j].category, ms[j].saved_locale);

	  errno = se;
	  return NULL;
	}
    }

  /* if we here, then all individual locales were installed */

  ms[i].locale = *name == '\0' ? getenv("LANG") : name;

  return (char *) ms[i].locale;

#else

  /* Braindead implementation until I finish the fancy one.  */

  if (name == NULL || name[0] == '\0')
      return (char *) "C";

  if (!strcmp (name, "C") || !strcmp (name, "POSIX"))
    return (char *) name;

  errno = EINVAL;
  return NULL;
#endif
}

#if defined(__linux__) && defined(USE_ISO_8859_1)

static int
DEFUN (loc_open, (loc_name, cat_name), CONST unsigned char *loc_name AND CONST unsigned char *cat_name)
{
  unsigned char buffer[128];

  (void) strcpy (buffer, _PATH_LOCALE);
  (void) strcat (buffer, "/");
  (void) strcat (buffer, loc_name);
  (void) strcat (buffer, "/");
  (void) strcat (buffer, cat_name);

  return open (buffer, O_RDONLY);
}

static int
DEFUN (guard_check, (fd, cname), CONST int fd AND CONST unsigned char *cname)
{
#ifndef NOGUARD
  unsigned char fcname[256];

  /*
   * Guard check (overkill?)
   */

  fcname[sizeof (fcname) - 1] = '\0';
  if (read (fd, fcname, sizeof (fcname) - 1) < strlen (cname) + 1)
    {
      (void) close (fd);
      return 0;
    }
  else if (strcmp (cname, fcname))
    {
      (void) close (fd);
      return 0;
    }

#endif

  return 1;
}

static int
DEFUN (loc_collate, (fd, cname), CONST int fd AND CONST unsigned char *cname)
{
  static unsigned char values[UCHAR_MAX + 1];
  static unsigned char offsets[UCHAR_MAX + 1];
  static struct collate_info cinfo =
  {
    0,
    NULL,
    values,
    offsets
  };

  if (read (fd, values, sizeof (values)) != sizeof (values))
    {
      (void) close (fd);
      return 0;
    }
  if (read (fd, offsets, sizeof (offsets)) != sizeof (offsets))
    {
      (void) close (fd);
      return 0;
    }
  if (read (fd, &cinfo.nsubsts, sizeof (cinfo.nsubsts)) != sizeof (cinfo.nsubsts))
    {
      (void) close (fd);
      return 0;
    }
  /*
   * XXX: for a while a did not like substitutions
   */
  if (cinfo.nsubsts)
    {
      (void) close (fd);
      return 0;
    }

  if (!guard_check (fd, cname))
    return 0;

  (void) close (fd);

  _collate_info = &cinfo;

  return 1;
}

static int
DEFUN (loc_ctype, (fd, cname), CONST int fd AND CONST unsigned char *cname)
{
  /*
   * There is no urgent need for multibytes
   */
  extern CONST struct ctype_mbchar_info __ctype_mbchar_C;
  static struct ctables
    {
      unsigned short int xtype[UCHAR_MAX + 2];
      unsigned char xlower[UCHAR_MAX + 2];
      unsigned char xupper[UCHAR_MAX + 2];
    }
  ctables;
  static struct ctype_ctype_info c2info =
  {
    ctables.xtype,
    ctables.xlower,
    ctables.xupper
  };
  static CONST struct ctype_info cinfo =
  {
    &c2info,
    (struct ctype_mbchar_info *) &__ctype_mbchar_C	/* No multibytes!!! */
  };

  if (read (fd, &ctables, sizeof (ctables)) != sizeof (ctables))
    {
      (void) close (fd);
      return 0;
    }

  if (!guard_check (fd, cname))
    return 0;

  (void) close (fd);

  __ctype_b = ctables.xtype + 1;
  __ctype_tolower = ctables.xlower + 1;
  __ctype_toupper = ctables.xupper + 1;

  _ctype_info = &cinfo;

  return 1;
}

static int
DEFUN (loc_rdline, (fd, buf, buflen, target), CONST int fd AND unsigned char *buf AND CONST int buflen AND unsigned char **target[])
{
  int i;
  short len;
  unsigned char *cp = buf;

  for (i = 0; target[i] != NULL; i++)
    {
      if (read (fd, &len, sizeof (len)) != sizeof (len) ||
	  len > buflen - (cp - buf) ||
	  read (fd, cp, len) != len ||
	  *(cp + len - 1) != '\0')
	{
	  (void) close (fd);
	  return 0;
	}
      *target[i] = cp;
      cp += len;
    }
  return 1;
}

static int
DEFUN (loc_monetary, (fd, cname), CONST int fd AND CONST unsigned char *cname)
{
  static struct monetary_info minfo;
  static unsigned char **lines[] =
  {
    (unsigned char **) &minfo.int_curr_symbol,
    (unsigned char **) &minfo.currency_symbol,
    (unsigned char **) &minfo.mon_decimal_point,
    (unsigned char **) &minfo.mon_thousands_sep,
    (unsigned char **) &minfo.mon_grouping,
    (unsigned char **) &minfo.positive_sign,
    (unsigned char **) &minfo.negative_sign,
    NULL
  };
  static unsigned char mbuffer[128];	/* Hope it long enough */

  if (!loc_rdline (fd, mbuffer, sizeof (mbuffer), lines))
    return 0;

#define fclen (((&minfo.n_sign_posn) - (&minfo.int_frac_digits)) + sizeof (minfo.n_sign_posn))

  if (read (fd, &minfo.int_frac_digits, fclen) != fclen)
    {
      (void) close (fd);
      return 0;
    }

#undef fclen

  if (!guard_check (fd, cname))
    return 0;

  (void) close (fd);

  _monetary_info = &minfo;

  return 1;
}

static int
DEFUN (loc_numeric, (fd, cname), CONST int fd AND CONST unsigned char *cname)
{
  static struct numeric_info ninfo;
  static unsigned char **lines[] =
  {
    (unsigned char **) &ninfo.decimal_point,
    (unsigned char **) &ninfo.thousands_sep,
    (unsigned char **) &ninfo.grouping,
    NULL
  };
  static unsigned char nbuffer[64];	/* Hope it long enough */

  if (!loc_rdline (fd, nbuffer, sizeof (nbuffer), lines))
    return 0;

  if (!guard_check (fd, cname))
    return 0;

  (void) close (fd);

  _numeric_info = &ninfo;

  return 1;
}

static int
DEFUN (loc_time, (fd, cname), CONST int fd AND CONST unsigned char *cname)
{
  static struct time_info tinfo;
  static unsigned char **lines[] =
  {
    (unsigned char **) &tinfo.abbrev_wkday[0],
    (unsigned char **) &tinfo.abbrev_wkday[1],
    (unsigned char **) &tinfo.abbrev_wkday[2],
    (unsigned char **) &tinfo.abbrev_wkday[3],
    (unsigned char **) &tinfo.abbrev_wkday[4],
    (unsigned char **) &tinfo.abbrev_wkday[5],
    (unsigned char **) &tinfo.abbrev_wkday[6],
    (unsigned char **) &tinfo.full_wkday[0],
    (unsigned char **) &tinfo.full_wkday[1],
    (unsigned char **) &tinfo.full_wkday[2],
    (unsigned char **) &tinfo.full_wkday[3],
    (unsigned char **) &tinfo.full_wkday[4],
    (unsigned char **) &tinfo.full_wkday[5],
    (unsigned char **) &tinfo.full_wkday[6],
    (unsigned char **) &tinfo.abbrev_month[0],
    (unsigned char **) &tinfo.abbrev_month[1],
    (unsigned char **) &tinfo.abbrev_month[2],
    (unsigned char **) &tinfo.abbrev_month[3],
    (unsigned char **) &tinfo.abbrev_month[4],
    (unsigned char **) &tinfo.abbrev_month[5],
    (unsigned char **) &tinfo.abbrev_month[6],
    (unsigned char **) &tinfo.abbrev_month[7],
    (unsigned char **) &tinfo.abbrev_month[8],
    (unsigned char **) &tinfo.abbrev_month[9],
    (unsigned char **) &tinfo.abbrev_month[10],
    (unsigned char **) &tinfo.abbrev_month[11],
    (unsigned char **) &tinfo.full_month[0],
    (unsigned char **) &tinfo.full_month[1],
    (unsigned char **) &tinfo.full_month[2],
    (unsigned char **) &tinfo.full_month[3],
    (unsigned char **) &tinfo.full_month[4],
    (unsigned char **) &tinfo.full_month[5],
    (unsigned char **) &tinfo.full_month[6],
    (unsigned char **) &tinfo.full_month[7],
    (unsigned char **) &tinfo.full_month[8],
    (unsigned char **) &tinfo.full_month[9],
    (unsigned char **) &tinfo.full_month[10],
    (unsigned char **) &tinfo.full_month[11],
    (unsigned char **) &tinfo.ampm[0],
    (unsigned char **) &tinfo.ampm[1],
    (unsigned char **) &tinfo.date_time,
    (unsigned char **) &tinfo.date,
    (unsigned char **) &tinfo.time,
    (unsigned char **) &tinfo.ut0,
    (unsigned char **) &tinfo.tz,
    NULL
  };
  static unsigned char tbuffer[1024];	/* Hope it long enough */

  if (!loc_rdline (fd, tbuffer, sizeof (tbuffer), lines))
    return 0;

  if (!guard_check (fd, cname))
    return 0;

  (void) close (fd);

  _time_info = &tinfo;

  return 1;
}

static int
DEFUN (loc_response, (fd, cname), CONST int fd AND CONST unsigned char *cname)
{
  static struct response_info rinfo;
  static unsigned char **lines[] =
  {
    (unsigned char **) &rinfo.yesexpr,
    (unsigned char **) &rinfo.noexpr,
    NULL
  };
  static unsigned char rbuffer[64];	/* Hope it long enough */

  if (!loc_rdline (fd, rbuffer, sizeof (rbuffer), lines))
    return 0;

  if (!guard_check (fd, cname))
    return 0;

  (void) close (fd);

  _response_info = &rinfo;

  return 1;
}

#endif
