package com.one;

import com.one.file.Connector;
import com.one.file.FileConnection;
import com.vmx.BufDataInputStream;
import com.vmx.InputStreamDecoder;
import com.vmx.IntVector;
import com.vmx.ProgressCallback;
import com.vmx.StringEncoder;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;

public class GraphicFont extends AbstractFont
{
	protected static final String ASCII_DESCRIPTOR = "font.ini";
	protected static final String BINARY_DESCRIPTOR = "font.bin";
	protected static final int BINARY_VERSION = 1;

	protected static final int SIZE = 256;

	protected String path;

	protected String name;
	protected int sizepoints;

	protected int[][][] data = new int[SIZE][SIZE][];
	protected int[][] colors = new int[SIZE][SIZE];

	protected int ascent, descent, leading;
	protected short[][] widths = new short[SIZE][SIZE];

	protected String[][] source = new String[SIZE][SIZE];
	protected short[][] srcx = new short[SIZE][SIZE];
	protected short[][] srcy = new short[SIZE][SIZE];

	protected Hashtable vectors = new Hashtable();
	protected long[] usage = new long[SIZE];

	public GraphicFont(String path, ProgressCallback callback) throws IOException
	{
		if(path.indexOf("://") < 0)
		{
			path = "file:///" + path;
		}
		
		if(!path.endsWith("/"))
		{
			path += "/";
		}

		this.path = path;

		try
		{
			readBinaryDescriptor(callback);
		}
		catch(IOException e)
		{
			readAsciiDescriptor(callback);
		}
	}

	protected void readAsciiDescriptor(ProgressCallback callback) throws IOException
	{
		FileConnection fc = (FileConnection)Connector.open(path + ASCII_DESCRIPTOR, Connector.READ);
		BufDataInputStream bdis = new BufDataInputStream(new FileInputStream(fc));
		InputStreamDecoder ini = new InputStreamDecoder(bdis, StringEncoder.ENC_DEFAULT, 0); // InputStreamDecoder.getStreamDecoder(Connector.openInputStream(path + ASCII_DESCRIPTOR)); // InputStreamDecoder.getFileDecoder(fc);
		IniRecord record;

		String section = null;
		IntVector vector = null;
		boolean info = true;

		int ch, hi, lo;
		String[] param;

		if(callback != null)
		{
			callback.setProgress(0);
			callback.setMax(ini.available());
		}

		while((record = IniRecord.getNextRecord(ini)) != null)
		{
			try
			{
				if(record.value.startsWith("[") && record.value.endsWith("]"))
				{
					section = record.value.substring(1, record.value.length() - 1).trim();
					info = section.equalsIgnoreCase("Info");

					if(vector != null)
					{
						vector.trimToSize();
					}
					
					if(!info)
					{
						vector = new IntVector(SIZE);
						vectors.put(section, vector);
					}

					if(callback != null)
					{
						callback.setProgress(callback.getMax() - ini.available());
					}

					continue;
				}

				if(info)
				{
					if(record.key.equalsIgnoreCase("Face"))
					{
						name = record.value;
					}
					else if(record.key.equalsIgnoreCase("Size"))
					{
						sizepoints = Integer.parseInt(record.value);
					}
					else if(record.key.equalsIgnoreCase("Ascent"))
					{
						ascent = Integer.parseInt(record.value);
					}
					else if(record.key.equalsIgnoreCase("Descent"))
					{
						descent = Integer.parseInt(record.value);
					}
					else if(record.key.equalsIgnoreCase("Leading"))
					{
						leading = Integer.parseInt(record.value);
					}
				}
				else
				{
					ch = Integer.parseInt(record.key, 16) & 0xFFFF;

					vector.add(ch);

					hi = ch >>> 8;
					lo = ch & 0xFF;

					param = TextProcessor.vectorToStringArray(StringPattern.tokenizeString(record.value, ','));

					source[hi][lo] = section;

					srcx[hi][lo] = (short)Integer.parseInt(param[0].trim());
					srcy[hi][lo] = (short)Integer.parseInt(param[1].trim());

					widths[hi][lo] = (short)Integer.parseInt(param[2].trim());
				}
			}
			catch(Exception e)
			{
				System.out.println(e.toString());
			}
		}

		if(vector != null)
		{
			vector.trimToSize();
		}

		ini.close();
		fc.close();

		writeBinaryDescriptor(callback);

		initialize();
	}

