/* vi: set sw=4 ts=4: */
/*
 * CCE - Console Chinese Environment -
 * Copyright (C) 1998-2003 Rui He (rhe@3eti.com)
 *
 * 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 ``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 TERRENCE R. LAMBERT 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 	<stdio.h>
#include 	<unistd.h>
#include 	<stdlib.h>
#include 	<stdarg.h>

#include	<fcntl.h>
#include	<string.h>

#include	<defs.h>
#include	<convert.h>
#include	<errors.h>

#ifdef HAVE_SYS_MMAN_H
#include	<sys/mman.h>
#endif

int CalculateCharIndex(int coding, unsigned int High, unsigned int Low)
{
	if (coding == CODE_UNICODE)	return (High << 8 | Low);

	if (High == 0xFF || Low == 0xFF || Low == 0x7F) return -1;

	switch(coding)
	{
	case CODE_2022:
		return -1; /* not supported yet */

	case CODE_SJIS:  /* SJIS: {81-9F E0-EF} {40-7E 80-FC} */
		if (High < 0x81 || (High > 0x9F && High < 0xE0) || High > /* 0xFC */ 0xEF || Low < 0x40 || Low > 0xFC) return -1;
		High -=  (High >= 0xE0 ? 0xC1 : 0x81); 
		Low -= (Low > 0x7F ? 0x41: 0x40); /* excluding 0x7F */
		return High * (94*2) + Low;

	case CODE_GB:
	case CODE_KSC:
	case CODE_EUC:
		if (High < 0xA1 || Low < 0xA1) return -1;  /* A1-FE */
		return (High - 0xA1) * 94 + (Low - 0xA1); 

	case CODE_UHC: /*41-5A/61-7A/81-FE 126 * 178: 41-5A(0-0x19) 61-7A(0x1A-0x33) 81-FE(0x34-0xB1) */
		if (Low >= 0x81) Low -= 0x4D;
		else if (Low > 0x7A) return -1;
		else if (Low >= 0x61) Low -= 0x47;
		else if (Low > 0x5A) return -1;
		else if (Low >= 0x41) Low -= 0x41;
		else return -1;
		return (High - 0x81) * 178 + Low;

	case CODE_BIG5:
		if (High < 0xA1 || Low < 0x40 || (Low > 0x7E && Low < 0xA1)) return -1; 
		if (Low < 0xA1) Low -= 0x40; else Low -= 0x62;
		return  (High - 0xA1) * 157 + Low; 
	
	case CODE_GBK:
		if (High < 0x81 || Low < 0x40) return -1;
		if (Low > 0x7F) Low--; /* skip the 0x7F invalid code */
    	return (High - 0x81) * 190 + (Low - 0x40);

	default:
		return -1;
	}
}

int ConvertIndexToChar(int coding, int index, u_char buf[])
{
	int High, Low;

	switch(coding)
	{
	case CODE_2022:
		return -1; /* not supported yet */

	case CODE_SJIS:  /* SJIS: {81-9F E0-EF} {40-7E 80-FC} */
		High = index / (94*2) + 0x81;
		if (High >= 0xA0) High += 0xE0 - 0xA0;
		Low = index % (94 * 2) + 0x40;
		if (Low >= 0x7F) Low++;
		break;

	case CODE_GB:
	case CODE_KSC:
	case CODE_EUC:
		High = (index / 94) + 0xA1;
		Low = (index % 94) + 0xA1;
		break;

	case CODE_UHC: /*41-5A/61-7A/81-FE 126 * 178: 41-5A(0-0x19) 61-7A(0x1A-0x33) 81-FE(0x34-0xB1) */
		High = index / 178 + 0x81;
		Low = index % 178;
		if (Low >= 0x34) Low += 0x81-0x34;
		else if (Low >= 0x1A) Low += 0x61 - 0x1A;
		else Low += 0x41;
		break;

	case CODE_BIG5:
		High = index / 157 + 0xA1;
		Low = index % 157 + 0x40;
		if (Low >= 0x7F) Low += 0xA1 - 0x7F;  /* to 0xA1-0xFE */
		break;
	
	case CODE_GBK:
		High = index / 190 + 0x81;
		Low = index % 190 + 0x40;
		if (Low >= 0x7F) Low++; /* skip 0x7F */
		break;
	case CODE_UNICODE:
		High = index >> 8;
		Low = index & 0xFF;
		break;

	default:
		return -1;
	}

	if (High == 0xFF || Low == 0xFF || Low == 0x7F) return -1;
	
	if (buf)
	{
		buf[0] = (u_char)High;
		buf[1] = (u_char)Low;
	}

	return High << 8 | Low;

}

