/*
 * Copyright (C) 2008 Search Solution Corporation. All rights reserved by Search Solution. 
 *
 *   This program is free software; you can redistribute it and/or modify 
 *   it under the terms of the GNU General Public License as published by 
 *   the Free Software Foundation; version 2 of the License. 
 *
 *  This program 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 General Public License for more details. 
 *
 *  You should have received a copy of the GNU General Public License 
 *  along with this program; if not, write to the Free Software 
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 *
 */

/*
 * message_catalog.c - Message catalog functions with NLS support
 */

#ident "$Id$"

#include "config.h"

#ifndef NLERR
#define NLERR   (nl_catd) -1
#endif

#undef HAVE_NL_TYPES_H
#ifdef HAVE_NL_TYPES_H
#include <nl_types.h>
#else /* HAVE_NL_TYPES_H */
/*
 * Note: stems from FreeBSD nl_type.h and msgcat.c.
 */
#if defined(WINDOWS)
#include <windows.h>
#endif

#include <sys/types.h>
#include <sys/stat.h>
#if !defined(WINDOWS)
#include <sys/mman.h>
#include <netinet/in.h>
#endif

#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <locale.h>
#include <string.h>
#include <limits.h>
#include <stdio.h>

#include "porting.h"
#include "message_catalog.h"
#include "environment_variable.h"
#include "error_code.h"
#include "language_support.h"
#if defined(WINDOWS)
#include "intl_support.h"
#endif

#define NL_SETMAX       255
#define NL_MSGMAX       32767

/*
 * MESSAGE CATALOG FILE FORMAT.
 *
 * The NetBSD/FreeBSD message catalog format is similar to the format used by
 * Svr4 systems.  The differences are:
 *   * fixed byte order (big endian)
 *   * fixed data field sizes
 *
 * A message catalog contains four data types: a catalog header, one
 * or more set headers, one or more message headers, and one or more
 * text strings.
 */

#define NLS_MAGIC      0xff88ff89

struct nls_cat_hdr
{
  INT32 _magic;
  INT32 _nsets;
  INT32 _mem;
  INT32 _msg_hdr_offset;
  INT32 _msg_txt_offset;
};

struct nls_set_hdr
{
  INT32 _setno;			/* set number: 0 < x <= NL_SETMAX */
  INT32 _nmsgs;			/* number of messages in the set  */
  INT32 _index;			/* index of first msg_hdr in msg_hdr table */
};

struct nls_msg_hdr
{
  INT32 _msgno;			/* msg number: 0 < x <= NL_MSGMAX */
  INT32 _msglen;
  INT32 _offset;
};

#ifndef ENCODING_LEN
#define ENCODING_LEN    40
#endif

#define NL_SETD         0
#define NL_CAT_LOCALE   1

typedef struct _nl_cat_d
{
  void *_data;
  int _size;
#if defined(WINDOWS)
  HANDLE map_handle;
#endif
} *nl_catd;

static nl_catd catopen (const char *, int);
static char *catgets (nl_catd, int, int, const char *);
static int catclose (nl_catd);

/*
 * Note: stems from FreeBSD nl_type.h and msgcat.c.
 */
#define DEFAULT_NLS_PATH "/usr/share/nls/%L/%N.cat:/usr/share/nls/%N/%L:/usr/local/share/nls/%L/%N.cat:/usr/local/share/nls/%N/%L"

static nl_catd load_msgcat (const char *);

