package Icarus.Graphics.Animation;

import System.Drawing.*;
import System.Drawing.Imaging.*;
import System.Collections.*;

/**
 * The Pazera frameset provider implements the plain-image frameset format described by Ernest Pazera in the following article originally posted on Gamedev.net: http://www.gamedev.net/reference/articles/article1122.asp
 * 
 * @author $Author: asskoala $
 * @version $Revision: 1.1 $ $Date: 2005/12/11 21:07:21 $
 */
public class PazeraFramesetProvider implements IFramesetProvider
{

	/**
	 * Exports the frameset.
	 */
	public void exportFrameset(Frameset frameset, String filename) 
	{
		//First, we want to figure out the dimensions of the image in frames:
		int frameCount = frameset.getFrameCount();
		double dimension = System.Math.Sqrt(frameCount);
		int width = (int) System.Math.Ceiling(dimension);
		int height = (int) System.Math.Ceiling(((double) frameCount) / ((double) width));

		//Generate a matrix of frames to output:
		IFrame[][] frameMatrix = new IFrame[height][width];
		for (int i = 0; i < frameCount; i++) 
		{
			frameMatrix[i / width][i % width] = frameset.getFrameAt(i);
		}
		for (int i = frameCount; i < width * height; i++) 
		{
			frameMatrix[i / width][i % width] = null;
		}

		//Now, precalculate the dimensions of each frame, taking the anchor point into consideration:
		Size[][] frameSizes = new Size[height][width];
		for (int i = 0; i < frameCount; i++) 
		{
			//Get the frame information:
			Point anch = frameset.getFrameAt(i).getAnchorPoint();
			Size size = frameset.getFrameAt(i).GetSize();

			//Adjust the size by the anchor point:
			if (anch.get_X() < 0) { size.set_Width(size.get_Width() - anch.get_X()); }
			else if (anch.get_X() >= size.get_Width()) { size.set_Width(anch.get_X() + 1); }

			if (anch.get_Y() < 0) { size.set_Height(size.get_Height() - anch.get_Y()); }
			else if (anch.get_Y() >= size.get_Height()) { size.set_Height(anch.get_Y() + 1); }

			frameSizes[i / width][i % width] = size;
		}
		for (int i = frameCount; i < width * height; i++) 
		{
			frameSizes[i / width][i % width] = new Size(0, 0);
		}

		//Compute the widths of each column:
		int[] columnWidths = new int[width];
		for (int i = 0; i < width; i++) 
		{
			int w = 0;

			for (int j = 0; j < height; j++) 
			{
				if (w < frameSizes[j][i].get_Width()) 
				{
					w = frameSizes[j][i].get_Width();
				}
			}

			columnWidths[i] = w;
		}

		//Compute the heights of each row:
		int[] rowHeights = new int[height];
		for (int i = 0; i < height; i++) 
		{
			int h = 0;

			for (int j = 0; j < width; j++) 
			{
				if (h < frameSizes[i][j].get_Height()) 
				{
					h = frameSizes[i][j].get_Height();
				}
			}

			rowHeights[i] = h;
		}

		//Compute the total dimension of the image:
		int imageWidth = 1;
		for (int i = 0; i < width; i++) { imageWidth += columnWidths[i] + 1; }

		int imageHeight = 1;
		for (int i = 0; i < height; i++) { imageHeight += rowHeights[i] + 1; }

		//Generate the rectangles corresponding to each frame:
		Rectangle[] frameRectangles = new Rectangle[frameCount];
		for (int i = 0; i < frameCount; i++) 
		{
			int row = i / width;
			int col = i % width;

			int x = 1;
			if (col > 0) { x += frameRectangles[i - 1].get_Right(); }

			int y = 1;
			if (row > 0) { y += frameRectangles[i - width].get_Bottom(); }

			frameRectangles[i] = new Rectangle(x, y, columnWidths[col], rowHeights[row]);
		}

		//Sahweet, now we can start making the image:
		Bitmap output = new Bitmap(imageWidth, imageHeight);
		Graphics g = Graphics.FromImage(output);

		//Set up control colors:
		Color colorBound = Color.FromArgb(255, 0, 255);
		Color colorInside = Color.FromArgb(0, 255, 0);
		Color colorAnchor = Color.FromArgb(0, 255, 255);
		Color colorOutside = Color.FromArgb(255, 255, 255);

		//Paint background with the bounds color:
		Brush outsideBrush = new SolidBrush(colorOutside);
		Brush boundBrush = new SolidBrush(colorBound);
		g.FillRectangle(boundBrush, 0, 0, imageWidth, imageHeight);

		//Iterate through the rectangles and paint images and their control patterns:
		for (int i = 0; i < frameCount; i++) 
		{
			IFrame cf = frameset.getFrameAt(i);
			Point anchor = cf.getAnchorPoint();
			Bitmap img = cf.getFrameImage();

			int x = frameRectangles[i].get_X();
			int y = frameRectangles[i].get_Y();

			int startx = x;
			int starty = y;
			int anchx = x;
			int anchy = y;
			
			if (anchor.get_X() < 0)
			{
				startx -= anchor.get_X();
			} 
			else 
			{
				anchx += anchor.get_X();
			}

			if (anchor.get_Y() < 0) 
			{
				starty -= anchor.get_Y();
			} 
			else 
			{
				anchy += anchor.get_Y();
			}

			//Paint inside / outside:
			//Horizontal:
			for (int j = x; j < startx; j++) { output.SetPixel(j, y - 1, colorOutside); }
			for (int j = startx; j < startx + img.get_Width(); j++) { output.SetPixel(j, y - 1, colorInside); }
			for (int j = startx + img.get_Width(); j < x + columnWidths[i % width]; j++) { output.SetPixel(j, y - 1, colorOutside); }

			//Vertical:
			for (int j = y; j < starty; j++) { output.SetPixel(x - 1, j, colorOutside); }
			for (int j = starty; j < starty + img.get_Height(); j++) { output.SetPixel(x - 1, j, colorInside); }
			for (int j = starty + img.get_Height(); j < y + rowHeights[i / width]; j++) { output.SetPixel(x - 1, j, colorOutside); }

			//Paint anchor points:
			output.SetPixel(anchx, y - 1, colorAnchor);
			output.SetPixel(x - 1, anchy, colorAnchor);

			//Render actual image:
			g.DrawImage(img, startx, starty);
		}

		//Render the remaining, unfilled frames:
		Rectangle previousRect = frameRectangles[frameCount - 1];
		for (int i = frameCount; i < width * height; i++) 
		{
			Rectangle curr = new Rectangle(previousRect.get_Right() + 1, previousRect.get_Top(), columnWidths[i % height], previousRect.get_Height());
			g.FillRectangle(outsideBrush, curr.get_X() - 1, curr.get_Y(), 1, curr.get_Height());
			g.FillRectangle(outsideBrush, curr.get_Left(), curr.get_Y() - 1, curr.get_Width(), 1);
		}

		//Now we also need to render right-most blank lines:
		for (int i = 0; i < width; i++) 
		{
			Rectangle cr = frameRectangles[i];
			for (int j = cr.get_X(); j < cr.get_Right(); j++) { output.SetPixel(j, imageHeight - 1, colorOutside); }
		}
		for (int i = 0; i < height; i++) 
		{
			Rectangle cr = frameRectangles[i * width];
			for (int j = cr.get_Y(); j < cr.get_Bottom(); j++) { output.SetPixel(imageWidth - 1, j, colorOutside); }
		}

		//Also, render the control structure:
		output.SetPixel(imageWidth - 1, 1, colorInside);
		output.SetPixel(imageWidth - 1, 2, colorAnchor);

		//Remove ugly pink:
		//output.MakeTransparent(colorBound);

		//Save the image:
		String[] fileparts = filename.Split(new char[] {'.'});
		String extension = fileparts[fileparts.length - 1];
		ImageFormat fmt = ImageFormat.get_Bmp();
		if (extension.equalsIgnoreCase("gif")) { fmt = ImageFormat.get_Gif(); }
		else if (extension.equalsIgnoreCase("jpg") || extension.equalsIgnoreCase("jpeg")) { fmt = ImageFormat.get_Jpeg(); }
		else if (extension.equalsIgnoreCase("png")) { fmt = ImageFormat.get_Png(); }

		output.Save(filename, fmt);
	}

