/*
 * Copyright (c) 2003-2004, Artem B. Bityuckiy
 * Copyright (c) 1999,2000, Konstantin Chuguev. All rights reserved.
 *
 * 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 "cesbi.h"

#if defined (ICONV_TO_UCS_CES_TABLE) \
 || defined (ICONV_FROM_UCS_CES_TABLE)
 
#include <_ansi.h>
#include <reent.h>
#include <newlib.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/iconvnls.h>
#include "../lib/endian.h"
#include "../lib/local.h"
#include "../lib/ucsconv.h"
#include "../ccs/ccs.h"

/*
 * Table-based CES converter is implemented here.  Table-based CES converter
 * deals with encodings with "null" CES, like KOI8-R. In this case it is
 * possible to implement one generic algorithm which works with different
 * CCS tables.
 *
 * Table-based CES converter deals with CCS tables placed into iconv/ccs
 * subdirectory. First, converter tries to find needed CCS table among
 * linked-in tables. If not found, it tries to load it from external file
 * (only if corespondent capability was enabled in Newlib configuration). 
 *
 * 16 bit encodings are assumed to be Big Endian.
 */

static ucs2_t
find_code_size (ucs2_t code, const __uint16_t *tblp);

static __inline ucs2_t
find_code_speed (ucs2_t code, const __uint16_t *tblp);

static __inline ucs2_t
find_code_speed_8bit (ucs2_t code, const unsigned char *tblp);

#ifdef _ICONV_ENABLE_EXTERNAL_CCS
static const iconv_ccs_desc_t *
load_file (struct _reent *rptr, const char *name, int direction);
#endif

/*
 * Interface data and functions implementation.
 */
static size_t
table_close (struct _reent *rptr,
                    void *data)
{
  const iconv_ccs_desc_t *ccsp = (iconv_ccs_desc_t *)data;

  if (ccsp->type == TABLE_EXTERNAL)
    _free_r (rptr, (void *)ccsp->tbl);

  _free_r( rptr, (void *)ccsp);
  return 0;
}

#if defined (ICONV_FROM_UCS_CES_TABLE)
static void *
table_init_from_ucs (struct _reent *rptr,
                            const char *encoding)
{
  int i;
  const iconv_ccs_t *biccsp = NULL;
  iconv_ccs_desc_t *ccsp;
  
  for (i = 0; _iconv_ccs[i] != NULL; i++)
    if (strcmp (_iconv_ccs[i]->name, encoding) == 0)
      {
        biccsp = _iconv_ccs[i]; 
        break;
      }

  if (biccsp != NULL)
    {
      if (biccsp->from_ucs == NULL
          || (ccsp = (iconv_ccs_desc_t *)
                     _malloc_r (rptr, sizeof (iconv_ccs_desc_t))) == NULL)
        return NULL;

      ccsp->type = TABLE_BUILTIN;
      ccsp->bits = biccsp->bits;
      ccsp->optimization = biccsp->from_ucs_type;
      ccsp->tbl = biccsp->from_ucs;
      
      return (void *)ccsp;
    }
    
#ifdef _ICONV_ENABLE_EXTERNAL_CCS
  return (void *)load_file (rptr, encoding, 1);
#else
  return NULL;
#endif
}

static size_t
table_convert_from_ucs (void *data,
                               ucs4_t in,
                               unsigned char **outbuf,
                               size_t *outbytesleft)
{
  const iconv_ccs_desc_t *ccsp = (iconv_ccs_desc_t *)data;
  ucs2_t code;

  if (in > 0xFFFF || in == INVALC)
    return (size_t)ICONV_CES_INVALID_CHARACTER;

  if (ccsp->bits == TABLE_8BIT)
    {
      code = find_code_speed_8bit ((ucs2_t)in,
                                  (const unsigned char *)ccsp->tbl);
      if (code == INVALC)
        return (size_t)ICONV_CES_INVALID_CHARACTER;
      **outbuf = (unsigned char)code;
      *outbuf += 1;
      *outbytesleft -= 1;
      return 1; 
    }
  else if (ccsp->optimization == TABLE_SPEED_OPTIMIZED)
    code = find_code_speed ((ucs2_t)in, ccsp->tbl);
  else
    code = find_code_size ((ucs2_t)in, ccsp->tbl);

  if (code == INVALC)
    return (size_t)ICONV_CES_INVALID_CHARACTER;

  if (*outbytesleft < 2)
    return (size_t)ICONV_CES_NOSPACE;
  
  /* We can't store whole word since **outbuf may be not 2-byte aligned */
  **outbuf = (unsigned char)((ucs2_t)code >> 8);
  *(*outbuf + 1) = (unsigned char)code;
  *outbuf += 2;
  *outbytesleft -= 2;
  return 2; 
}
#endif /* ICONV_FROM_UCS_CES_TABLE */