nl_catd
catopen (const char *name, int type)
{
  int spcleft, saverr;
  char path[PATH_MAX];
  char *nlspath, *lang, *base, *cptr, *pathP, *tmpptr;
  char *cptr1, *plang, *pter, *pcode;
  struct stat sbuf;

  if (name == NULL || *name == '\0')
    {
      errno = EINVAL;
      return (NLERR);
    }

  /* is it absolute path ? if yes, load immediately */
  if (strchr (name, '/') != NULL)
    return (load_msgcat (name));

  if (type == NL_CAT_LOCALE)
    lang = setlocale (LC_MESSAGES, NULL);
  else
    lang = getenv ("LANG");

  if (lang == NULL || *lang == '\0' || strlen (lang) > ENCODING_LEN
      || (lang[0] == '.' && (lang[1] == '\0'
			     || (lang[1] == '.' && lang[2] == '\0')))
      || strchr (lang, '/') != NULL)
    {
      lang = "C";
    }

  if ((plang = cptr1 = strdup (lang)) == NULL)
    return (NLERR);
  if ((cptr = strchr (cptr1, '@')) != NULL)
    *cptr = '\0';
  pter = pcode = "";
  if ((cptr = strchr (cptr1, '_')) != NULL)
    {
      *cptr++ = '\0';
      pter = cptr1 = cptr;
    }
  if ((cptr = strchr (cptr1, '.')) != NULL)
    {
      *cptr++ = '\0';
      pcode = cptr;
    }

  if ((nlspath = getenv ("NLSPATH")) == NULL)
    nlspath = DEFAULT_NLS_PATH;

  if ((base = cptr = strdup (nlspath)) == NULL)
    {
      saverr = errno;
      free (plang);
      errno = saverr;
      return (NLERR);
    }

  while ((nlspath = strsep (&cptr, ":")) != NULL)
    {
      pathP = path;
      if (*nlspath)
	{
	  for (; *nlspath; ++nlspath)
	    {
	      if (*nlspath == '%')
		{
		  switch (*(nlspath + 1))
		    {
		    case 'l':
		      tmpptr = plang;
		      break;
		    case 't':
		      tmpptr = pter;
		      break;
		    case 'c':
		      tmpptr = pcode;
		      break;
		    case 'L':
		      tmpptr = lang;
		      break;
		    case 'N':
		      tmpptr = (char *) name;
		      break;
		    case '%':
		      ++nlspath;
		      /* fallthrough */
		    default:
		      if (pathP - path >= sizeof (path) - 1)
			goto too_long;
		      *(pathP++) = *nlspath;
		      continue;
		    }
		  ++nlspath;
		put_tmpptr:
		  spcleft = sizeof (path) - (pathP - path) - 1;
		  if (strlcpy (pathP, tmpptr, spcleft) >= spcleft)
		    {
		    too_long:
		      free (plang);
		      free (base);
		      errno = ENAMETOOLONG;
		      return (NLERR);
		    }
		  pathP += strlen (tmpptr);
		}
	      else
		{
		  if (pathP - path >= sizeof (path) - 1)
		    goto too_long;
		  *(pathP++) = *nlspath;
		}
	    }
	  *pathP = '\0';
	  if (stat (path, &sbuf) == 0)
	    {
	      free (plang);
	      free (base);
	      return (load_msgcat (path));
	    }
	}
      else
	{
	  tmpptr = (char *) name;
	  --nlspath;
	  goto put_tmpptr;
	}
    }
  free (plang);
  free (base);
  errno = ENOENT;
  return (NLERR);
}

char *
catgets (nl_catd catd, int set_id, int msg_id, const char *s)
{
  struct nls_cat_hdr *cat_hdr;
  struct nls_set_hdr *set_hdr;
  struct nls_msg_hdr *msg_hdr;
  int l, u, i, r;

  if (catd == NULL || catd == NLERR)
    {
      errno = EBADF;
      /* LINTED interface problem */
      return (char *) s;
    }

  cat_hdr = (struct nls_cat_hdr *) catd->_data;
  set_hdr = (struct nls_set_hdr *) (void *) ((char *) catd->_data
					     + sizeof (struct nls_cat_hdr));

  /* binary search, see knuth algorithm b */
  l = 0;
  u = ntohl ((UINT32) cat_hdr->_nsets) - 1;
  while (l <= u)
    {
      i = (l + u) / 2;
      r = set_id - ntohl ((UINT32) set_hdr[i]._setno);

      if (r == 0)
	{
	  msg_hdr = (struct nls_msg_hdr *)
	    (void *) ((char *) catd->_data +
		      sizeof (struct nls_cat_hdr) +
		      ntohl ((UINT32) cat_hdr->_msg_hdr_offset));

	  l = ntohl ((UINT32) set_hdr[i]._index);
	  u = l + ntohl ((UINT32) set_hdr[i]._nmsgs) - 1;
	  while (l <= u)
	    {
	      i = (l + u) / 2;
	      r = msg_id - ntohl ((UINT32) msg_hdr[i]._msgno);
	      if (r == 0)
		{
		  return ((char *) catd->_data +
			  sizeof (struct nls_cat_hdr) +
			  ntohl ((UINT32)
				 cat_hdr->_msg_txt_offset) +
			  ntohl ((UINT32) msg_hdr[i]._offset));
		}
	      else if (r < 0)
		{
		  u = i - 1;
		}
	      else
		{
		  l = i + 1;
		}
	    }

	  /* not found */
	  goto notfound;

	}
      else if (r < 0)
	{
	  u = i - 1;
	}
      else
	{
	  l = i + 1;
	}
    }

notfound:
  /* not found */
  errno = ENOMSG;
  /* LINTED interface problem */
  return (char *) s;
}

