/* Copyright (C) 1995 Free Software Foundation, Inc.

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 <assert.h>
#include <dirent.h>
#include <fcntl.h>
#include <langinfo.h>
#include <libintl.h>
#include <limits.h>
#include <obstack.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/uio.h>

#include "localedef.h"
#include "localeinfo.h"
#include "token.h"

/* We don't have these constants defined because we don't use them.  Give
   default values.  */
#define CTYPE_MB_CUR_MIN 0
#define CTYPE_MB_CUR_MAX 0
#define CTYPE_HASH_SIZE 0
#define CTYPE_HASH_LAYERS 0
#define CTYPE_CLASS 0
#define CTYPE_TOUPPER_EB 0
#define CTYPE_TOLOWER_EB 0
#define CTYPE_TOUPPER_EL 0
#define CTYPE_TOLOWER_EL 0
 

/* We have all categories defined in `categories.def'.  Now construct
   the description and data structure used for all categories.  */
#define DEFINE_CATEGORY(category, name, items, postload, in, check, out)      \
    struct cat_item category##_desc[] =					      \
      {									      \
        NO_PAREN items							      \
      };								      \
									      \
    char *category##_values[NELEMS (category##_desc) - 1] = { NULL, };
#include "categories.def"
#undef DEFINE_CATEGORY

