/***************************************************************************
         ccasefolder.cpp  -  Get a case folded version of a string
                             -------------------
    begin                : Thu Jul 17 2008
    copyright            : (C) 2008 by Edward Sheldrake
    email                : ejs1920@yahoo.co.uk
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "ccasefolder.h"

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifndef ICONV_CONST
#define ICONV_CONST
#endif

/*
 * Using iconv directly to convert to/from local and unicode.
 * CIconv outputs a CString and that isn't really appropriate.
 * There is mbstowcs() but it doesn't seem to work.
 */
#include <iconv.h>

/* For local encoding */
#include "cconfig.h"

/* calloc */
#include <stdlib.h>

/* printf */
#include <stdio.h>

/* errno */
#include <errno.h>

/* int32_t */
#ifdef HAVE_STDINT_H
#include <stdint.h>
#else
#define int32_t int
#endif

#ifdef WORDS_BIGENDIAN
#define UCS4_HOST "UCS-4BE"
#else
#define UCS4_HOST "UCS-4LE"
#endif

/** */
CCaseFolder::CCaseFolder()
{
	CString localenc;
	if ( CConfig::Instance() )
	{
		localenc = CConfig::Instance()->GetLocalEncoding();
	}
	else
	{
		localenc = "UTF-8";
	}
	
	to_ucs4 = iconv_open( UCS4_HOST, localenc.Data() );
	from_ucs4 = iconv_open( localenc.Data(), UCS4_HOST );
}

/** */
CCaseFolder::~CCaseFolder()
{
	if ( to_ucs4 != (iconv_t)-1 )
	{
		iconv_close( to_ucs4 );
	}
	
	if ( from_ucs4 != (iconv_t)-1 )
	{
		iconv_close( from_ucs4 );
	}
}

/** */
bool CCaseFolder::Fold( const CString & input, CString & output )
{
	if ( (to_ucs4 == (iconv_t) -1) || (from_ucs4 == (iconv_t) -1) )
	{
		return false;
	}

	/* wchar_t was only 2 bytes on Windows, int32_t is always 4 bytes */
	const int int32_t_size = sizeof(int32_t);
	int errors = 0;
	
	char * inbuf = input.Data();
	size_t inleft = input.Length();
	
	size_t outleft = inleft * int32_t_size + 10;
	int32_t * unfolded = (int32_t*) calloc( 1, outleft );
	
	if ( !unfolded )
	{
		return false;
	}
	
	char * outbuf = (char*) unfolded;
	char * outstart = outbuf;
	
	size_t res = (size_t) -1;
	
	while ( res == (size_t) -1 )
	{
		res = iconv( to_ucs4, (ICONV_CONST char **)&inbuf, &inleft, &outbuf, &outleft );
		
		if ( res == (size_t) -1 )
		{
			if ( errno == EILSEQ )
			{
				outstart[outbuf - outstart] = '_';
				
				inbuf++;
				inleft--;
				
				outbuf++;
				outleft--;
				
				errors++;
			}
			else
			{
				printf("CCaseFolder::Fold: iconv() to unicode failed %d\n",errno);
				free(unfolded);
				return false;
			}
		}
	}
	
	/* output length in bytes */
	size_t len = (input.Length() * int32_t_size + 10) - outleft;
	
	/* output length in wchars */
	
	if ( len%int32_t_size != 0 )
	{
		printf("CCaseFolder::Fold: unexpected output size\n");
		free( unfolded );
		return false;
	}
	
	size_t wlen = len / int32_t_size;
	
	/* greatest change is 1 character to 3 */
	size_t foldedlen = len * 3 + 10;
	
	int32_t * folded = (int32_t *) calloc( 1, foldedlen );
	
	if ( !folded )
	{
		free(unfolded);
		return false;
	}
	
	size_t outpos = 0;
	for ( size_t i = 0; i < wlen; ++i )
	{
		switch ( unfolded[i] )
		{
#include "ccasefolder-generated-code.cpp"
			default:
				folded[outpos] = unfolded[i];
				++outpos;
				break;
		}
	}
	
	/* finished with unfolded unicode */
	free( unfolded );
	
	inbuf = (char*) folded;
	inleft = outpos * int32_t_size;
	
	outleft = (outpos * int32_t_size) + 10;
	char * resultdata = (char*) calloc( 1, outleft );
	outbuf = resultdata;
	
	res = (size_t) -1;
	
	while ( res == (size_t) -1 )
	{
		res = iconv( from_ucs4, (ICONV_CONST char**)&inbuf, &inleft, &outbuf, &outleft );
		
		if ( res == (size_t) -1 )
		{
			if ( errno == EILSEQ )
			{
				resultdata[outbuf - resultdata] = '_';
				
				inbuf++;
				inleft--;
				
				outbuf++;
				outleft--;
				
				errors++;
			}
			else
			{
				printf("CCaseFolder::Fold: iconv() from unicode failed %d\n",errno);
				free( folded );
				free( resultdata );
				return false;
			}
		}
	}
	
	/* finished with folded unicode */
	free( folded );
	
	/*
	 * copy result data to output variable - CString computes whatever the length is,
	 * the data is null terminated
	 */
	output = resultdata;
	
	/* free memory */
	free( resultdata );
	
	if ( errors > 0 )
	{
		printf("CCaseFolder::Fold: total %d EILSEQs encountered\n",errors);
	}
	
	return true;
}