int
catclose (nl_catd catd)
{
  if (catd == NULL || catd == NLERR)
    {
      errno = EBADF;
      return (-1);
    }

#if defined(WINDOWS)
  UnmapViewOfFile (catd->_data);
  CloseHandle (catd->map_handle);
#else
  munmap (catd->_data, (size_t) catd->_size);
#endif
  free (catd);
  return (0);
}

/*
 * Internal support functions
 */

static nl_catd
load_msgcat (const char *path)
{
  struct stat st;
  nl_catd catd;
  void *data = NULL;
#if defined(WINDOWS)
  HANDLE map_handle;
  OFSTRUCT open_info;
  HFILE file_handle;
#else
  int fd;
#endif

  /* XXX: path != NULL? */

#if defined(WINDOWS)
  file_handle = OpenFile (path, &open_info, OF_READ);
  if (file_handle == HFILE_ERROR)
    {
      return (NLERR);
    }
  if (stat (path, &st) != 0)
    {
      CloseHandle (file_handle);
      return NLERR;
    }

  map_handle =
    CreateFileMapping (file_handle, NULL, PAGE_READONLY, 0, st.st_size, NULL);
  if (map_handle != NULL)
    {
      data = MapViewOfFile (map_handle, FILE_MAP_READ, 0, 0, 0);
    }
#else
  if ((fd = open (path, O_RDONLY)) == -1)
    return (NLERR);

  if (fstat (fd, &st) != 0)
    {
      close (fd);
      return (NLERR);
    }

  data = mmap (0, (size_t) st.st_size, PROT_READ, MAP_SHARED, fd, (off_t) 0);
  close (fd);
#endif

  if (data == MAP_FAILED)
    return (NLERR);

  if (ntohl ((UINT32) ((struct nls_cat_hdr *) data)->_magic) != NLS_MAGIC)
    {
#if defined(WINDOWS)
      UnmapViewOfFile (data);
      CloseHandle (map_handle);
#else
      munmap (data, (size_t) st.st_size);
#endif
      errno = EINVAL;
      return (NLERR);
    }

  if ((catd = malloc (sizeof (*catd))) == NULL)
    {
#if defined(WINDOWS)
      UnmapViewOfFile (data);
      CloseHandle (map_handle);
#else
      munmap (data, (size_t) st.st_size);
#endif
      return (NLERR);
    }

  catd->_data = data;
  catd->_size = (int) st.st_size;
#if defined(WINDOWS)
  catd->map_handle = map_handle;
#endif
  return (catd);
}
#endif /* !HAVE_NL_TYPES_H */

#define CAT_FILE_DIR    "msg"

/* system message catalog definition */
struct msgcat_def
{
  int cat_id;
  const char *name;
  MSG_CATD msg_catd;
};
struct msgcat_def msgcat_System[] = {
  {MSGCAT_CATALOG_CUBRID /* 0 */ , "cubrid.cat", NULL},
  {MSGCAT_CATALOG_CSQL /* 1 */ , "csql.cat", NULL},
  {MSGCAT_CATALOG_UTILS /* 2 */ , "utils.cat", NULL}
};

#define MSGCAT_SYSTEM_DIM \
        (sizeof(msgcat_System) / sizeof(struct msgcat_def))

/*
 * msgcat_init - initialize message catalog module
 *   return: NO_ERROR or ER_FAILED
 */
int
msgcat_init (void)
{
  int i;

  for (i = 0; i < MSGCAT_SYSTEM_DIM; i++)
    {
      if (msgcat_System[i].msg_catd == NULL)
	msgcat_System[i].msg_catd = msgcat_open (msgcat_System[i].name);
    }

  for (i = 0; i < MSGCAT_SYSTEM_DIM; i++)
    {
      if (msgcat_System[i].msg_catd == NULL)
	return ER_FAILED;
    }

  return NO_ERROR;
}

/*
 * msgcat_final - finalize message catalog module
 *   return: NO_ERROR or ER_FAILED
 */
int
msgcat_final (void)
{
  int i, rc;

  rc = NO_ERROR;
  for (i = 0; i < MSGCAT_SYSTEM_DIM; i++)
    {
      if (msgcat_System[i].msg_catd != NULL)
	{
	  if (msgcat_close (msgcat_System[i].msg_catd) != NO_ERROR)
	    rc = ER_FAILED;
	  msgcat_System[i].msg_catd = NULL;
	}
    }

  return rc;
}

