package com.one.vector;

import javax.microedition.lcdui.*;
import java.util.*;
import java.io.*;
import com.vmx.ProgressCallback;

public class VectorImage
{
	public static final int VERSION = 0x0401;
	
	protected Vector groups;
	protected int count;
	
	protected String title;
	protected String description;
	protected Vector comments;
	protected byte[] extra;
	
	protected int version;
	
	protected int backcolor;
	protected boolean useback;
	public boolean usecolor;
	
	public int width, height;
	protected int nativewidth, nativeheight;
	
	public double[] param = new double[6];
	public int frame;
	
	protected double fps;
	protected long framedelay;
	protected int maxframe;
	protected int loop;
	
	protected int[] clip = new int[4];
	protected int color, stroke;
	
	protected Image offscreen;
	protected Graphics offgraphics;
	protected int[] rgb;
	protected int[] temp;
	protected int fragcount;
	protected int fragwidth, fragheight;
	
	protected int[] nametable = new int[VectorElement.OBJECT_NAMES.length];
	
	public VectorImage(DataInputStream dis) throws IOException
	{
		read(dis);
	}
	
	public void read(DataInputStream dis) throws IOException
	{
		if(dis.readInt() != 0x4D564900)
		{
			throw new IOException("MVI signature not found");
		}
		
		version = dis.readUnsignedShort();
		
		if((version & 0xFF00) != (VERSION & 0xFF00))
		{
			throw new IOException("Incompatible version: 0x" + Integer.toHexString(version).toUpperCase() + " != 0x" + Integer.toHexString(VERSION).toUpperCase());
		}
//		else if(version != VERSION)
//		{
//			System.out.println("Warning: version 0x" + Integer.toHexString(VERSION).toUpperCase() + " recommended, but 0x" + Integer.toHexString(version).toUpperCase() + " found");
//		}
		
		title = dis.readUTF();
		description = dis.readUTF();
		
		nativewidth = dis.readInt();
		nativeheight = dis.readInt();
		
		param[0] = dis.readFloat();
		param[1] = dis.readFloat();
		param[4] = dis.readFloat();
		param[5] = dis.readFloat();
		
		usecolor = dis.readBoolean();
		useback = dis.readBoolean();
		backcolor = dis.readInt();
		
		maxframe = dis.readInt();
		loop = dis.readInt();
		setFPS(dis.readFloat());
		
		int i, n;
		
		n = dis.readInt();
		
		try
		{
			extra = new byte[n];
			dis.read(extra);
		}
		catch(OutOfMemoryError oome)
		{
			extra = new byte[0];
			dis.skip(n);
		}
		
		n = dis.readUnsignedShort();
		
		for(i = 0; i < n; i++)
		{
			if(i < nametable.length)
			{
				nametable[i] = dis.readUnsignedShort();
			}
			else
			{
				dis.readUnsignedShort();
			}
		}
		
		n = dis.readUnsignedShort();
		comments = new Vector(n);
		
		for(i = 0; i < n; i++)
		{
			comments.addElement(new Comment(dis));
		}
		
		count = dis.readUnsignedShort();
		groups = new Vector(count);
		
		Group grp;
		
		for(i = 0; i < count; i++)
		{
			grp = new Group(this, dis);
			groups.addElement(grp);
		}
		
		scale(-1, -1);
	}
	
	public void write(DataOutputStream dos) throws IOException
	{
		dos.writeInt(0x4D564900);
		
		dos.writeShort(VERSION);
		
		dos.writeUTF(title);
		dos.writeUTF(description);
		
		dos.writeInt(nativewidth);
		dos.writeInt(nativeheight);
		
		dos.writeFloat((float)param[0]);
		dos.writeFloat((float)param[1]);
		dos.writeFloat((float)param[4]);
		dos.writeFloat((float)param[5]);
		
		dos.writeBoolean(usecolor);
		dos.writeBoolean(useback);
		dos.writeInt(backcolor);
		
		dos.writeInt(maxframe);
		dos.writeInt(loop);
		dos.writeFloat((float)fps);
		
		int i;
		
		dos.writeInt(extra.length);
		dos.write(extra);
		
		dos.writeShort(nametable.length);
		
		for(i = 0; i < nametable.length; i++)
		{
			dos.writeShort(nametable[i]);
		}
		
		dos.writeShort(comments.size());
		
		for(i = 0; i < comments.size(); i++)
		{
			((Comment)comments.elementAt(i)).write(dos);
		}
		
		dos.writeShort(count);
		
		for(i = 0; i < count; i++)
		{
			((Group)groups.elementAt(i)).write(dos);
		}
		
		dos.writeShort(0);
	}
	