#if defined (ICONV_TO_UCS_CES_TABLE)
static void *
table_init_to_ucs (struct _reent *rptr,
                          const char *encoding)
{
  int i;
  const iconv_ccs_t *biccsp = NULL;
  iconv_ccs_desc_t *ccsp;
  
  for (i = 0; _iconv_ccs[i] != NULL; i++)
    if (strcmp (_iconv_ccs[i]->name, encoding) == 0)
      {
        biccsp = _iconv_ccs[i]; 
        break;
      }

  if (biccsp != NULL)
    {
      if (biccsp->to_ucs == NULL
          || (ccsp = (iconv_ccs_desc_t *)
                     _malloc_r (rptr, sizeof (iconv_ccs_desc_t))) == NULL)
        return NULL;

      ccsp->type = TABLE_BUILTIN;
      ccsp->bits = biccsp->bits;
      ccsp->optimization = biccsp->to_ucs_type;
      ccsp->tbl = biccsp->to_ucs;
      
      return (void *)ccsp;
    }
  
#ifdef _ICONV_ENABLE_EXTERNAL_CCS
  return (void *)load_file (rptr, encoding, 0);
#else
  return NULL;
#endif
}

static ucs4_t
table_convert_to_ucs (void *data,
                             const unsigned char **inbuf,
                             size_t *inbytesleft)
{
  const iconv_ccs_desc_t *ccsp = (iconv_ccs_desc_t *)data;
  ucs2_t ucs;
  
  if (ccsp->bits == TABLE_8BIT)
    {
      if (*inbytesleft < 1)
        return (ucs4_t)ICONV_CES_BAD_SEQUENCE;
  
      ucs = (ucs2_t)ccsp->tbl[**inbuf];
      
      if (ucs == INVALC)
        return (ucs4_t)ICONV_CES_INVALID_CHARACTER;
         
      *inbytesleft -= 1;
      *inbuf += 1;
      return (ucs4_t)ucs; 
    }

  if (*inbytesleft < 2)
    return (ucs4_t)ICONV_CES_BAD_SEQUENCE;

  if (ccsp->optimization == TABLE_SIZE_OPTIMIZED)
    ucs = find_code_size((ucs2_t)**inbuf << 8 | (ucs2_t)*(*inbuf + 1),
                         ccsp->tbl);
  else
    ucs = find_code_speed((ucs2_t)**inbuf << 8 | (ucs2_t)*(*inbuf + 1),
                          ccsp->tbl);

  if (ucs == INVALC)
    return (ucs4_t)ICONV_CES_INVALID_CHARACTER;

  *inbuf += 2;
  *inbytesleft -= 2;
  return (ucs4_t)ucs; 
}
#endif /* ICONV_TO_UCS_CES_TABLE */

static int
table_get_mb_cur_max (void *data)
{
  return ((iconv_ccs_desc_t *)data)->bits/8;
}


#if defined (ICONV_TO_UCS_CES_TABLE)
const iconv_to_ucs_ces_handlers_t
_iconv_to_ucs_ces_handlers_table = 
{
  table_init_to_ucs,
  table_close,
  table_get_mb_cur_max,
  NULL,
  NULL,
  NULL,
  table_convert_to_ucs
};
#endif /* ICONV_FROM_UCS_CES_TABLE */

#if defined (ICONV_FROM_UCS_CES_TABLE)
const iconv_from_ucs_ces_handlers_t
_iconv_from_ucs_ces_handlers_table =
{
  table_init_from_ucs,
  table_close,
  table_get_mb_cur_max,
  NULL,
  NULL,
  NULL,
  table_convert_from_ucs
};
#endif /* ICONV_TO_UCS_CES_TABLE */

/*
 * Supplementary functions.
 */

/*
 * find_code_speed - find code in 16 bit speed-optimized table.
 *
 * PARAMETERS:
 *     ucs2_t code - code whose mapping to find.
 *     const __uint16_t *tblp - table pointer.
 *
 * RETURN:
 *     Code that corresponds to 'code'.
 */
static __inline ucs2_t
find_code_speed (ucs2_t code,
                        const __uint16_t *tblp)
{
  int idx = tblp[code >> 8];

  if (idx == INVBLK)
    return (ucs2_t)INVALC;

  return (ucs2_t)tblp[(code & 0x00FF) + idx];
}

/*
 * find_code_speed_8bit - find code in 8 bit speed-optimized table.
 *
 * PARAMETERS:
 *     ucs2_t code - code whose mapping to find.
 *     const __uint16_t *tblp - table pointer.
 *
 * RETURN:
 *     Code that corresponds to 'code'.
 */