	/**
	 * Goes through the image and extracts frames from it.
	 */
	public void fillFrameset(Frameset frameSet, Bitmap source) 
	{
		//Initialize the control colors:
		ControlColors colors = new ControlColors(source);

		//Find frame bounds.
		IList v_bounds = findVerticalBounds(source, colors);
		IList h_bounds = findHorizontalBounds(source, colors);

		//Make rectangles that denote the bounds:
		IList rectangles = boundsToRectangles(h_bounds, v_bounds);
		IEnumerator i = rectangles.GetEnumerator();
		while (i.MoveNext()) 
		{
			Rectangle r = (Rectangle) i.get_Current();
			Rectangle realBounds = findRealBounds(source, colors, r);
			//if (realBounds.get_IsEmpty()) { continue; }
            if (realBounds.get_Width() < 1 || realBounds.get_Height() < 1) { continue; }

			Point anchor = findAnchor(source, colors, r);

			//Adjust anchor coordinates to the current frame reference:
			anchor.set_X(anchor.get_X() - realBounds.get_X());
			anchor.set_Y(anchor.get_Y() - realBounds.get_Y());

			Bitmap frame = extractFrame(source, colors, realBounds);
            frame.MakeTransparent(colors.bound);

			frameSet.addFrame(frame, anchor);
		}
	}

