/***************************************************************************
 *   copyright           : (C) 2002 by Hendrik Sattler                     *
 *   mail                : post@hendrik-sattler.de                         *
 *                                                                         *
 *   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; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

//own headers
#include <charsets.h>
#include <helper.h>
#include <gtincl.h>
#include <intincl.h>
#include "ucs4.h"

//standard headers
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <iconv.h>
#include <limits.h>


static
size_t replace_char_escape (char **inbuf, size_t *inbytesleft,
                            char **outbuf, size_t *outbytesleft)
{
  uint16_t temp = (uint16_t)(*((ucs4char_t*)*inbuf) & UINT16_MAX);

  if (inbuf == NULL || *inbuf == NULL ||
      outbuf == NULL || *outbuf == NULL ||
      *inbytesleft <= 0) {
    return 0;
  }
  if (*outbytesleft < 5) {
    return (size_t)-1;
  }
  /* do this endianess independent */
  sprintf(*outbuf,"\\%02X%02X",(temp>>8)&0xFF,temp&0xFF);
  *inbuf += sizeof(ucs4char_t);
  *inbytesleft -= sizeof(ucs4char_t);
  *outbuf += 5;
  *outbytesleft -= 5;
  return 0;
}

static
size_t replace_char_questionmark (char **inbuf, size_t *inbytesleft,
				  char **outbuf, size_t *outbytesleft)
{
  if (inbuf == NULL || *inbuf == NULL ||
      outbuf == NULL || *outbuf == NULL ||
      *inbytesleft <= 0) {
    return 0;
  }
  if (*outbytesleft < 5) {
    return (size_t)-1;
  }
  sprintf(*outbuf,"?");
  *inbuf += sizeof(ucs4char_t);
  *inbytesleft -= sizeof(ucs4char_t);
  *outbuf += 1;
  *outbytesleft -= 1;
  return 0;
}

static
size_t replace_char(enum repmode replacement_mode,
		    char **inbuf, size_t *inbytesleft,
		    char **outbuf, size_t *outbytesleft)
{
  size_t retval = 0;
  switch(replacement_mode) {
  case REPMODE_IGNORE:
    fprintf(stderr,_("%s: Unicode character 0x%lx cannot be converted.\n"),_("Error"),
            (unsigned long)*((ucs4char_t*)*inbuf));
    break;
  case REPMODE_ESCAPE_CHARS:
    retval = replace_char_escape(inbuf,inbytesleft,outbuf,outbytesleft);
    break;
  case REPMODE_QUESTIONMARK:
    retval = replace_char_questionmark(inbuf,inbytesleft,outbuf,outbytesleft);
    break;
  }
  return retval;
}

char* convert_from_internal (const char* to_code,
			     ucs4char_t* input,
			     enum repmode replacement_mode)
{
  iconv_t cd;
  size_t status;
  int estatus;
  char* retval;

  size_t insize = sizeof(ucs4char_t);
  size_t insize_conv;
  char* inptr = (char*)input;
  char* inptr_conv = inptr;

  /* must be at least 5 for replace_char functions */
  const size_t mult = (MB_LEN_MAX>5)?MB_LEN_MAX:5;

  /* this should be enough for every possible locale
   * MB_CUR_MAX can NOT be used!
   */
  size_t outsize = ucs4len(input)*mult;
  size_t outsize_conv = outsize;
  char* outptr;
  char* outptr_conv;

  if (to_code == NULL || input == NULL) return NULL;
  
  cd = iconv_open(to_code,ucs4_get_iconv_charset());
  if (cd == (iconv_t)-1) {
    fprintf(stderr,_("Error on text conversion from charset \"%s\" to charset \"%s\": %s\n"),
	    ucs4_get_iconv_charset(),to_code,strerror(errno));
    exit(EXIT_FAILURE);
  }

  retval = mem_alloc(outsize+mult,1); //not to be modified later
  outptr = retval;
  outptr_conv = retval;

  while (ucs4len((ucs4char_t*)inptr) > 0) {
    insize_conv = insize;
    status = iconv(cd,
		   (ICONV_CAST)&inptr_conv,&insize_conv,
		   &outptr_conv,&outsize_conv);
    estatus = errno;
    /* the character conversion may have failed
     * because the target charset has no such char
     */
    if (status > (size_t)0) {
      insize_conv = insize;
      //set the vars back to before conversion try
      inptr_conv = inptr;
      outptr_conv = outptr;
      outsize_conv = outsize;
      status = replace_char(replacement_mode,
			    &inptr_conv,&insize_conv,
			    &outptr_conv,&outsize_conv);
      if (status == (size_t)-1) {
	//there is only one implemented
	estatus = E2BIG;
      }
    }
    /* the character conversion/replacement may be buggy
     */
    if (status == (size_t)-1) {
      switch (estatus) {
      case E2BIG: //we have to resize outbuf, should never happen
	fprintf(stderr,"%s: %s %s\n",_("Error"),
	        _("insufficient memory on unicode decoding."),
                _("Please report as bug."));
	exit(EXIT_FAILURE);
	break;
      case EINVAL:
      case EILSEQ:
	fprintf(stderr,"%s: %s\n",_("Error with internal charset"),strerror(estatus));
	exit(EXIT_FAILURE);
	break;
      default:
	fprintf(stderr,"%s: %s\n",_("Error"),strerror(estatus));
	exit(EXIT_FAILURE);
	break;
      }
    }
    //we update the loop-external values, too
    inptr = inptr_conv;
    outptr = outptr_conv;
    outsize = outsize_conv;
  }
  iconv_close(cd);

  return retval;
}