/*
 * msgcat_message -
 *   return: a message string or NULL
 *   cat_id(in):
 *   set_id(in):
 *   msg_id(in):
 *
 * Note:
 */
char *
msgcat_message (int cat_id, int set_id, int msg_id)
{
  char *msg;
  static char *empty = "";

  if (cat_id < 0 || cat_id >= MSGCAT_SYSTEM_DIM)
    return NULL;
  if (msgcat_System[cat_id].msg_catd == NULL)
    {
      msgcat_System[cat_id].msg_catd =
	msgcat_open (msgcat_System[cat_id].name);
      if (msgcat_System[cat_id].msg_catd == NULL)
	return NULL;
    }

  msg = msgcat_gets (msgcat_System[cat_id].msg_catd, set_id, msg_id, NULL);
  if (msg == NULL)
    {
      fprintf (stderr,
	       "Cannot find message id %d in set id %d from the file %s(%s).",
	       msg_id, set_id, msgcat_System[cat_id].name,
	       msgcat_System[cat_id].msg_catd->file);
      /* to protect the error of copying NULL pointer, return empty string ("")
       * rather than NULL */
      return empty;
    }

  return msg;
}

/*
 * msgcat_open - open a message catalog
 *   return: message catalog descriptor MSG_CATD or NULL
 *   name(in): message catalog file name
 *
 * Note: File name will be converted to a full path name with the the root
 *       directory prefix.
 *       The returned MSG_CATD is allocated with malloc(). It will be freed in
 *       msgcat_close().
 */
MSG_CATD
msgcat_open (const char *name)
{
  nl_catd catd;
  MSG_CATD msg_catd;
  char path[PATH_MAX];

  /* $CUBRID/msg/$LANG/'name' */
  snprintf (path, PATH_MAX, "%s/%s/%s/%s", envvar_root (), CAT_FILE_DIR,
	    lang_name (), name);
  catd = catopen (path, 0);
  if (catd == NLERR)
    {
      /* try once more as default language */
      snprintf (path, PATH_MAX, "%s/%s/%s/%s", envvar_root (), CAT_FILE_DIR,
		LANG_NAME_DEFAULT, name);
      catd = catopen (path, 0);
      if (catd == NLERR)
	return NULL;
    }

  msg_catd = (MSG_CATD) malloc (sizeof (*msg_catd));
  msg_catd->file = strdup (path);
  msg_catd->catd = (void *) catd;

  return msg_catd;
}

/*
 * msgcat_gets - read a message string from the message catalog
 *   return:
 *   msg_catd(in): message catalog descriptor
 *   set_id(in): set id (number)
 *   msg_id(in): message id (number)
 *   s(in): default message string which shall be returned if the identified
 *          message is not available
 *
 * Note:
 */
char *
msgcat_gets (MSG_CATD msg_catd, int set_id, int msg_id, const char *s)
{
  nl_catd catd;

  catd = (nl_catd) msg_catd->catd;
  return catgets (catd, set_id, msg_id, s);
}

/*
 * msgcat_close - close a message catalog
 *   return: NO_ERROR or ER_FAILED
 *   msg_catd(in): message catalog descriptor MSG_CATD
 *
 * Note:
 */
int
msgcat_close (MSG_CATD msg_catd)
{
  nl_catd catd;

  catd = (nl_catd) msg_catd->catd;
  free ((void *) msg_catd->file);
  free (msg_catd);
  if (catclose (catd) < 0)
    return ER_FAILED;

  return NO_ERROR;
}

/*
 * msgcat_open_file - open a text file in the directory of message catalogs
 *   return: FILE pointer or NULL
 *   name(in):
 */
FILE *
msgcat_open_file (const char *name)
{
  FILE *fp;
  char path[PATH_MAX];

  /* $CUBRID/msg/$LANG/'name' */
  snprintf (path, PATH_MAX, "%s/%s/%s/%s", envvar_root (), CAT_FILE_DIR,
	    lang_name (), name);
  fp = fopen (path, "r");
  if (fp == NULL)
    {
      /* try once more as default language */
      snprintf (path, PATH_MAX, "%s/%s/%s/%s", envvar_root (), CAT_FILE_DIR,
		LANG_NAME_DEFAULT, name);
      fp = fopen (path, "r");
    }

  return fp;
}
