/*
 * Copyright (c) 2003-2004, Artem B. Bityuckiy
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
#include <_ansi.h>
#include <reent.h>
#include <newlib.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
#include <sys/iconvnls.h>
#ifdef _MB_CAPABLE
#include <wchar.h>
#include <iconv.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include "local.h"
#include "conv.h"
#include "ucsconv.h"
#include "iconvnls.h"
#endif

/*
 * _iconv_nls_construct_filename -- constructs full file name. 
 *
 * PARAMETERS:
 *   struct _reent *rptr - reent structure of current thread/process.  
 *   const char *file   - the name of file.
 *   const char *dir    - the name of subdirectory;
 *   const char *ext    - file extension.
 *
 * DESCRIPTION:
 *   Function constructs patch to icionv-related file.
 *   'file' shouldn't be NULL. Doesn't use extension if 'ext' is NULL.
 *
 * RETURN:
 *   The pointer to file name if success, In case of error returns NULL
 *   and sets current thread's/process's errno.
 */
const char *
_iconv_nls_construct_filename (struct _reent *rptr,
                                      const char *file,
                                      const char *dir,
                                      const char *ext)
{
  int len1, len2, len3;
  char *path;
  char *p;
  int dirlen = strlen (dir);
    
  if ((path = _getenv_r (rptr, NLS_ENVVAR_NAME)) == NULL || *path == '\0')
    path = ICONV_DEFAULT_NLSPATH;

  len1 = strlen (path);
  len2 = strlen (file);
  len3 = strlen (ext);

  if ((p = _malloc_r (rptr, len1 + dirlen + len2 + len3 + 3)) == NULL)
    return (const char *)NULL;

  memcpy (p, path, len1);
  if (p[len1 - 1] != '/')
    p[len1++] = '/';
  memcpy (p + len1, dir, dirlen);
  len1 += dirlen;
  p[len1++] = '/';
  memcpy (p + len1, file, len2);
  len1 += len2;
  if (ext != NULL)
  {
    memcpy (p + len1, ext, len3);
    len1 += len3;
  }
  p[len1] = '\0';
 
  return (const char *)p;
}


#ifdef _MB_CAPABLE
/*
 * _iconv_nls_get_mb_cur_max -- return encoding's maximum length
 *                              of a multi-byte character.
 *
 * PARAMETERS:
 *    iconv_t cd - opened iconv conversion descriptor;
 *    int direction - "from encoding" or "to encoding" direction.
 *
 * DESCRIPTION:
 *    Return maximum  length  of a multi-byte character in one of 'cd's
 *    encoding. Return "from" encoding's value if 'direction' is 0 and
 *    "to" encoding's value if 'direction' isn't 0.
 */
int
_iconv_nls_get_mb_cur_max (iconv_t cd,
                                  int direction)
{
  iconv_conversion_t *ic = (iconv_conversion_t *)cd;
  
  return ic->handlers->get_mb_cur_max (ic->data, direction);
}

/*
 * _iconv_nls_is_stateful -- is encoding stateful?
 *
 * PARAMETERS:
 *    iconv_t cd - opened iconv conversion descriptor;
 *    int direction - "from encoding" or "to encoding" direction.
 *
 * DESCRIPTION:
 *    Returns 0 if encoding is stateless or 1 if stateful.
 *    Tests "from" encoding if 'direction' is 0 and
 *    "to" encoding's value if 'direction' isn't 0.

 */
int
_iconv_nls_is_stateful (iconv_t cd,
                               int direction)
{
  iconv_conversion_t *ic = (iconv_conversion_t *)cd;
  
  return ic->handlers->is_stateful (ic->data, direction);
}

/*
 * _iconv_nls_conv - special version of iconv for NLS.
 *
 * PARAMETERS:
 *    Same as _iconv_r.
 *
 * DESCRIPTION:
 *    Function behaves as _iconv_r but:
 *    1.  Don't handle reset/return shift states queries
 *        (like iconv does when 'inbuf' == NULL, etc);
 *    2. Don't save result if 'outbuf' == NULL or
 *       '*outbuf' == NULL;
 *    3. Don't perform default conversion if there is no character
 *       in "to" encoding that corresponds to character from "from"
 *       encoding.
 *
 * RETURN:
 *    Same as _iconv_r.
 */
size_t
_iconv_nls_conv (struct _reent *rptr,
                        iconv_t cd,
                        const char **inbuf,
                        size_t *inbytesleft,
                        char **outbuf,
                        size_t *outbytesleft)
{
  iconv_conversion_t *ic = (iconv_conversion_t *)cd;
  int flags = ICONV_FAIL_BIT;

  if ((void *)cd == NULL || cd == (iconv_t)-1 || ic->data == NULL
       || (ic->handlers != &_iconv_null_conversion_handlers
           && ic->handlers != &_iconv_ucs_conversion_handlers))
    {
      _REENT_ERRNO (rptr) = EBADF;
      return (size_t)-1;
    }
  
  if (inbytesleft == NULL || *inbytesleft == 0)
    return (size_t)0;
  
  if (outbuf == NULL || *outbuf == NULL)
    flags |= ICONV_DONT_SAVE_BIT;
  
  if (outbytesleft == NULL || *outbytesleft == 0)
    {
      _REENT_ERRNO (rptr) = E2BIG;
      return (size_t)-1;
    }

  return ic->handlers->convert (rptr,
                                ic->data,
                                (const unsigned char**)inbuf,
                                inbytesleft,
                                (unsigned char**)outbuf,
                                outbytesleft,
                                flags);
}

