#include <errno.h>

#include "unicode/utypes.h"
#include "cmemory.h"
#include "unicode/ucnv.h"
#include "icuconv.h"

static void errno_set(enum UErrorCode e);

icuconv_t
icuconv_open(const char * tocode, const char * fromcode)
{
  UErrorCode	err = U_ZERO_ERROR;
  icuconv_t	cd;

  cd = (icuconv_t)uprv_malloc(sizeof(*cd));
  if (cd == NULL) {
    errno = ENOMEM;        
    return (icuconv_t)(-1);
  }
  /*create the converters */
  cd->inConverter = ucnv_open (fromcode, &err);
  if (U_FAILURE (err)) {
    errno_set(err);
    return (icuconv_t)(-1);
  }
  cd->outConverter = ucnv_open (tocode, &err);
  if (U_FAILURE (err))
    {
      ucnv_close (cd->inConverter);
      errno_set(err);
      return (icuconv_t)(-1);
    }
  return (icuconv_t)(cd);
}


icuconv_size_t
icuconv(icuconv_t      	cd,
      const char **	inbuf,
      icuconv_size_t *		inbytesleft,
      char **		outbuf,
      icuconv_size_t *		outbytesleft)
{
  const char *mySource;
  const char *mySource_limit;

  char *myTarget;
  char *myTarget_limit;
  int32_t targetCapacity = 0;

  UErrorCode	err = U_ZERO_ERROR;

  if (cd == NULL) {
    errno = EBADF;
    return((icuconv_size_t)-1);
  }
  if (inbuf == NULL || (*inbuf) == NULL || inbytesleft == NULL)
    {
      ucnv_reset(cd->inConverter);
      ucnv_reset(cd->outConverter);
      return((icuconv_size_t)0);
    }
  mySource = (char*)*inbuf;
  mySource_limit = *inbuf + *inbytesleft;
  myTarget = *outbuf;
  myTarget_limit  = *outbuf + *outbytesleft;

  if (*outbytesleft > 0)
    {
      T_UConverter_fromCodepageToCodepage (cd->outConverter,
					   cd->inConverter,
					   &myTarget,
					   myTarget_limit,
					   &mySource,
					   mySource_limit,
					   NULL,
					   FALSE,
					   &err);
    }
  *inbuf = mySource;
  *inbytesleft = mySource_limit - mySource;
  *outbuf = myTarget;
  *outbytesleft = myTarget_limit - myTarget;

  if (U_FAILURE (err)) {
    errno_set(err);
    return((icuconv_size_t)-1);
  }
  return (icuconv_size_t)0;
}


int
icuconv_close(icuconv_t cd)
{
  if (cd == NULL) {
    errno = EBADF;
    return (int)(-1);
  }
  ucnv_close (cd->inConverter);
  ucnv_close (cd->outConverter);
  uprv_free(cd);
  return (0);
}

void
errno_set(enum UErrorCode e)
{
  int i;
  struct errno_map {
    enum UErrorCode e;
    int  err; 
  } errno_map[] = {
    { U_ZERO_ERROR, -1 },
    { U_ILLEGAL_ARGUMENT_ERROR  ,  EINVAL},       /* Start of codes indicating failure */
    { U_MISSING_RESOURCE_ERROR  ,  ENOMEM},
    { U_INVALID_FORMAT_ERROR    ,  ELIBBAD},
    { U_FILE_ACCESS_ERROR       ,  ENOENT},
    { U_INTERNAL_PROGRAM_ERROR  ,  ELIBBAD},       /* Indicates a bug in the library code */
    { U_MESSAGE_PARSE_ERROR     ,  -1},
    { U_MEMORY_ALLOCATION_ERROR ,  ENOMEM},       /* Memory allocation error */
    { U_INDEX_OUTOFBOUNDS_ERROR ,  E2BIG},
    { U_PARSE_ERROR             ,  -1},       /* Equivalent to Java ParseException */
    { U_INVALID_CHAR_FOUND      , EILSEQ},       /* In the Character conversion routines: Invalid character or sequence was encountered*/
    { U_TRUNCATED_CHAR_FOUND    , EINVAL},       /* In the Character conversion routines: More bytes are required to complete the conversion successfully*/
    { U_ILLEGAL_CHAR_FOUND      , EILSEQ},       /* In codeset conversion: a sequence that does NOT belong in the codepage has been encountered*/
    { U_INVALID_TABLE_FORMAT    , ELIBBAD},       /* Conversion table file found, but corrupted*/
    { U_INVALID_TABLE_FILE      , EINVAL},       /* Conversion table file not found*/
    { U_BUFFER_OVERFLOW_ERROR   , E2BIG},       /* A result would not fit in the supplied buffer */
    { U_UNSUPPORTED_ERROR       , -1}       /* Requested operation not supported in current context */
  };

  for( i = 0; i < sizeof(errno_map)/sizeof(struct errno_map); i++) 
    {
      if (e == errno_map[i].e)
	{
	  errno = errno_map[i].err;
	  return;
	}
    }
  errno = -1;
  return;
}