int LastCodePoint[CODE_NUM] =
{
	0x0000, 0xFEFE, 0xEFFC,  /* 2022, EUC, SJIS */
	0xFEFE, 0xFEFE, 0xFEFE,  /* Big5, GB, GBK */
	0xFEFE, 0xFEFE, 0xFFFF,  /* KSC, UHC, Unicode */
};
 
int GetEncodingSpace(int coding)
{
	return CalculateCharIndex(coding, LastCodePoint[coding] >> 8, LastCodePoint[coding] & 0xFF)+1;
}

#if !defined(CONVTABLE_USE_MMAP) 

#include "gencceconv.c"
 
#else /* CONVTABLE_USE_MMAP  */

ConvTableHeader *Header;
unsigned short  *ConvertTable[CODE_NUM][CODE_NUM];
unsigned int ConvertTableSize[CODE_NUM][CODE_NUM];

int MmapConvertTable(char *filename)
{
	int fd = -1, size, i, j;
	unsigned char *p;
	
	if (Header) return (int)Header; /* already mapped(>0) or tried(-1) */
	
	if ((fd = open(filename, O_RDONLY|CCE_OPENFLAG)) < 0 || 
		read(fd, &size, sizeof(int)) != sizeof(int)
		|| size <= 0 || 
		(p = mmap(0, size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED)
	{
		if (fd >= 0) close(fd);
		Header = (ConvTableHeader *)-1; /* tried, but failed  0 not try yet */
		return -1;
	}

	Header = (ConvTableHeader *)p;
	for(i = 0; i < CODE_NUM; i++)
		for(j = 0; j < CODE_NUM; j++)
		{
			if (Header->ConvertTableOffset[i][j] > 0)
				ConvertTable[i][j] = (unsigned short *)(p + Header->ConvertTableOffset[i][j]);
			else ConvertTable[i][j] = NULL;
		}
	
	memcpy( &ConvertTableSize, Header->ConvertTableSize, sizeof(ConvertTableSize));

#ifdef DEBUG
	message("Mmapped CCE conversion table %s size %d at address %p\r\n",
			filename, size, p);
#endif
	
	close(fd);  /* won't affect mmapped region */
	return 0;
}

int MunmapConvertTable(void)
{
	int ret = -1;
	if (Header != NULL && Header != (ConvTableHeader*)-1 && Header->size > 0)
	{

#ifdef DEBUG
		message("Munmapped CCE conversion table size %d at address %p\r\n",
			Header->size, Header);
#endif
	
		ret = munmap(Header, Header->size);
		if (ret == 0) Header = NULL; /* success, clear it */
	}
	return ret;
}

#endif /* CONVTABLE_USE_MMAP */

int ShiftJIS2JIS(unsigned int ch, unsigned int cl, unsigned char *OutCh, unsigned char *OutCl)
{
    ch -= (ch > 0x9F) ? 0xB1: 0x71;
    ch = (ch << 1) + 1;

    if (cl > 0x9E)
    {
        cl = cl - 0x7E;  /* [9F-FC] -> [21-7E] */
        ch ++;            /* [22-7E, even] */
    }
    else
    {
        if (cl > 0x7E) cl --;  /* [80-9E] -> [7F-9D] */
        cl -= 0x1F;             /* [40-7E 7F-9D] => [21-7E] */
    }

	ch |= 0x80;
	cl |= 0x80; /* set the high bit */

	if (OutCh) *OutCh = ch & 0xFF;
	if (OutCl) *OutCl = cl & 0xFF; /* just for safety */

	return (ch << 8) | cl;
}

int JIS2ShiftJIS(unsigned int ch, unsigned int cl, unsigned char *OutCh, unsigned char *OutCl)
{
	ch &= 0x7F; cl &= 0x7F; /* make sure it's in 21-7E range */

    if (ch & 1) /* odd [21-5F 60-7E] => [40-7E 80-9E] 94 codepoints */
        cl += (cl > 0x5F ? 0x20 : 0x1F);
    else cl += 0x7E; /* even, [21-7E] => [9F->FC]  94 codepoints */

    ch = ((ch - 0x21) >> 1) + 0x81;
    if (ch > 0x9F) ch += 0x40;

    if (OutCh) *OutCh = ch;
    if (OutCl) *OutCl = cl;
   
	return (ch << 8) | cl;
}

int ConvertEncoding_Char(int inCoding, int outCoding, unsigned char High, unsigned char Low, 
		unsigned char *OutHigh, unsigned char *OutLow)
{
	int OutChar = -1, index;
	int finalOutCoding = -1;
	
	index = CalculateCharIndex(inCoding, High, Low);

	if (index >= 0 && inCoding == CODE_SJIS)
	{
		if (outCoding != CODE_SJIS)  /* output is different */
		{
			ShiftJIS2JIS(High, Low, &High, &Low); 
			inCoding = CODE_EUC;
			index = CalculateCharIndex(inCoding, High, Low);
		}
  	}
	else if (outCoding == CODE_SJIS)
	{
		finalOutCoding = CODE_SJIS;
		outCoding = CODE_EUC;
	}
	else if (outCoding == CODE_UHC)
	{
		finalOutCoding = CODE_UHC;
		outCoding = CODE_KSC;  /* only 1 path from KSC->UHC */
	}	
	
	do
	{
		if (index < 0)  return  -1; /* input char encoding invalid */

		if (inCoding == outCoding || (inCoding == CODE_GB && outCoding == CODE_GBK) ||
            (inCoding == CODE_KSC && outCoding == CODE_UHC) ) 
		{
			OutChar = (High << 8 | Low );
		}
		else if ((inCoding == CODE_GBK && outCoding == CODE_GB) || (inCoding == CODE_UHC && outCoding == CODE_KSC))
		{
			if (High < 0xA1 || Low < 0xA1)  /* both GB/GBK KSC/UHC doesn't allow 0xFF */
				return -1;
			OutChar = (High << 8 | Low);
		}
		else  /* now we need to use the ConvertTable */
		{
#ifdef CONVTABLE_USE_MMAP
			if (Header == NULL) MmapConvertTable(CCELIB "/" CONV_TABLE_FILE);
#endif			
			if (ConvertTable[inCoding][outCoding] ) /* there is a convert table exist! */
			{
				if ( index >= (int)ConvertTableSize[inCoding][outCoding] || /* NULL is 0 */
			    	 (OutChar = (int)ConvertTable[inCoding][outCoding][index]) <= 0 )
				{
					return -1;
				}
			}
			else  /* do direct ConvertTable exist, lets go to GB2312 first */
			{
				if (inCoding == CODE_GBK)  /* no table for GBK->GB */
				{
					if (High < 0xA1 || Low < 0xA1)  
					return -1;
				}
			 	else
				{
					int TmpOutChar;

					if ( index >= (int)ConvertTableSize[inCoding][CODE_GB] ||
                		(TmpOutChar = (int)ConvertTable[inCoding][CODE_GB][index]) <= 0 )
						return -1; /* can't convert to GB either! failed! */

					High = TmpOutChar >> 8;
					Low = TmpOutChar & 0xFF;
				}
				inCoding = CODE_GB;
				index = CalculateCharIndex(CODE_GB, High, Low);
			}
		}

	}while(OutChar < 0);  /* OutChar > 0 terminate condition */

	if (outCoding == CODE_EUC && finalOutCoding == CODE_SJIS)
		return JIS2ShiftJIS(OutChar >> 8, OutChar & 0xFF, OutHigh, OutLow);

   	if (OutHigh) *OutHigh = (unsigned char)(OutChar >> 8);
   	if (OutLow)  *OutLow  = (unsigned char)(OutChar & 0xFF);

    return OutChar;
}