struct category category[] =
  {
#define DEFINE_CATEGORY(category, name, items, postload, in, check, out)      \
    [category] = { _NL_NUM_##category, name, NELEMS (category##_desc) - 1,    \
                   category##_desc, category##_values, in, check, out },
#include "categories.def"
#undef DEFINE_CATEGORY
  };
#define NCATEGORIES NELEMS (category)


#define SYNTAX_ERROR							      \
    error (0, 0, gettext ("%s:%Zd: syntax error in locale definition file"),  \
	   locfile_data.filename, locfile_data.line_no)


/* Prototypes for local functions.  */
static int get_byte (char *byte_ptr);
static char *is_locale_name (int cat_no, const char *str, int len);


/* Read a locale definition file FILE.  The format is defined in
   POSIX.2 2.5.3.  */
void
locfile_read (const char *fname)
{
  /* Pointer to text of last token.  */
  char *ptr;
  /* Length of last token (or if NUMBER the value itself).  */
  int len;
  /* The last returned token.  */
  int token;
  /* For error correction we remember whether the last token was correct.  */
  int correct_token = 1;

  /* Open the desired input file on stdin.  */
  locfile_open (fname);

  while ((token = locfile_lex (&ptr, &len)) != 0)
    {
      int cat_no;

      for (cat_no = 0; cat_no < NCATEGORIES; ++cat_no)
	if (token == category[cat_no].cat_id)
	  break;

      if (cat_no >= NCATEGORIES)
	/* A syntax error occured.  No valid category defintion starts.  */
	{
	  if (correct_token != 0)
	    error (0, 0, gettext ("%s:%Zd: locale category start expected"),
		   locfile_data.filename, locfile_data.line_no);

	  /* To prevent following errors mark as error case.  */
	  correct_token = 0;

	  /* Synchronization point is the beginning of a new category.
	     Overread all line upto this silently.  */
	  ignore_to_eol (0, 0);
	  continue;
	}

      /* Rest of the line should be empty.  */
      ignore_to_eol (0, 1);

      /* Perhaps these category is already specified.  We simply give a
         warning and overwrite the values.  */
      if (category[cat_no].filled != 0)
	error (0, 0, gettext ("%s:%Zd: multiple definition of locale "
			      "category %s"), locfile_data.filename,
	       locfile_data.line_no, category[cat_no].name);

      /* We read the first token because this could be the copy statement.  */
      token = xlocfile_lex (&ptr, &len);

      if (token == TOK_COPY)
	/* Copying the definitions from an existing locale is requested.  */
	{
	  char *str;

	  /* Get the name of the locale to copy from.  */
	  token = xlocfile_lex (&ptr, &len);
	  if (token != TOK_IDENT && token != TOK_STRING)
	    /* No name, then mark error and ignore everything upto next
	       start of an category section.  */
	    {
	      /* To prevent following errors mark as error case.  */
	      correct_token = 0;

	      /* Synchronization point is the beginning of a new category.
		 Overread all line upto this silently.  */
	      ignore_to_eol (0, 0);
	    }
	  else if ((str = is_locale_name (cat_no, ptr, len)) != NULL)
	    /* Yes the name really names an existing locale file.  We are
	       returned the complete file name.  Store it so that we can
	       copy it in the output phase.  */
	    {
	      category[cat_no].copy_locale = str;
	      category[cat_no].filled = 1;
	      
	      ignore_to_eol (0, 1);
	    }
	  else
	    /* No, the name does not address a valid locale file.  Mark
	       error case and ignore rest of category.  */
	    {
	      char tmp[len + 1];
	      memcpy (tmp, ptr, len);
	      tmp[len] = '\0';
	      error (0, 0, gettext ("%s:%Zd: invalid locale `%s' in copy "
				    "statement"), locfile_data.filename,
			 locfile_data.line_no, tmp);
	      correct_token = 0;
	      ignore_to_eol (0, 0);
	    }

	  /* This should END as the next token.  */
	  token = xlocfile_lex (&ptr, &len);

	  if (token == TOK_END)
	    /* This is the end of the category.  */
	    {
	      token = xlocfile_lex (&ptr, &len);

	      if (token != category[cat_no].cat_id)
		/* Wrong category name after END.  */
		{
		  error (0, 0, gettext ("%s:%Zd: category `%s' does not "
					"end with `END %s'"),
			 locfile_data.filename, locfile_data.line_no,
			 category[cat_no].name, category[cat_no].name);
		  ignore_to_eol (0, 0);
		}
	      else
		ignore_to_eol (0, 1);

	      correct_token = 1;
	    }
	  else
	    /* No END following copy.  Give error while not in error case.  */
	    {
	      if (correct_token != 0)
		error (0, 0, gettext ("%s:%Zd: `copy' must be sole rule"),
		       locfile_data.filename, locfile_data.line_no);
	      correct_token = 0;
	      ignore_to_eol (0, 0);
	    }

	  continue;
	}

      /* Now it's time to mark as mentioned in the locale file.  */
      category[cat_no].filled = 1;

      if (category[cat_no].infct != NULL)
	/* The category needs a special input handling.  */
	{
	  category[cat_no].infct(token);
	  continue;
	}

      /* Now process the given items.  */
      while (1)
	{
	  int item_no;

	  if (token == TOK_END)
	    /* This is the end of the category.  */
	    {
	      token = xlocfile_lex (&ptr, &len);

	      if (token != category[cat_no].cat_id)
		{
		  error (0, 0, gettext ("%s:%Zd: category `%s' does not end "
					"with `END %s'"),
			 locfile_data.filename, locfile_data.line_no,
			 category[cat_no].name, category[cat_no].name);
		  ignore_to_eol (0, 0);
		}
	      else
		ignore_to_eol (0, 1);

	      /* Start next category.  */
	      break;
	    }

	  /* All other lines should describe valid items of the category.  */
	  for (item_no = 0; item_no < category[cat_no].number; ++item_no)
	    if (category[cat_no].item_desc[item_no].item_id == token)
	      break;

	  if (item_no >= category[cat_no].number)
	    /* This is not a valid item of the category.  */
	    {
	      SYNTAX_ERROR;
	      ignore_to_eol (0, 0);

	      token = xlocfile_lex (&ptr, &len);

	      /* And process next item.  */
	      continue;
	    }

	  /* Test whether already a value is defined.  */
	  if (category[cat_no].item_value[item_no] != NULL)
	    error (0, 0, gettext ("%s:%Zd: category item `%s' already "
				  "defined"),
		   locfile_data.filename, locfile_data.line_no,
		   category[cat_no].item_desc[item_no].name);

	  switch (category[cat_no].item_desc[item_no].value_type)
	    {
	    case string:
	      /* Get next token.  This is the argument to the item.  */
	      token = xlocfile_lex (&ptr, &len);

	      if (token != TOK_STRING)
		SYNTAX_ERROR;
	      else
		category[cat_no].item_value[item_no] = strdup (ptr);
	      ignore_to_eol (0, ptr != NULL);
	      break;
	    case stringarray:
	      /* This is a difficult case.  The number of strings in
		 the array may vary.  But for now its only necessary
		 with ALT_DIGITS from LC_TIME.  This item is the last
		 so be can solve it by storing the number of string in
		 the first place and the string indeces following
		 that.  */
	      {
		int cnt;
		char **buffer;
		if (category[cat_no].item_value[item_no] != NULL)
		  buffer = (char **) category[cat_no].item_value[item_no];
		else
		  buffer = (char **) xmalloc (
		    sizeof (char *) * category[cat_no].item_desc[item_no].max);

		category[cat_no].item_value[item_no] = (char *) buffer;

		/* As explained we may need a place to store the real number
		   of strings.  */
		if (category[cat_no].item_desc[item_no].min
		    != category[cat_no].item_desc[item_no].max)
		  ++buffer;

		cnt = 0;
		do
		  {
		    token = xlocfile_lex (&ptr, &len);
		    if (token != TOK_STRING)
		      {
			SYNTAX_ERROR;
			break;
		      }

		    if (cnt >= category[cat_no].item_desc[item_no].max)
		      {
			error (0, 0, gettext ("%s:%Zd: too many elements "
					      "for item `%s`"),
			       locfile_data.filename, locfile_data.line_no,
			       category[cat_no].item_desc[item_no].name);
			break;
		      }

		    buffer[cnt++] = strdup (ptr);

		    token = locfile_lex (&ptr, &len);
		  }
		while (token == TOK_CHAR && len == ';');

		ignore_to_eol (token, ptr != NULL);

		if (cnt < category[cat_no].item_desc[item_no].min)
		  error (0, 0, gettext ("%s:%Zd: too few elements for item "
					"`%s'"),
			 locfile_data.filename, locfile_data.line_no,
			 category[cat_no].item_desc[item_no].name);

		if (category[cat_no].item_desc[item_no].min
		    != category[cat_no].item_desc[item_no].max)
		  *(int *) category[cat_no].item_value[item_no] = cnt;
	      }
	      break;
	    case byte:
	      {
		int ok;
		category[cat_no].item_value[item_no] = (char *) xmalloc (
		  __alignof__ (char));
		ok = get_byte (category[cat_no].item_value[item_no]);
		ignore_to_eol (0, ok);
	      }
	      break;
	    case bytearray:
	      {
		char *buffer;
		int maxsize;
		int cnt;
		char byte;
		int ok;

		buffer = (char *) xmalloc ((maxsize = 30));
		cnt = 0;

		while ((ok = get_byte (&byte)))
		  {
		    if (cnt >= maxsize)
		      buffer = (char *) xmalloc ((maxsize *= 2));

		    buffer[cnt++] = byte;

		    token = locfile_lex (&ptr, &len);
		    if (token != TOK_CHAR || len != ';')
		      break;
		  }

		buffer[cnt] = '\0';
		category[cat_no].item_value[item_no] = buffer;
		ignore_to_eol (token, ok);
	      }
	      break;
	    default:
	      error (5, 0, gettext ("internal error in %s, line %u"),
		     __FUNCTION__, __LINE__);
	      /* NOTREACHED */
	    }

	  /* Get next token.  */
	  token = xlocfile_lex (&ptr, &len);
	} /* while (1) */
    }
}


/* Check given values for categories for consistency.  */
void
categories_check (void)
{
  int cat_no;

  for (cat_no = 0; cat_no < NCATEGORIES; ++cat_no)
    if (category[cat_no].copy_locale == NULL)
      if (category[cat_no].filled != 0)
	if (category[cat_no].checkfct)
	  category[cat_no].checkfct();
	else
	  {
	    int item_no;

	    for (item_no = 0; item_no < category[cat_no].number; ++item_no)
	      if (category[cat_no].item_value[item_no] == NULL)
		{
		  int errcode;

		  /* If the item is defined in the standard is it an error to
		     have it not defined.  */
		  errcode = category[cat_no].item_desc[item_no].status == std
		            ? 5 : 0;

		  error (errcode, 0, gettext ("item `%s' of category `%s' "
					      "undefined"),
			 category[cat_no].item_desc[item_no].name,
			 category[cat_no].name);
		}
	  }
      else
	error (0, 0, gettext ("category `%s' not defined"),
	       category[cat_no].name);
}


/* Write out the binary representation of the category data which can be
   loaded by setlocale(1).  */
void
categories_write (void)
{
  struct locale_file
  {
      int magic;
      int n;
      int idx[0];
  } *data;
  struct obstack obstk;
  int cat_no;

#define obstack_chunk_alloc xmalloc
#define obstack_chunk_free free
  obstack_init (&obstk);

  for (cat_no = 0; cat_no < NCATEGORIES; ++cat_no)
    {
      int result = 0;

      if (category[cat_no].copy_locale != NULL)
	/* Simply copy the addressed locale file of the specified
	   category.  Please note that this is tried before the distinction
	   between categories which need special handling is made.  */
	{
	  int source;

	  /* Open source file.  */
	  source = open (category[cat_no].copy_locale, O_RDONLY);
	  if (source < 0)
	    error (0, 0, gettext ("cannot copy locale definition file `%s'"),
		   category[cat_no].copy_locale);
	  else
	    {
	      /* Construct file name of output file and open for writing.  */
	      char path[strlen (output_path)
                        + strlen(category[cat_no].name) + 1];
	      int dest;
	      char *t;

	      t = stpcpy (path, output_path);
	      strcpy (t, category[cat_no].name);

	      dest = creat (path, 0666);
	      if (dest == -1)
		error (0, 0, gettext ("cannot open output file `%s': %m"),
		       path);
	      else
		{
		  char buffer[BUFSIZ];
		  int size;
		  
		  /* Copy the files.  */
		  do
		    {
		      size = read (source, buffer, BUFSIZ);
		      write (dest, buffer, size);
		    }
		  while (size > 0);

		  close (dest);

		  /* Show success.  */
		  puts (category[cat_no].name);
		}
	      close (source);
	    }

	  /* Next category.   */
	  continue;
	}

      if (category[cat_no].outfct)
	result = category[cat_no].outfct();
      else
	{
	  char *path, *t;
	  int fd;
	  struct iovec *iov;
	  int item_no, len, slen, cnt;
	  int elems = 0;

	  /* Count number of elements.  */
	  for (item_no = 0; item_no < category[cat_no].number; ++item_no)
	    {
	      switch (category[cat_no].item_desc[item_no].value_type)
		{
		case string:
		case byte:
		case bytearray:
		  ++elems;
		  break;
		case stringarray:
		  elems += category[cat_no].item_desc[item_no].max;
		  break;
		default:
		  error (5, 0, gettext ("internal error in %s, line %u"),
			 __FUNCTION__, __LINE__);
		  /* NOTREACHED */
		}
	    }

	  /* We now have the number of elements.  We build the structure
	     and a helper structure for writing all out.  */
	  len = sizeof (struct locale_file) + elems * sizeof (int);
	  data = obstack_alloc (&obstk, len);
	  iov = obstack_alloc (&obstk, (elems + 1) * sizeof (struct iovec));

	  data->magic = LIMAGIC (cat_no);
	  data->n = elems;
	  iov[0].iov_base = data;
	  iov[0].iov_len = len;

	  cnt = 0;
	  for (item_no = 0; item_no < category[cat_no].number; ++item_no)
	    if (category[cat_no].item_value[item_no] == NULL)
	      {
		switch (category[cat_no].item_desc[item_no].value_type)
		  {
		  case string:
		  case byte:
		  case bytearray:
		    data->idx[cnt] = len;
		    ++len;  /* We reserve one single byte for this entry.  */
		    iov[1 + cnt].iov_base = (char *) "";
		    iov[1 + cnt].iov_len = 1;
		    ++cnt;
		    break;
		  case stringarray:
		    {
		      int max;
		      int nstr;

		      max = category[cat_no].item_desc[item_no].max;

		      for (nstr = 0; nstr < max; ++nstr)
			{
			  data->idx[cnt] = len;
			  ++len;
			  iov[1 + cnt].iov_base = (char *) "";
			  iov[1 + cnt].iov_len = 1;
			  ++cnt;
			}
		    }
		  }
	      }
	    else
	      switch (category[cat_no].item_desc[item_no].value_type)
		{
		case string:
		case bytearray:
		  data->idx[cnt] = len;
		  slen = strlen (category[cat_no].item_value[item_no]) + 1;
		  len += slen;
		  iov[1 + cnt].iov_base = category[cat_no].item_value[item_no];
		  iov[1 + cnt].iov_len = slen;
		  ++cnt;
		  break;
		case byte:
		  data->idx[cnt] = len;
		  slen = 1;
		  len += slen;
		  iov[1 + cnt].iov_base = category[cat_no].item_value[item_no];
		  iov[1 + cnt].iov_len = slen;
		  ++cnt;
		  break;
		case stringarray:
		  {
		    int nstr, nact;
		    char **first;
		  
		    if (category[cat_no].item_desc[item_no].min
			== category[cat_no].item_desc[item_no].max)
		      {
			nstr = category[cat_no].item_desc[item_no].min;
			first = (char **) category[cat_no].item_value[item_no];
		      }
		    else
		      {
			nstr = *(int *) category[cat_no].item_value[item_no];
			first =
			  ((char **) category[cat_no].item_value[item_no]) + 1;
		      }
		    nact = nstr;
		    while (nstr > 0)
		      {
			data->idx[cnt] = len;
			if (*first != NULL)
			  {
			    slen = strlen (*first) + 1;
			    iov[1 + cnt].iov_base = first;
			  }
			else
			  {
			    slen = 1;
			    iov[1 + cnt].iov_base = (char *) "";
			  }
			len += slen;
			iov[1 + cnt].iov_len = slen;
			++cnt;
			++first;
			--nstr;
		      }
		    while (nact < category[cat_no].item_desc[item_no].max)
		      {
			data->idx[cnt] = len;
			len += 1;
			iov[1 + cnt].iov_base = (char *) "";
			iov[1 + cnt].iov_len = 1;
			++cnt;
			++nact;
		      }
		  }
		  break;
		default:
		  /* Cannot happen.  */
		  break;
		}
	  assert (cnt <= elems);

	  /* Construct the output filename from the argument given to
	     localedef on the command line.  */
	  path = (char *) obstack_alloc (&obstk, strlen (output_path) +
					 strlen (category[cat_no].name) + 1);
	  t = stpcpy (path, output_path);
	  strcpy (t, category[cat_no].name);

	  fd = creat (path, 0666);
	  if (fd == -1)
	    {
	      error (0, 0, gettext ("cannot open output file `%s': %m"), path);
	      result = 1;
	    }
	  else
	    {
	      if (writev (fd, iov, cnt + 1) == -1)
		{
		  error (0, 0, gettext ("cannot write output file `%s': %m"),
			 path);
		  result = 1;
		}

if (elems==0) write(fd, &elems, 1);

	      close (fd);
	    }
	  /* The old data is not needed anymore, but keep the obstack
	     intact.  */
	  obstack_free (&obstk, data);
	}

      if (result == 0)
	puts (category[cat_no].name);
    }
  /* Now the whole obstack can be removed.  */
  obstack_free (&obstk, NULL);
}


/* Get the representation of a number.  This is a positive integer or
   the number -1 which is handled as a special symbol by the scanner.  */
static int
get_byte (char *byte_ptr)
{
  int token;
  char *ptr;
  int len;

  token = locfile_lex (&ptr, &len);
  if (token != TOK_NUMBER && token != TOK_MINUS1)
    /* None of the valid number format.  */
    {
      error (0, 0, gettext ("%s:%Zd: number expected"),
	     locfile_data.filename, locfile_data.line_no);
      *byte_ptr = 0;
      return 0;
    }

  if (token == TOK_MINUS1)
    {
      *byte_ptr = CHAR_MAX;
      return 1;
    }

  if (len > CHAR_MAX)
    /* The value of the numbers has to be less than CHAR_MAX.  This is
       ok for the information they have to express.  */
    {
      error (0, 0, gettext ("%s:%Zd: invalid number"),
	     locfile_data.filename, locfile_data.line_no);
      *byte_ptr = 0;
      return 0;
    }

  *byte_ptr = len;
  return 1;
}


/* Test whether the string STR with length LEN is the name of an existing
   locale and whether a file for category CAT_NO is found in this directory.
   This categories are looked for in the system locale definition file
   directory.
   Return the complete file name for the category file.  */
static char *
is_locale_name (int cat_no, const char *str, int len)
{
  static char **locale_names = NULL;
  static int max_count = 0;
  static int locale_count = 0;
  int cnt, exist, fd;
  char *fname;
  struct stat st;

  if (locale_names == NULL)
    /* Read in the list of all available locales.  */
    {
      DIR *dir;
      struct dirent *dirent;
      
      /* LOCALE_NAMES is not NULL anymore, but LOCALE_COUNT == 0.  */
      ++locale_names;
	  
      dir = opendir (LOCALE_PATH);
      if (dir == NULL)
	{
	  error (1, errno, gettext ("cannot read locale directory `%s'"),
		 LOCALE_PATH);

	  return NULL;
	}

      /* Now we can look for all files in the directory.  */
      while ((dirent = readdir (dir)) != NULL)
	if (strcmp (dirent->d_name, ".") != 0
	    && strcmp (dirent->d_name, "..") != 0)
	  {
	    if (max_count == 0)
	      locale_names = (char **) xmalloc ((max_count = 10)
						* sizeof (char *));
	    else
	      locale_names = (char **) xrealloc (locale_names,
						 (max_count *= 2)
						 * sizeof (char *));
	    locale_names[locale_count++] = strdup (dirent->d_name);
	  }
      closedir (dir);
    }

  for (cnt = 0; cnt < locale_count; ++cnt)
    if (strncmp (str, locale_names[cnt], len) == 0
	&& locale_names[cnt][len] == '\0')
      break;
  
  if (cnt >= locale_count)
    return NULL;
  
  /* Now search for this specific locale file.  */
  asprintf (&fname, "%s/%s/%s", LOCALE_PATH, locale_names[cnt],
	    category[cat_no].name);
  
  fd = open (fname, O_RDONLY);
  if (fd < 0)
    {
      free (fname);
      return NULL;
    }
  
  exist = fstat (fd, &st);
  close (fd);
  
  if (exist < 0)
    {
      free (fname);
      return NULL;
    }

  return fname;
}

/*
 * Local Variables:
 *  mode:c
 *  c-basic-offset:2
 * End:
 */