	protected void writeBinaryDescriptor(ProgressCallback callback) throws IOException
	{
		if(callback != null)
		{
			callback.setProgress(0);
			callback.setMax(vectors.size() + SIZE);
		}

		FileConnection fc = (FileConnection)Connector.open(path + BINARY_DESCRIPTOR);

		if(fc.exists())
		{
			fc.truncate(0);
		}
		else
		{
			fc.create();
		}

		DataOutputStream dos = fc.openDataOutputStream();

		dos.writeUTF(name);
		dos.writeShort(sizepoints);

		dos.writeShort(ascent);
		dos.writeShort(descent);
		dos.writeShort(leading);

		Hashtable indices = new Hashtable(vectors.size() + vectors.size() / 2);
		int index = 0;

		Enumeration sections = vectors.keys();
		String section;
		IntVector vector;

		dos.writeShort(vectors.size());

		while(sections.hasMoreElements())
		{
			section = (String)sections.nextElement();
			vector = (IntVector)vectors.get(section);

			dos.writeUTF(section);
			dos.writeShort(vector.size());

			for(int i = 0; i < vector.size(); i++)
			{
				dos.writeShort(vector.get(i));
			}

			indices.put(section, new Integer(index++));

			if(callback != null)
			{
				callback.progress(1);
			}
		}

		int hi, lo;

		for(hi = 0; hi < SIZE; hi++)
		{
			for(lo = 0; lo < SIZE; lo++)
			{
				if(source[hi][lo] == null)
				{
					dos.writeShort(-1);
				}
				else
				{
					dos.writeShort(((Integer)indices.get(source[hi][lo])).intValue());

					dos.writeShort(srcx[hi][lo]);
					dos.writeShort(srcy[hi][lo]);
					dos.writeShort(widths[hi][lo]);
				}
			}

			if(callback != null)
			{
				callback.progress(1);
			}
		}

		dos.close();
		fc.close();
	}

	protected void readBinaryDescriptor(ProgressCallback callback) throws IOException
	{
		DataInputStream dis = Connector.openDataInputStream(path + BINARY_DESCRIPTOR);

		name = dis.readUTF();
		sizepoints = dis.readShort();

		ascent = dis.readShort();
		descent = dis.readShort();
		leading = dis.readShort();

		int vcount = dis.readShort();

		if(callback != null)
		{
			callback.setProgress(0);
			callback.setMax(vcount + SIZE);
		}

		String[] sources = new String[vcount];

		String section;
		IntVector vector;
		int ccount;

		int hi, lo;

		for(hi = 0; hi < vcount; hi++)
		{
			section = dis.readUTF();
			ccount = dis.readShort();

			vector = new IntVector(ccount);
			vectors.put(section, vector);

			for(lo = 0; lo < ccount; lo++)
			{
				vector.add(dis.readShort() & 0xFFFF);
			}

			sources[hi] = section;

			if(callback != null)
			{
				callback.progress(1);
			}
		}

		int index;

		for(hi = 0; hi < SIZE; hi++)
		{
			for(lo = 0; lo < SIZE; lo++)
			{
				index = dis.readShort();

				if(index >= 0)
				{
					source[hi][lo] = sources[index];

					srcx[hi][lo] = dis.readShort();
					srcy[hi][lo] = dis.readShort();
					widths[hi][lo] = dis.readShort();
				}
			}

			if(callback != null)
			{
				callback.progress(1);
			}
		}

		dis.close();

		initialize();
	}