static __inline ucs2_t
find_code_speed_8bit (ucs2_t code,
                             const unsigned char *tblp)
{
  int idx;
  unsigned char ccs;

  if (code == ((ucs2_t *)tblp)[0])
    return (ucs2_t)0xFF;
 
  idx = ((ucs2_t *)tblp)[1 + (code >> 8)];
  
  if (idx == INVBLK)
    return (ucs2_t)INVALC;

  ccs = tblp[(code & 0x00FF) + idx];

  return ccs == 0xFF ? (ucs2_t)INVALC : (ucs2_t)ccs;
}

/* Left range boundary */
#define RANGE_LEFT(n)     (tblp[FIRST_RANGE_INDEX + (n)*3 + 0])
/* Right range boundary */
#define RANGE_RIGHT(n)    (tblp[FIRST_RANGE_INDEX + (n)*3 + 1])
/* Range offset */
#define RANGE_INDEX(n)    (tblp[FIRST_RANGE_INDEX + (n)*3 + 2])
/* Un-ranged offset */
#define UNRANGED_INDEX(n) (tblp[FIRST_UNRANGED_INDEX_INDEX] + (n)*2)

/*
 * find_code_size - find code in 16 bit size-optimized table.
 *
 * PARAMETERS:
 *     ucs2_t code - code whose mapping to find.
 *     const __uint16_t *tblp - table pointer.
 *
 * RETURN:
 *     Code that corresponds to 'code'.
 */
static ucs2_t
find_code_size (ucs2_t code,
                       const __uint16_t *tblp)
{
  int first, last, cur, center;

  if (tblp[RANGES_NUM_INDEX] > 0)
    {
      first = 0;
      last = tblp[RANGES_NUM_INDEX] - 1;
 
      do
        {
          center = (last - first)/2;
          cur = center + first;
          
          if (code > RANGE_RIGHT (cur))
            first = cur;
          else if (code < RANGE_LEFT (cur))
            last = cur;
          else
            return (ucs2_t)tblp[RANGE_INDEX (cur) + code - RANGE_LEFT (cur)];
        } while (center > 0);

        if (last - first == 1)
          {
            if (code >= RANGE_LEFT (first) && code <= RANGE_RIGHT (first))
              return (ucs2_t)tblp[RANGE_INDEX (first)
                                  + code - RANGE_LEFT (first)];
            if (code >= RANGE_LEFT (last) && code <= RANGE_RIGHT (last))
              return (ucs2_t)tblp[RANGE_INDEX (last)
                                  + code - RANGE_LEFT (last)];
          }
    }
  
  if (tblp[UNRANGED_NUM_INDEX] > 0)
    {
      first = 0;
      last = tblp[UNRANGED_NUM_INDEX] - 1;
 
      do
        {
          int c;

          center = (last - first)/2;
          cur = center + first;
          c = tblp[UNRANGED_INDEX (cur)];
 
          if (code > c)
            first = cur;
          else if (code < c)
            last = cur;
          else
            return (ucs2_t)tblp[UNRANGED_INDEX (cur) + 1];
        } while (center > 0);

        if (last - first == 1)
          {
            if (code == tblp[UNRANGED_INDEX (first)])
              return (ucs2_t)tblp[UNRANGED_INDEX (first) + 1];
            if (code == tblp[UNRANGED_INDEX (last)])
              return (ucs2_t)tblp[UNRANGED_INDEX (last) + 1];
          }
    }

  return (ucs2_t)INVALC;
}

#ifdef _ICONV_ENABLE_EXTERNAL_CCS

#define _16BIT_ELT(offset) \
    ICONV_BETOHS(*((__uint16_t *)(buf + (offset))))
#define _32BIT_ELT(offset) \
    ICONV_BETOHL(*((__uint32_t *)(buf + (offset))))

/*
 * load_file - load conversion table from external file and initialize
 *             iconv_ccs_desc_t object.
 *
 * PARAMETERS:
 *    struct _reent *rptr - reent structure of current thread/process.
 *    const char *name - encoding name.
 *    int direction - conversion direction.
 *
 * DESCRIPTION:
 *    Loads conversion table of appropriate endianess from external file
 *    and initializes 'iconv_ccs_desc_t' table description structure.
 *    If 'direction' is 0 - load "To UCS" table, else load "From UCS"
 *    table.
 *
 * RETURN:
 *    iconv_ccs_desc_t * pointer is success, NULL if failure.
 */