	private IList findVerticalBounds(Bitmap source, ControlColors colors) 
	{
		IList result = new ArrayList();

		int width = source.get_Width();
		int index = 0;

		while (index < width) 
		{
			index = firstHorizontalInstance(source, colors.bound, index, width, 0);
			result.Add(new Integer(index));
			index++;
		}

		return result;
	}

	private IList findHorizontalBounds(Bitmap source, ControlColors colors) 
	{
		IList result = new ArrayList();

		int height = source.get_Height();
		int index = 0;

		while (index < height) 
		{
			index = firstVerticalInstance(source, colors.bound, index, height, 0);
			result.Add(new Integer(index));
			index++;
		}

		return result;
	}

	/**
	 * Goes through the y-th row of the image, looking at x from start to stop, exclusive, and returns the first instance of color c, or stop if there is no such color in the row in the range.
	 */
	private int firstHorizontalInstance(Bitmap source, Color c, int start, int stop, int y) 
	{
		int index = 0;
		for (index = start; index < stop; index++) 
		{
			if (source.GetPixel(index, y).Equals(c)) 
			{
				break;
			}
		}

		return index;
	}

	/**
	 * Goes through the x-th column of the image, looking at y from start to stop, exclusive, and returns the first instance of color c, or stop if there is no such color in the column in the range.
	 */
	private int firstVerticalInstance(Bitmap source, Color c, int start, int stop, int x) 
	{
		int index = 0;
		for (index = start; index < stop; index++) 
		{
			if (source.GetPixel(x, index).Equals(c)) 
			{
				break;
			}
		}

		return index;
	}

	private IList boundsToRectangles(IList horizontal, IList vertical) 
	{
		IList result = new ArrayList();

        for (int row = 0; row < horizontal.get_Count() - 1; row ++) 
        {
            for (int col = 0; col < vertical.get_Count() - 1; col ++) 
            {
                int left = ((Integer) vertical.get_Item(col)).intValue();
                int right = ((Integer) vertical.get_Item(col + 1)).intValue();
                int top = ((Integer) horizontal.get_Item(row)).intValue();
                int bottom = ((Integer) horizontal.get_Item(row + 1)).intValue();

                result.Add(new Rectangle(left, top, right - left, bottom - top));
            }
        }

		return result;
	}

	private Point findAnchor(Bitmap source, ControlColors colors, Rectangle reference) 
	{
		int x = firstHorizontalInstance(source, colors.anchor, reference.get_Left(), reference.get_Right(), reference.get_Top());
		int y = firstVerticalInstance(source, colors.anchor, reference.get_Top(), reference.get_Bottom(), reference.get_Left());

		return new Point(x, y);
	}

	private Bitmap extractFrame(Bitmap source, ControlColors colors, Rectangle reference) 
	{
		//Make new result image:
		Bitmap result = new Bitmap(reference.get_Width(), reference.get_Height());

		//Make graphics so we can render onto the image:
		Graphics g = Graphics.FromImage(result);

		//Render:
		g.DrawImage(
			source,
			new Rectangle(0, 0, reference.get_Width(), reference.get_Height()),
			reference,
			GraphicsUnit.Pixel
		);
//		g.DrawImage(source, -reference.get_X(), -reference.get_Y(), reference.get_Width(), reference.get_Height());
//			DrawImage(source, -reference.get_X(), -reference.get_Y());

		return result;
	}

	private Rectangle findRealBounds(Bitmap source, ControlColors colors, Rectangle reference) 
	{
		int left = firstHorizontalInstance(source, colors.inside, reference.get_Left(), reference.get_Right(), reference.get_Top());
		int top = firstVerticalInstance(source, colors.inside, reference.get_Top(), reference.get_Bottom(), reference.get_Left());
		int right = firstHorizontalInstance(source, colors.outside, left, reference.get_Right(), reference.get_Top());
		int bottom = firstVerticalInstance(source, colors.outside, top, reference.get_Bottom(), reference.get_Left());

		return new Rectangle(left, top, right - left, bottom - top);
	}

	/**
	 * This is just a little struct-like thing to simplify passing around the control colors.
	 */
	private class ControlColors 
	{
		public Color bound;
		public Color inside;
		public Color anchor;
		public Color outside;

		public ControlColors(Bitmap img) 
		{
			int width = img.get_Width();

			bound = img.GetPixel(width - 1, 0);
			inside = img.GetPixel(width - 1, 1);
			anchor = img.GetPixel(width - 1, 2);
			outside = img.GetPixel(width - 1, 3);
		}
	}
}