	public VectorImage()
	{
		version = VERSION;
		
		title = VectorElement.defaultName();
		description = title;
		
		nativewidth = 1000;
		nativeheight = 1000;
		
		param[0] = 0;
		param[1] = 0;
		param[2] = 1;
		param[3] = 1;
		param[4] = 1;
		param[5] = 1;
		
		usecolor = true;
		useback = true;
		backcolor = 0xFF000000;
		
		maxframe = 0;
		loop = 0;
		setFPS(0);
		
		comments = new Vector();
		extra = new byte[0];
		
		int i;
		
		for(i = 0; i < nametable.length; i++)
		{
			nametable[i] = 0;
		}
		
		groups = new Vector();
		count = 0;
	}
	
	public void writeSVG(PrintStream ps, String notice, ProgressCallback callback)
	{
		int prevwidth = width;
		int prevheight = height;
		int prevframe = frame;
		
		if(callback != null)
		{
			callback.setProgress(0);
			callback.setMax(maxframe + 1);
		}
		
		scale(-1, -1);
		
		ps.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
		
		if(notice != null)
		{
			ps.println("<!-- " + notice + " -->");
		}
		
		ps.print("<svg xmlns=\"http://www.w3.org/2000/svg\"");
		ps.print(" viewBox=\"0 0 " + Integer.toString(width) + " " + Integer.toString(height) + "\"");
		ps.println(">");
		
		ps.println("\t<title>" + title + "</title>");
		ps.println("\t<desc>" + description + "</desc>");
		
		ps.print("\t<rect");
		ps.print(" width=\"" + Integer.toString(width) + "\"");
		ps.print(" height=\"" + Integer.toString(height) + "\"");
		ps.print(" stroke=\"" + VectorElement.formatColor(backcolor) + "\"");
		ps.print(" fill=\"" + VectorElement.formatColor(backcolor) + "\"");
		ps.println("/>");
		
		String dur = Double.toString((double)(framedelay) / 1000f);
		Group grp;
		
		for(frame = 0; frame <= maxframe; frame++)
		{
			ps.println("\t<g visibility=\"hidden\">");
			
			ps.print("\t\t<set");
			ps.print(" attributeName=\"visibility\"");
			ps.print(" to=\"visible\"");
			ps.print(" begin=\"" + Double.toString((double)(frame * framedelay) / 1000f) + "\"");
			ps.print(" dur=\"" + dur + "\"");
			
			if(frame == maxframe)
			{
				ps.print(" fill=\"freeze\"");
			}
			
			ps.println("/>");
			
			for(int i = 0; i < count; i++)
			{
				grp = (Group)groups.elementAt(i);
				
				grp.update(true, false);
				grp.writeSVG(ps);
			}
			
			ps.println("\t</g>");
			
			if(callback != null)
			{
				callback.progress(1);
			}
		}
		
		ps.println("</svg>");
		
		gotoFrame(prevframe);
		scale(prevwidth, prevheight);
	}
	
	public void addGroup(Group grp)
	{
		grp.setName(nextGroupName());
		grp.setImage(this);
		grp.rescale();
		
		groups.addElement(grp);
		count = groups.size();
	}
	
	public void gotoFrame(int target)
	{
		if(target < 0)
		{
			target = 0;
		}
		else if(target > maxframe)
		{
			target = maxframe;
		}
		
		if(target > frame)
		{
			do
			{
				frame++;
				
				for(int i = 0; i < count; i++)
				{
					((Group)groups.elementAt(i)).update(false, false);
				}
			}
			while(frame < target);
		}
		else if(target < frame)
		{
			do
			{
				frame--;
				
				for(int i = 0; i < count; i++)
				{
					((Group)groups.elementAt(i)).update(false, false);
				}
			}
			while(frame > target);
		}
		
		for(int i = 0; i < count; i++)
		{
			((Group)groups.elementAt(i)).create();
			((Group)groups.elementAt(i)).rescale();
		}
	}
	
	public boolean nextFrame()
	{
		if(frame < maxframe)
		{
			frame++;
		}
		else if(loop != 0)
		{
			gotoFrame(0);
			
			if(loop > 0)
			{
				loop--;
			}
		}
		else
		{
			return false;
		}
		
		for(int i = 0; i < count; i++)
		{
			((Group)groups.elementAt(i)).update(true, false);
		}
		
		return true;
	}
	
	public boolean prevFrame()
	{
		if(frame > 0)
		{
			frame--;
		}
		else if(loop != 0)
		{
			gotoFrame(maxframe);
			
			if(loop > 0)
			{
				loop--;
			}
		}
		else
		{
			return false;
		}
		
		for(int i = 0; i < count; i++)
		{
			((Group)groups.elementAt(i)).update(true, false);
		}
		
		return true;
	}
	
	public int currentFrame()
	{
		return frame;
	}
	
	public void update()
	{
		for(int i = 0; i < count; i++)
		{
			((Group)groups.elementAt(i)).update(true, false);
		}
	}
	
