package com.vmx;

import java.io.*;
import java.util.Vector;
import com.one.*;
import com.one.file.FileConnection;

public class StringEncoder
{
	public static final char EMPTY_CHAR = ' ';

	/** Таблицы кодировок */
    protected static char[][] codepage;
    protected static byte[][] fcetable;
    
    /** Названия кодировок */
    public static String[] encodings;
    public static String[] encnames;
    
    public static final int ENC_UTF8 = -1;
    public static final int ENC_UTF16N = -2; // Network Order
    public static final int ENC_UTF16L = -3; // Little Endian
    
    public static final String ENC_NAME_UTF8 = "Unicode UTF-8";
    public static final String ENC_NAME_UTF16N = "Unicode UTF-16n";
    public static final String ENC_NAME_UTF16L = "Unicode UTF-16l";
    
    public static int ENC_DEFAULT = 0;
    public static int ENC_ARCHIVE = 0;
    
	/**
	 * Конструктор. Пустой.
	 */
	public StringEncoder()
	{
	}
	
	/**
	 * Загрузить кодовые таблицы
	 */
	public static void loadCodePages(boolean loadfce) throws IOException
	{
		Vector v = new Vector();

		String defenc = null;
        String arcenc = null;
		
		RandomAccessInputStream is = new BufferedInputStream(AuxClass.getResourceAsStream("/enc/enc.ini"));
		
		int enc = InputStreamDecoder.detectEncoding(is, ENC_UTF8);
		//int pos = is.tell();
		//is.reset();
		
		BufDataInputStream bdis = new BufDataInputStream(is);
		//bdis.seek(pos);
		
		InputStreamDecoder isd = new InputStreamDecoder(bdis, enc, 0);
		IniRecord record = null;
		
		while((record = IniRecord.getNextRecord(isd)) != null)
		{
			if(defenc == null && record.key.equals("DEFAULT"))
			{
				defenc = record.value;
			}
			else if(arcenc == null && record.key.equals("ARCHIVE"))
			{
				arcenc = record.value;
			}
			else
			{
				v.addElement(record);
			}
		}
		
		isd.close();
		
		codepage = new char[v.size()][128];
		encnames = new String[v.size()];
		encodings = new String[v.size()];
		
		DataInputStream dis = null;
		
		for(int i = 0; i < v.size(); i++)
		{
			encnames[i] = ((IniRecord)v.elementAt(i)).key;
			encodings[i] = ((IniRecord)v.elementAt(i)).value;

			try
			{
				isd = InputStreamDecoder.getResourceDecoder("/enc/" + encodings[i] + ".ini");

				for(int j = 0; j < 128; j++)
				{
					codepage[i][j] = (char)j;
				}

				while((record = IniRecord.getNextRecord(isd)) != null)
				{
					try
					{
						codepage[i][Integer.parseInt(record.key, 16) - 128] = (char)Integer.parseInt(record.value, 16);
					}
					catch(Exception e)
					{
					}
				}
			}
			catch(NullPointerException npe)
			{
				// текстового файла нет -
				// пробуем открыть двоичный

				try
				{
					dis = new DataInputStream(AuxClass.getResourceAsStream("/enc/" + encodings[i] + ".enc"));

					for(int j = 0; j < 128; j++)
					{
						codepage[i][j] = dis.readChar();
					}

					dis.close();
				}
				catch(Exception e)
				{
					ErrScreen.showErrMsg(39, e);
				}
			}
			catch(Exception e)
			{
				ErrScreen.showErrMsg(39, e);
			}
		}

		if(loadfce)
		{
			try
			{
				fcetable = new byte[v.size()][65536];

				for(int i = 0; i < v.size(); i++)
				{
					for(int j = 0; j < 65536; j++)
					{
						fcetable[i][j] = (byte)EMPTY_CHAR;
					}

					for(int j = 0; j < 128; j++)
					{
						fcetable[i][codepage[i][j]] = (byte)(j + 128);
					}
				}
			}
			catch(OutOfMemoryError oome)
			{
				fcetable = null;
			}
		}
		else
		{
			fcetable = null;
		}
		
		if(defenc != null)
		{
			ENC_DEFAULT = findEncoding(defenc);
		}
		
		if(arcenc != null)
		{
			ENC_ARCHIVE = findEncoding(arcenc);
		}
	}
	
	/**
	 * Кодировать строку s в кодировку enc
	 */
	public static byte[] encodeString(String s, int enc)
	{
		if(enc >= 0)
		{
			byte[] bs = new byte[s.length()];
			char[] cs = s.toCharArray();
			
			for(int i = 0; i < s.length(); i++)
			{
				bs[i] = (byte)encodeChar(cs[i], enc);
			}
			
			return bs;
		}
		else if(enc == ENC_UTF8)
		{
			try
			{
				return s.getBytes("UTF-8");
			}
			catch(UnsupportedEncodingException x)
			{
				ErrScreen.showErrMsg(40, x);
				return null;
			}
		}
		else if(enc == ENC_UTF16N)
		{
			byte[] bs = new byte[s.length() * 2];
			char[] cs = s.toCharArray();
			
			for(int i = 0; i < s.length(); i++)
			{
				bs[i * 2] = (byte)(cs[i] >>> 8);
				bs[i * 2 + 1] = (byte)(cs[i] & 0xFF);
			}
			
			return bs;
		}
		else if(enc == ENC_UTF16L)
		{
			byte[] bs = new byte[s.length() * 2];
			char[] cs = s.toCharArray();
			
			for(int i = 0; i < s.length(); i++)
			{
				bs[i * 2] = (byte)(cs[i] & 0xFF);
				bs[i * 2 + 1] = (byte)(cs[i] >>> 8);
			}
			
			return bs;
		}
		
		return null;
	}
	
