using System.Drawing;
using System.IO;
using System.Drawing.Imaging;
using System.Collections;

namespace Icarus.Graphics.Animation
{
    public class PazeraFramesetProvider 
		: 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.X < 0) { size.Width = size.Width - anch.X; }
				else if (anch.X >= size.Width) { size.Width = anch.X + 1; }

				if (anch.Y < 0) { size.Height = size.Height - anch.Y; }
				else if (anch.Y >= size.Height) { size.Height = anch.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].Width) 
					{
						w = frameSizes[j,i].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].Height) 
					{
						h = frameSizes[i,j].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].Right; }

				int y = 1;
				if (row > 0) { y += frameRectangles[i - width].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);
			System.Drawing.Graphics g = System.Drawing.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].X;
				int y = frameRectangles[i].Y;

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

				if (anchor.Y < 0) 
				{
					starty -= anchor.Y;
				} 
				else 
				{
					anchy += anchor.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.Width; j++) { output.SetPixel(j, y - 1, colorInside); }
				for (int j = startx + img.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.Height; j++) { output.SetPixel(x - 1, j, colorInside); }
				for (int j = starty + img.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.Right + 1, previousRect.Top, columnWidths[i % height], previousRect.Height);
				g.FillRectangle(outsideBrush, curr.X - 1, curr.Y, 1, curr.Height);
				g.FillRectangle(outsideBrush, curr.Left, curr.Y - 1, curr.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.X; j < cr.Right; j++) { output.SetPixel(j, imageHeight - 1, colorOutside); }
			}
			for (int i = 0; i < height; i++) 
			{
				Rectangle cr = frameRectangles[i * width];
				for (int j = cr.Y; j < cr.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.Bmp;
			if (extension.Equals("gif", System.StringComparison.CurrentCultureIgnoreCase)) { fmt = ImageFormat.Gif; }
			else if (extension.Equals("jpg", System.StringComparison.CurrentCultureIgnoreCase) || extension.Equals("jpeg", System.StringComparison.CurrentCultureIgnoreCase)) { fmt = ImageFormat.Jpeg; }
			else if (extension.Equals("png", System.StringComparison.CurrentCultureIgnoreCase)) { fmt = ImageFormat.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.
			var v_bounds = findVerticalBounds(source, colors);
			var h_bounds = findHorizontalBounds(source, colors);

			//Make rectangles that denote the bounds:
			var rectangles = boundsToRectangles(h_bounds, v_bounds);
			var i = rectangles.GetEnumerator();
			while (i.MoveNext()) 
			{
				Rectangle r = (Rectangle) i.Current;
				Rectangle realBounds = findRealBounds(source, colors, r);
				
				if (realBounds.Width < 1 || realBounds.Height < 1) { continue; }

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

				//Adjust anchor coordinates to the current frame reference:
				anchor.X = anchor.X - realBounds.X;
				anchor.Y = anchor.Y - realBounds.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.Width;
			int index = 0;

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

			return result;
		}

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

			int height = source.Height;
			int index = 0;

			while (index < height) 
			{
				index = firstVerticalInstance(source, colors.bound, index, height, 0);
				result.Add(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.Count - 1; row ++) 
			{
				for (int col = 0; col < vertical.Count - 1; col ++) 
				{
					int left = (int) vertical[col];
					int right = (int) vertical[col + 1];
					int top = (int)horizontal[row];
					int bottom = (int)horizontal[row + 1];

					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.Left, reference.Right, reference.Top);
			int y = firstVerticalInstance(source, colors.anchor, reference.Top, reference.Bottom, reference.Left);

			return new Point(x, y);
		}

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

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

			//Render:
			g.DrawImage(
				source,
				new Rectangle(0, 0, reference.Width, reference.Height),
				reference,
				GraphicsUnit.Pixel
			);
	//		g.DrawImage(source, -reference.X, -reference.Y, reference.Width, reference.Height);
	//			DrawImage(source, -reference.X, -reference.Y);

			return result;
		}

		private Rectangle findRealBounds(Bitmap source, ControlColors colors, Rectangle reference) 
		{
			int left = firstHorizontalInstance(source, colors.inside, reference.Left, reference.Right, reference.Top);
			int top = firstVerticalInstance(source, colors.inside, reference.Top, reference.Bottom, reference.Left);
			int right = firstHorizontalInstance(source, colors.outside, left, reference.Right, reference.Top);
			int bottom = firstVerticalInstance(source, colors.outside, top, reference.Bottom, reference.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.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);
			}
		}
	}
}