	public void scale(int w, int h)
	{
		if(w < 0)
		{
			if(h < 0)
			{
				width = nativewidth;
				height = nativeheight;
			}
			else
			{
				width = nativewidth * h / nativeheight;
				height = h;
			}
		}
		else
		{
			if(h < 0)
			{
				width = w;
				height = nativeheight * w / nativewidth;
			}
			else
			{
				width = w;
				height = h;
			}
		}
		
		param[2] = (1.0f / param[4]) * width;
		param[3] = (1.0f / param[5]) * height;
		
		for(int i = 0; i < count; i++)
		{
			((Group)groups.elementAt(i)).rescale();
		}
		
		System.gc();
		
		try
		{
			offscreen = Image.createImage(width, height);
			offgraphics = offscreen.getGraphics();
			
			rgb = null;
			temp = null;
			fragcount = 1;
			
			do
			{
				fragwidth = width / fragcount;
				fragheight = height / fragcount;
				
				try
				{
					rgb = new int[fragwidth * fragheight];
					temp = new int[rgb.length];
				}
				catch(OutOfMemoryError oome)
				{
					rgb = null;
					temp = null;
					fragcount++;
				}
			}
			while(rgb == null || temp == null);
		}
		catch(OutOfMemoryError oome)
		{
			offscreen = null;
			offgraphics = null;
			rgb = null;
			temp = null;
		}
	}
	
	public void scaleToFit(int w, int h)
	{
		double nativeaspect = (double)nativewidth / (double)nativeheight;
		double aspect = (double)w / (double)h;
		
		if(nativeaspect > aspect)
		{
			scale(w, (int)(w / nativeaspect));
		}
		else
		{
			scale((int)(h * nativeaspect), h);
		}
	}
	
	public void setNativeSize(int nw, int nh)
	{
		nativewidth = nw;
		nativeheight = nh;
	}
	
	public int getWidth()
	{
		return width;
	}
	
	public int getHeight()
	{
		return height;
	}
	
	public int getNativeWidth()
	{
		return nativewidth;
	}
	
	public int getNativeHeight()
	{
		return nativeheight;
	}
	
	public void setUseColor(boolean usecolor)
	{
		this.usecolor = usecolor;
	}
	
	public void setUseBack(boolean useback)
	{
		this.useback = useback;
	}
	
	public boolean getUseColor()
	{
		return usecolor;
	}
	
	public boolean getUseBack()
	{
		return useback;
	}
	
	public void setBackColor(int backcolor)
	{
		this.backcolor = backcolor;
	}
	
	public int getBackColor()
	{
		return backcolor;
	}
	
	public void paint(Graphics g, int x, int y)
	{
		clip[0] = g.getClipX();
		clip[1] = g.getClipY();
		clip[2] = g.getClipWidth();
		clip[3] = g.getClipHeight();
		
		color = g.getColor();
		stroke = g.getStrokeStyle();
		
		g.clipRect(x, y, width, height);
		g.translate(x, y);
		
		if(usecolor && useback)
		{
			g.setColor(backcolor);
			g.fillRect(0, 0, width, height);
		}
		
		if(offgraphics != null)
		{
			Group grp;
			
			for(int i = 0; i < count; i++)
			{
				grp = (Group)groups.elementAt(i);
				
				if(grp.hasEffects() || grp.angle != 0)
				{
					offgraphics.translate(-offgraphics.getTranslateX(), -offgraphics.getTranslateY());
					offgraphics.setClip(0, 0, width, height);
					
					offgraphics.setColor(grp.getColor());
					offgraphics.fillRect(0, 0, width, height);
					
					offgraphics.translate(-grp.getTranslateX(), -grp.getTranslateY());
					grp.paint(offgraphics);
					
					Enumeration effects;
					int fx, fy;
					
					for(fx = 0; fx < fragcount; fx++)
					{
						for(fy = 0; fy < fragcount; fy++)
						{
							offscreen.getRGB(rgb, 0, fragwidth, fx * fragwidth, fy * fragheight, fragwidth, fragheight);
							
							if(grp.hasEffects())
							{
								effects = grp.listEffects();
								
								while(effects.hasMoreElements())
								{
									((Effect)effects.nextElement()).apply(rgb, temp, fragwidth, fragheight);
								}
							}
							
							if(grp.angle != 0)
							{
								AuxMath.rotateRGB(rgb, temp, fragwidth, fragheight, grp.angle);
								g.drawRGB(temp, 0, fragwidth, grp.getTranslateX() + fx * fragwidth, grp.getTranslateY() + fy * fragheight, fragwidth, fragheight, true);
							}
							else
							{
								g.drawRGB(rgb, 0, fragwidth, grp.getTranslateX() + fx * fragwidth, grp.getTranslateY() + fy * fragheight, fragwidth, fragheight, true);
							}
						}
					}
				}
				else
				{
					grp.paint(g);
				}
			}
		}
		else
		{
			for(int i = 0; i < count; i++)
			{
				((Group)groups.elementAt(i)).paint(g);
			}
		}
		
		g.translate(-x, -y);
		g.setClip(clip[0], clip[1], clip[2], clip[3]);
		
		g.setColor(color);
		g.setStrokeStyle(stroke);
	}
	