	/**
	 * Получить длину строки s в байтах в кодировке enc
	 */
	public static int getEncodedLength(String s, int enc)
	{
		if(enc >= 0)
		{
			return s.length();
		}
		else if(enc == ENC_UTF8)
		{
			try
			{
				return s.getBytes("UTF-8").length;
			}
			catch(UnsupportedEncodingException x)
			{
				ErrScreen.showErrMsg(41, x);
				return -1;
			}
		}
		else if(enc == ENC_UTF16N || enc == ENC_UTF16L)
		{
			return s.length() * 2;
		}
		
		return -1;
	}

	/**
	 * Прочитать из потока строку длиной len байт в кодировке enc
	 */
	public static String readString(InputStream is, int len, int enc) throws IOException
	{
		byte[] b = new byte[len];
		len = is.read(b, 0, len);

		return StringEncoder.decodeString(b, 0, len, enc);
	}

	/**
	 * Декодировать участок массива b длиной len со смещения off из кодировки enc
	 */
	public static String decodeString(byte[] bs, int off, int len, int enc)
	{
		if(enc >= 0)
		{
			StringBuffer s = new StringBuffer(bs.length);
			
			for(int i = 0; i < len; i++)
			{
				s.append(decodeChar(bs[off + i], enc));
			}
			
			return s.toString();
		}
		else if(enc == ENC_UTF8)
		{
			try
			{
				return new String(bs, off, len, "UTF-8");
			}
			catch(UnsupportedEncodingException x)
			{
				ErrScreen.showErrMsg(42, x);
				return null;
			}
		}
		else if(enc == ENC_UTF16N)
		{
			StringBuffer s = new StringBuffer(bs.length / 2);
			
			if(len % 2 != 0)
			{
				len--;
			}
			
			for(int i = off; i < off + len; i += 2)
			{
				s.append((char)(((char)bs[i] << 8) | bs[i + 1]));
			}
			
			return s.toString();
		}
		else if(enc == ENC_UTF16L)
		{
			StringBuffer s = new StringBuffer(bs.length / 2);
			
			if(len % 2 != 0)
			{
				len--;
			}
			
			for(int i = off; i < off + len; i += 2)
			{
				s.append((char)(((char)bs[i + 1] << 8) | bs[i]));
			}
			
			return s.toString();
		}
		
		return null;
	}
	
	/**
	 * Декодировать символ
	 */
	public static char decodeChar(int b, int enc)
	{
		if(b < 0)
		{
			b += 256;
		}
		
		if(b < 128)
		{
			return (char)b;
		}
		else
		{
			return codepage[enc][b - 128];
		}
	}
	
	/**
	 * Кодировать символ
	 */
	public static int encodeChar(char ch, int enc)
	{
		if(ch < 128)
		{
			return (int)ch;
		}
		else if(fcetable != null)
		{
			return (int)fcetable[enc][ch] & 0xFF;
		}
		else
		{
			for(int i = 0; i < 128; i++)
			{
				if(ch == codepage[enc][i])
				{
					return i + 128;
				}
			}
			
			return (int)EMPTY_CHAR; //0x3F; // '?' - неизвестный символ
		}
	}

	/**
	 * Перекодировать текст из одной кодировки в другую.
	 *
	 * Для выполнения любых операций с Юникодом необходимы два различных файла.
	 * Для однобайтовых кодировок достаточно одного файла.
	 *
	 * @param src FileConnection с исходным текстом
	 * @param dst FileConnection для записи перекодированного текста
	 * @param srcenc кодировка исходного текста
	 * @param dstenc кодировка, в которую перекодировать
	 * @param useBOM использовать ли UTF BOM сигнатуру
	 * @throws IOException если используется Юникод и оба FileConnection соответствуют одному файлу
	 */
	public static void transcode(FileConnection src, FileConnection dst, int srcenc, int dstenc, boolean useBOM) throws IOException
	{
		if((srcenc < 0 || dstenc < 0) && (src.getPath() + src.getName()).equals(dst.getPath() + dst.getName()))
		{
			throw new IOException("For Unicode conversion source and destination files must be distinct");
		}
		
		BufDataInputStream bdis = new BufDataInputStream(new FileInputStream(src));

		// если там есть BOM сигнатура, ее надо пропустить
		InputStreamDecoder.detectEncoding(bdis, StringEncoder.ENC_DEFAULT);
		
		InputStreamDecoder isd = new InputStreamDecoder(bdis, srcenc, 0);
		OutputStreamEncoder ose = new OutputStreamEncoder(dst.openOutputStream(), dstenc);

		if(useBOM)
		{
			ose.writeBOM();
		}

		while(isd.available() > 0)
		{
			ose.writeChar(isd.readChar());
		}

		ose.close();
		isd.close();
	}
	
	/**
	 * Получить номер кодировки enc.
	 *
	 */
	public static int findEncoding(String enc)
	{
		try
		{
			return Integer.parseInt(enc);
		}
		catch(NumberFormatException nfe)
		{
		}

		for(int i = 0; i < encodings.length; i++)
		{
			if(encodings[i].equals(enc))
			{
				return i;
			}
		}
		
		return 0;
	}
	
//	private static void out(String s)
//	{
//		System.out.println("[StringEncoder] " + s);
//	}
}