	protected void initialize()
	{
		if(source[0][0] != null)
		{
			load(0, 0);
		}
		else
		{
			widths[0][0] = (short)((ascent + descent) / 2);
			int[] rgb = new int[widths[0][0] * (ascent + descent)];

			for(int x = 1; x < widths[0][0] - 1; x++)
			{
				rgb[widths[0][0] + x] = rgb[widths[0][0] * ((ascent + descent) - 2) + x] = 0xFF000000;
			}

			for(int y = 1; y < (ascent + descent) - 1; y++)
			{
				rgb[widths[0][0] * y + 1] = rgb[widths[0][0] * y + (widths[0][0] - 2)] = 0xFF000000;
			}

			data[0][0] = rgb;
			colors[0][0] = 0x000000;
		}

		int[] rgb = data[0][0];
		int color = colors[0][0];
		short width = widths[0][0];

		int hi, lo;

		for(hi = 0; hi < SIZE; hi++)
		{
			for(lo = 0; lo < SIZE; lo++)
			{
				if(source[hi][lo] == null)
				{
					data[hi][lo] = rgb;
					colors[hi][lo] = color;
					widths[hi][lo] = width;
				}
			}
		}
	}

	public String getPath()
	{
		return path;
	}

	public String getName()
	{
		return name;
	}

	public int getSizeInPoints()
	{
		return sizepoints;
	}

	public int getHeight()
	{
		return ascent + descent + leading;
	}

	public int getBaselinePosition()
	{
		return ascent;
	}

	public int charWidth(char ch)
	{
		return ((int)widths[ch >>> 8][ch & 0xFF]) & 0xFFFF;
	}

	public void drawChar(Graphics g, char ch, int x, int y)
	{
		int hi = ch >>> 8;
		int lo = ch & 0xFF;

		if(data[hi][lo] == null)
		{
			load(hi, lo);
		}

		int[] rgb = data[hi][lo];
		int color = g.getColor() & 0xFFFFFF;

		if(colors[hi][lo] != color)
		{
			for(int i = 0; i < rgb.length; i++)
			{
				rgb[i] = (rgb[i] & 0xFF000000) | color;
			}

			colors[hi][lo] = color;
		}

		g.drawRGB(rgb, 0, widths[hi][lo], x, y, widths[hi][lo], (ascent + descent), true);

		usage[hi] = System.currentTimeMillis();
	}

	protected void load(int ph, int pl)
	{
		try
		{
			while(true)
			{
				System.gc();

				try
				{
					InputStream is = Connector.openInputStream(path + source[ph][pl]);
					Image image = Image.createImage(is);
					is.close();

					IntVector vector = (IntVector)vectors.get(source[ph][pl]);
					int ch, hi, lo;

					int[] rgb;

					for(int i = 0; i < vector.size(); i++)
					{
						ch = vector.get(i) & 0xFFFF;

						hi = ch >>> 8;
						lo = ch & 0xFF;

						rgb = new int[widths[hi][lo] * (ascent + descent)];
						image.getRGB(rgb, 0, widths[hi][lo], srcx[hi][lo], srcy[hi][lo], widths[hi][lo], (ascent + descent));

						for(int j = 0; j < rgb.length; j++)
						{
							rgb[j] = ((((rgb[j] >> 16) & 0xFF) + ((rgb[j] >> 7) & 0x1FE) + (rgb[j] & 0xFF)) << 22) & 0xFF000000;
						}

						data[hi][lo] = rgb;
						colors[hi][lo] = 0x000000;
					}

					return;
				}
				catch(OutOfMemoryError oome)
				{
					int index = findLeastUsed(ph);

					if(index > 0)
					{
						unload(index);
					}
					else
					{
						throw new RuntimeException("Consider this as \"OutOfMemoryError\"");
					}
				}
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}

	protected void unload(int ph)
	{
		for(int pl = 0; pl < SIZE; pl++)
		{
			data[ph][pl] = null;
		}

		usage[ph] = 0;

		System.gc();
	}

	public void unloadAll()
	{
		for(int ph = 1; ph < SIZE; ph++)
		{
			if(usage[ph] > 0)
			{
				unload(ph);
			}
		}
	}

	protected int findLeastUsed(int exclude)
	{
		long minvalue = System.currentTimeMillis();
		int minindex = -1;

		for(int i = 1; i < SIZE; i++)
		{
			if(i != exclude && usage[i] > 0 && usage[i] < minvalue)
			{
				minvalue = usage[i];
				minindex = i;
			}
		}

		return minindex;
	}
}