	public void setFPS(double fps)
	{
		this.fps = fps;
		
		if(fps <= 0)
		{
			framedelay = Integer.MAX_VALUE;
		}
		else if(fps <= 1000)
		{
			framedelay = (long)(1000f / fps);
		}
		else
		{
			framedelay = 1;
		}
	}
	
	public void setMaxFrame(int maxframe)
	{
		this.maxframe = maxframe;
	}
	
	public void setLoopCount(int loop)
	{
		this.loop = loop;
	}
	
	public double getFPS()
	{
		return fps;
	}
	
	public long getFrameDelay()
	{
		return framedelay;
	}
	
	public int getLoopCount()
	{
		return loop;
	}
	
	public int getMaxFrame()
	{
		return maxframe;
	}
	
	public int getVersion()
	{
		return version;
	}
	
	public String nextGroupName()
	{
		return VectorElement.OBJECT_NAMES[0] + " " + Integer.toString(++nametable[0]);
	}
	
	public String nextCommentName()
	{
		return VectorElement.OBJECT_NAMES[1] + " " + Integer.toString(++nametable[0]);
	}
	
	public String nextElementName(int type)
	{
		return VectorElement.getElementPrefix(type) + " " + Integer.toString(++nametable[type + VectorElement.OFFSET_ELEMENTS]);
	}
	
	public String currentElementName(int type)
	{
		return VectorElement.getElementPrefix(type) + " " + Integer.toString(nametable[type + VectorElement.OFFSET_ELEMENTS] + 1);
	}
	
	public String nextTransformName(int param, int subparam)
	{
		return VectorElement.getTransformPrefix(param, subparam) + " " + Integer.toString(++nametable[param + VectorElement.OFFSET_TRANSFORMS]);
	}
	
	public String currentTransformName(int param, int subparam)
	{
		return VectorElement.getTransformPrefix(param, subparam) + " " + Integer.toString(nametable[param + VectorElement.OFFSET_TRANSFORMS] + 1);
	}
	
	public String nextEffectName(int type)
	{
		return VectorElement.getEffectPrefix(type) + " " + Integer.toString(++nametable[type + VectorElement.OFFSET_EFFECTS]);
	}
	
	public Enumeration listGroups()
	{
		return groups.elements();
	}
	
	public Group getGroup(String name)
	{
		Group grp;
		
		for(int i = 0; i < count; i++)
		{
			grp = (Group)groups.elementAt(i);
			
			if(grp.getName().equals(name))
			{
				return grp;
			}
		}
		
		return null;
	}
	
	public void deleteGroup(Group grp)
	{
		groups.removeElement(grp);
		count = groups.size();
	}
	
	public void arrangeGroups(int index, int newindex)
	{
		if(index < 0)
		{
			index = 0;
		}
		else if(index >= groups.size())
		{
			index = groups.size() - 1;
		}
		
		if(newindex < 0)
		{
			newindex = 0;
		}
		else if(newindex >= groups.size())
		{
			newindex = groups.size() - 1;
		}
		
		Object temp = groups.elementAt(newindex);
		groups.setElementAt(groups.elementAt(index), newindex);
		groups.setElementAt(temp, index);
	}
	
	public void setTitle(String ttl)
	{
		if(ttl != null)
		{
			title = ttl;
		}
		else
		{
			title = "";
		}
	}
	
	public void setDescription(String desc)
	{
		if(desc != null)
		{
			description = desc;
		}
		else
		{
			description = "";
		}
	}
	
	public String getTitle()
	{
		return title;
	}
	
	public String getDescription()
	{
		return description;
	}
	
	public void setExtra(byte[] b)
	{
		if(b != null)
		{
			extra = b;
		}
		else
		{
			extra = new byte[0];
		}
	}
	
	public byte[] getExtra()
	{
		return extra;
	}
	
	public void addComment(Comment comment)
	{
		comments.addElement(comment);
	}
	
	public Enumeration listComments()
	{
		return comments.elements();
	}
	
	public Comment getComment(String name)
	{
		Comment comment;
		
		for(int i = 0; i < count; i++)
		{
			comment = (Comment)comments.elementAt(i);
			
			if(comment.getName().equals(name))
			{
				return comment;
			}
		}
		
		return null;
	}
	
	public void deleteComment(Comment comment)
	{
		comments.removeElement(comment);
	}
}