/*
 * _iconv_nls_get_state -- get encoding's current shift state value.
 *
 * PARAMETERS:
 *    iconv_t cd - iconv descriptor; 
 *    mbstate_t *ps - where to save shift state;
 *    int direction - "from" encoding if 0, "to" encoding if 1.
 *
 * DESCRIPTION:
 *    Save encoding's current shift state to 'ps'. Save "from" encoding's
 *    shift state if 'direction' is 0 and "to" encodings's shift state
 *    if 'direction' isn't 0.
 */
void
_iconv_nls_get_state (iconv_t cd,
                             mbstate_t *ps,
                             int direction)
{
  iconv_conversion_t *ic = (iconv_conversion_t *)cd;
  
  ic->handlers->get_state (ic->data, ps, direction);

  return;
}

/*
 * _iconv_nls_set_state -- set encoding's current shift state value.
 *
 * PARAMETERS:
 *    iconv_t cd    - iconv descriptor; 
 *    mbstate_t *ps - where to save shift state.
 *    int direction - "from" encoding if 0, "to" encoding if 1.
 *
 * DESCRIPTION:
 *    Set encoding's current shift state.
 *
 * RETURN:
 *    0 if success, -1 if failure.
 */
int
_iconv_nls_set_state (iconv_t cd,
                             mbstate_t *ps,
                             int direction)
{
  iconv_conversion_t *ic = (iconv_conversion_t *)cd;
  
  return ic->handlers->set_state (ic->data, ps, direction);
}

/* Same as iconv_open() but don't perform name resolving */
static iconv_t
iconv_open1 (struct _reent *rptr,
                     const char *to,
                     const char *from)
{
  iconv_conversion_t *ic;
    
  if (to == NULL || from == NULL || *to == '\0' || *from == '\0')
    return (iconv_t)-1;

  ic = (iconv_conversion_t *)_malloc_r (rptr, sizeof (iconv_conversion_t));
  if (ic == NULL)
    return (iconv_t)-1;

  /* Select which conversion type to use */
  if (strcmp (from, to) == 0)
    {
      /* Use null conversion */
      ic->handlers = &_iconv_null_conversion_handlers;
      ic->data = ic->handlers->open (rptr, to, from);
    }
  else  
    {
      /* Use UCS-based conversion */
      ic->handlers = &_iconv_ucs_conversion_handlers;
      ic->data = ic->handlers->open (rptr, to, from);
    }

  if (ic->data == NULL)
    {
      _free_r (rptr, (void *)ic);
      return (iconv_t)-1;
    }

  return (void *)ic;
}

/*
 * _iconv_nls_open - open iconv descriptors for NLS.
 *
 * PARAMETERS:
 *     struct _reent *rptr - process's reent structure;
 *     const char *encoding - encoding name;
 *     iconv_t *tomb - wchar -> encoding iconv descriptor pointer;
 *     iconv_t *towc - encoding -> wchar iconv descriptor pointer;
 *     int flag - perform encoding name resolving flag.
 *
 * DESCRIPTION:
 *     Opens two iconv descriptors for 'encoding' -> wchar and 
 *     wchar -> 'encoding' iconv conversions. Function is used when locale or
 *     wide-oriented stream is opened. If 'flag' is 0, don't perform encoding
 *     name resolving ('encoding' must not be alias in this case).
 *
 * RETURN:
 *     If successful - return 0, else set errno and return -1.
 */
int
_iconv_nls_open (struct _reent *rptr,
                        const char *encoding,
                        iconv_t *tomb,
                        iconv_t *towc,
                        int flag)
{
  const char *wchar_encoding;

  if (sizeof (wchar_t) > 2 && WCHAR_MAX > 0xFFFF)
    wchar_encoding = "ucs_4_internal";
  else if (sizeof (wchar_t) > 1 && WCHAR_MAX > 0xFF)
    wchar_encoding = "ucs_2_internal";
  else
    wchar_encoding = ""; /* This shuldn't happen */

  if (flag)
    {
      if ((*towc = _iconv_open_r (rptr, wchar_encoding, encoding)) == (iconv_t)-1)
        return -1;
    
      if ((*tomb = _iconv_open_r (rptr, encoding, wchar_encoding)) == (iconv_t)-1)
      {
        _iconv_close_r (rptr, *towc);
        return -1;
      }
    }
  else
    {
      if ((*towc = iconv_open1 (rptr, wchar_encoding, encoding)) == (iconv_t)-1)
        return -1;
    
      if ((*tomb = iconv_open1 (rptr, encoding, wchar_encoding)) == (iconv_t)-1)
      {
        _iconv_close_r (rptr, *towc);
        return -1;
      }
    }

  return 0;
}

#endif /* _MB_CAPABLE */