static const iconv_ccs_desc_t *
load_file (struct _reent *rptr,
                  const char *name,
                  int direction)
{
  int fd;
  const unsigned char *buf;
  int tbllen, hdrlen;
  off_t off;
  const char *fname;
  iconv_ccs_desc_t *ccsp = NULL;
  int nmlen = strlen(name);
  /* Since CCS table name length can vary - it is aligned (by adding extra
   * bytes to it's end) to 4-byte boundary. */
  int alignment = nmlen & 3 ? 4 - (nmlen & 3) : 0;
  
  nmlen = strlen(name);
  
  hdrlen = nmlen + EXTTABLE_HEADER_LEN + alignment;

  if ((fname = _iconv_nls_construct_filename (rptr, name, ICONV_SUBDIR,
                                              ICONV_DATA_EXT)) == NULL)
    return NULL;
  
  if ((fd = _open_r (rptr, fname, O_RDONLY, S_IRUSR)) == -1)
    goto error1;
  
  if ((buf = (const unsigned char *)_malloc_r (rptr, hdrlen)) == NULL)
    goto error2;

  if (_read_r (rptr, fd, (void *)buf, hdrlen) != hdrlen)
    goto error3;

  if (_16BIT_ELT (EXTTABLE_VERSION_OFF) != TABLE_VERSION_1
      || _32BIT_ELT (EXTTABLE_CCSNAME_LEN_OFF) != nmlen
      || strncmp (buf + EXTTABLE_CCSNAME_OFF, name, nmlen) != 0)
    goto error3; /* Bad file */

  if ((ccsp = (iconv_ccs_desc_t *)
           _calloc_r (rptr, 1, sizeof (iconv_ccs_desc_t))) == NULL)
    goto error3;
  
  ccsp->bits = _16BIT_ELT (EXTTABLE_BITS_OFF);
  ccsp->type = TABLE_EXTERNAL;

  /* Add 4-byte alignment to name length */
  nmlen += alignment;

  if (ccsp->bits == TABLE_8BIT)
    {
      if (direction == 0) /* Load "To UCS" table */
        {
          off = (off_t)_32BIT_ELT (nmlen + EXTTABLE_TO_SPEED_OFF);
          tbllen = _32BIT_ELT (nmlen + EXTTABLE_TO_SPEED_LEN_OFF);
        }
      else /* Load "From UCS" table */
        {
          off = (off_t)_32BIT_ELT (nmlen + EXTTABLE_FROM_SPEED_OFF);
          tbllen = _32BIT_ELT (nmlen + EXTTABLE_FROM_SPEED_LEN_OFF);
        }
    }
  else if (ccsp->bits == TABLE_16BIT)
    {
      if (direction == 0) /* Load "To UCS" table */
        {
#ifdef TABLE_USE_SIZE_OPTIMIZATION
          off = (off_t)_32BIT_ELT (nmlen + EXTTABLE_TO_SIZE_OFF);
          tbllen = _32BIT_ELT (nmlen + EXTTABLE_TO_SIZE_LEN_OFF);
#else
          off = (off_t)_32BIT_ELT (nmlen + EXTTABLE_TO_SPEED_OFF);
          tbllen = _32BIT_ELT (nmlen + EXTTABLE_TO_SPEED_LEN_OFF);
#endif
        }
      else /* Load "From UCS" table */
        {
#ifdef TABLE_USE_SIZE_OPTIMIZATION
          off = (off_t)_32BIT_ELT (nmlen + EXTTABLE_FROM_SIZE_OFF);
          tbllen = _32BIT_ELT (nmlen + EXTTABLE_FROM_SIZE_LEN_OFF);
#else
          off = (off_t)_32BIT_ELT (nmlen + EXTTABLE_FROM_SPEED_OFF);
          tbllen = _32BIT_ELT (nmlen + EXTTABLE_FROM_SPEED_LEN_OFF);
#endif
        }
#ifdef TABLE_USE_SIZE_OPTIMIZATION
      ccsp->optimization = TABLE_SIZE_OPTIMIZED; 
#else
      ccsp->optimization = TABLE_SPEED_OPTIMIZED;
#endif
    }
  else
    goto error4; /* Bad file */

  if (off == EXTTABLE_NO_TABLE)
    goto error4; /* No correspondent table in file */

  if ((ccsp->tbl = (ucs2_t *)_malloc_r (rptr, tbllen)) == NULL)
    goto error4;

  if (_lseek_r (rptr, fd, off, SEEK_SET) == (off_t)-1
      || _read_r (rptr, fd, (void *)ccsp->tbl, tbllen) != tbllen)
    goto error5;

  goto normal_exit;

error5:
  _free_r (rptr, (void *)ccsp->tbl);
  ccsp->tbl = NULL;
error4:
  _free_r (rptr, (void *)ccsp);
  ccsp = NULL;
error3:
normal_exit:
  _free_r (rptr, (void *)buf);
error2:
  if (_close_r (rptr, fd) == -1)
    {
      if (ccsp != NULL)
        {
          if (ccsp->tbl != NULL)
            _free_r (rptr, (void *)ccsp->tbl);
          _free_r (rptr, (void *)ccsp);
        }
      ccsp = NULL;
    }
error1:
  _free_r (rptr, (void *)fname);
  return ccsp;
}
#endif

#endif /* ICONV_TO_UCS_CES_TABLE || ICONV_FROM_UCS_CES_TABLE */

