#region References
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
#endregion

//using System.Windows.Media.Imaging;

namespace Ultima
{
	public sealed class AnimationEdit
	{
		private static FileIndex m_FileIndex = new FileIndex("Anim.idx", "Anim.mul", 6);
		private static FileIndex m_FileIndex2 = new FileIndex("Anim2.idx", "Anim2.mul", -1);
		private static FileIndex m_FileIndex3 = new FileIndex("Anim3.idx", "Anim3.mul", -1);
		private static FileIndex m_FileIndex4 = new FileIndex("Anim4.idx", "Anim4.mul", -1);
		private static FileIndex m_FileIndex5 = new FileIndex("Anim5.idx", "Anim5.mul", -1);

		private static AnimIdx[] animcache;
		private static readonly AnimIdx[] animcache2;
		private static readonly AnimIdx[] animcache3;
		private static readonly AnimIdx[] animcache4;
		private static readonly AnimIdx[] animcache5;

		static AnimationEdit()
		{
			if (m_FileIndex.IdxLength > 0)
			{
				animcache = new AnimIdx[m_FileIndex.IdxLength / 12];
			}
			if (m_FileIndex2.IdxLength > 0)
			{
				animcache2 = new AnimIdx[m_FileIndex2.IdxLength / 12];
			}
			if (m_FileIndex3.IdxLength > 0)
			{
				animcache3 = new AnimIdx[m_FileIndex3.IdxLength / 12];
			}
			if (m_FileIndex4.IdxLength > 0)
			{
				animcache4 = new AnimIdx[m_FileIndex4.IdxLength / 12];
			}
			if (m_FileIndex5.IdxLength > 0)
			{
				animcache5 = new AnimIdx[m_FileIndex5.IdxLength / 12];
			}
		}

		/// <summary>
		///     Rereads AnimX files
		/// </summary>
		public static void Reload()
		{
			m_FileIndex = new FileIndex("Anim.idx", "Anim.mul", 6);
			m_FileIndex2 = new FileIndex("Anim2.idx", "Anim2.mul", -1);
			m_FileIndex3 = new FileIndex("Anim3.idx", "Anim3.mul", -1);
			m_FileIndex4 = new FileIndex("Anim4.idx", "Anim4.mul", -1);
			m_FileIndex5 = new FileIndex("Anim5.idx", "Anim5.mul", -1);
			if (m_FileIndex.IdxLength > 0)
			{
				animcache = new AnimIdx[m_FileIndex.IdxLength / 12];
			}
			if (m_FileIndex2.IdxLength > 0)
			{
				animcache = new AnimIdx[m_FileIndex2.IdxLength / 12];
			}
			if (m_FileIndex3.IdxLength > 0)
			{
				animcache = new AnimIdx[m_FileIndex3.IdxLength / 12];
			}
			if (m_FileIndex4.IdxLength > 0)
			{
				animcache = new AnimIdx[m_FileIndex4.IdxLength / 12];
			}
			if (m_FileIndex5.IdxLength > 0)
			{
				animcache = new AnimIdx[m_FileIndex5.IdxLength / 12];
			}
		}

		private static void GetFileIndex(
			int body, int fileType, int action, int direction, out FileIndex fileIndex, out int index)
		{
			switch (fileType)
			{
				default:
				case 1:
					fileIndex = m_FileIndex;
					if (body < 200)
					{
						index = body * 110;
					}
					else if (body < 400)
					{
						index = 22000 + ((body - 200) * 65);
					}
					else
					{
						index = 35000 + ((body - 400) * 175);
					}
					break;
				case 2:
					fileIndex = m_FileIndex2;
					if (body < 200)
					{
						index = body * 110;
					}
					else
					{
						index = 22000 + ((body - 200) * 65);
					}
					break;
				case 3:
					fileIndex = m_FileIndex3;
					if (body < 300)
					{
						index = body * 65;
					}
					else if (body < 400)
					{
						index = 33000 + ((body - 300) * 110);
					}
					else
					{
						index = 35000 + ((body - 400) * 175);
					}
					break;
				case 4:
					fileIndex = m_FileIndex4;
					if (body < 200)
					{
						index = body * 110;
					}
					else if (body < 400)
					{
						index = 22000 + ((body - 200) * 65);
					}
					else
					{
						index = 35000 + ((body - 400) * 175);
					}
					break;
				case 5:
					fileIndex = m_FileIndex5;
					if ((body < 200) && (body != 34)) // looks strange, though it works.
					{
						index = body * 110;
					}
					else if (body < 400)
					{
						index = 22000 + ((body - 200) * 65);
					}
					else
					{
						index = 35000 + ((body - 400) * 175);
					}
					break;
			}

			index += action * 5;

			if (direction <= 4)
			{
				index += direction;
			}
			else
			{
				index += direction - (direction - 4) * 2;
			}
		}

		private static AnimIdx[] GetCache(int filetype)
		{
			switch (filetype)
			{
				case 1:
					return animcache;
				case 2:
					return animcache2;
				case 3:
					return animcache3;
				case 4:
					return animcache4;
				case 5:
					return animcache5;
				default:
					return animcache;
			}
		}

		public static AnimIdx GetAnimation(int filetype, int body, int action, int dir)
		{
			AnimIdx[] cache = GetCache(filetype);
			FileIndex fileIndex;
			int index;
			GetFileIndex(body, filetype, action, dir, out fileIndex, out index);

			if (cache != null)
			{
				if (cache[index] != null)
				{
					return cache[index];
				}
			}
			return cache[index] = new AnimIdx(index, fileIndex, filetype);
		}

		public static bool IsActionDefinied(int filetype, int body, int action)
		{
			AnimIdx[] cache = GetCache(filetype);
			FileIndex fileIndex;
			int index;
			GetFileIndex(body, filetype, action, 0, out fileIndex, out index);

			if (cache != null)
			{
				if (cache[index] != null)
				{
					if ((cache[index].Frames != null) && (cache[index].Frames.Count > 0))
					{
						return true;
					}
					else
					{
						return false;
					}
				}
			}

			int AnimCount = Animations.GetAnimLength(body, filetype);
			if (AnimCount < action)
			{
				return false;
			}

			int length, extra;
			bool patched;
			bool valid = fileIndex.Valid(index, out length, out extra, out patched);
			if ((!valid) || (length < 1))
			{
				return false;
			}
			return true;
		}

		public static void LoadFromVD(int filetype, int body, BinaryReader bin)
		{
			AnimIdx[] cache = GetCache(filetype);
			FileIndex fileIndex;
			int index;
			GetFileIndex(body, filetype, 0, 0, out fileIndex, out index);
			int animlength = Animations.GetAnimLength(body, filetype) * 5;
			var entries = new Entry3D[animlength];

			for (int i = 0; i < animlength; ++i)
			{
				entries[i].lookup = bin.ReadInt32();
				entries[i].length = bin.ReadInt32();
				entries[i].extra = bin.ReadInt32();
			}
			foreach (Entry3D entry in entries)
			{
				if ((entry.lookup > 0) && (entry.lookup < bin.BaseStream.Length) && (entry.length > 0))
				{
					bin.BaseStream.Seek(entry.lookup, SeekOrigin.Begin);
					cache[index] = new AnimIdx(bin, entry.extra);
				}
				++index;
			}
		}

		public static void ExportToVD(int filetype, int body, string file)
		{
			AnimIdx[] cache = GetCache(filetype);
			FileIndex fileIndex;
			int index;
			GetFileIndex(body, filetype, 0, 0, out fileIndex, out index);
			using (var fs = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Write))
			{
				using (var bin = new BinaryWriter(fs))
				{
					bin.Write((short)6);
					int animlength = Animations.GetAnimLength(body, filetype);
					int currtype = animlength == 22 ? 0 : animlength == 13 ? 1 : 2;
					bin.Write((short)currtype);
					long indexpos = bin.BaseStream.Position;
					long animpos = bin.BaseStream.Position + 12 * animlength * 5;
					for (int i = index; i < index + animlength * 5; i++)
					{
						AnimIdx anim;
						if (cache != null)
						{
							if (cache[i] != null)
							{
								anim = cache[i];
							}
							else
							{
								anim = cache[i] = new AnimIdx(i, fileIndex, filetype);
							}
						}
						else
						{
							anim = cache[i] = new AnimIdx(i, fileIndex, filetype);
						}

						if (anim == null)
						{
							bin.BaseStream.Seek(indexpos, SeekOrigin.Begin);
							bin.Write(-1);
							bin.Write(-1);
							bin.Write(-1);
							indexpos = bin.BaseStream.Position;
						}
						else
						{
							anim.ExportToVD(bin, ref indexpos, ref animpos);
						}
					}
				}
			}
		}

		public static void Save(int filetype, string path)
		{
			string filename;
			AnimIdx[] cache;
			FileIndex fileindex;
			switch (filetype)
			{
				case 1:
					filename = "anim";
					cache = animcache;
					fileindex = m_FileIndex;
					break;
				case 2:
					filename = "anim2";
					cache = animcache2;
					fileindex = m_FileIndex2;
					break;
				case 3:
					filename = "anim3";
					cache = animcache3;
					fileindex = m_FileIndex3;
					break;
				case 4:
					filename = "anim4";
					cache = animcache4;
					fileindex = m_FileIndex4;
					break;
				case 5:
					filename = "anim5";
					cache = animcache5;
					fileindex = m_FileIndex5;
					break;
				default:
					filename = "anim";
					cache = animcache;
					fileindex = m_FileIndex;
					break;
			}
			string idx = Path.Combine(path, filename + ".idx");
			string mul = Path.Combine(path, filename + ".mul");
			using (
				FileStream fsidx = new FileStream(idx, FileMode.Create, FileAccess.Write, FileShare.Write),
						   fsmul = new FileStream(mul, FileMode.Create, FileAccess.Write, FileShare.Write))
			{
				using (BinaryWriter binidx = new BinaryWriter(fsidx), binmul = new BinaryWriter(fsmul))
				{
					for (int idxc = 0; idxc < cache.Length; ++idxc)
					{
						AnimIdx anim;
						if (cache != null)
						{
							if (cache[idxc] != null)
							{
								anim = cache[idxc];
							}
							else
							{
								anim = cache[idxc] = new AnimIdx(idxc, fileindex, filetype);
							}
						}
						else
						{
							anim = cache[idxc] = new AnimIdx(idxc, fileindex, filetype);
						}

						if (anim == null)
						{
							binidx.Write(-1);
							binidx.Write(-1);
							binidx.Write(-1);
						}
						else
						{
							anim.Save(binmul, binidx);
						}
					}
				}
			}
		}
	}

	public sealed class AnimIdx
	{
		public int idxextra;
		public ushort[] Palette { get; private set; }
		public List<FrameEdit> Frames { get; private set; }

		public AnimIdx(int index, FileIndex fileIndex, int filetype)
		{
			Palette = new ushort[0x100];
			int length, extra;
			bool patched;
			Stream stream = fileIndex.Seek(index, out length, out extra, out patched);
			if ((stream == null) || (length < 1))
			{
				return;
			}

			idxextra = extra;
			using (var bin = new BinaryReader(stream))
			{
				for (int i = 0; i < 0x100; ++i)
				{
					Palette[i] = (ushort)(bin.ReadUInt16() ^ 0x8000);
				}

				var start = (int)bin.BaseStream.Position;
				int frameCount = bin.ReadInt32();

				var lookups = new int[frameCount];

				for (int i = 0; i < frameCount; ++i)
				{
					lookups[i] = start + bin.ReadInt32();
				}

				Frames = new List<FrameEdit>();

				for (int i = 0; i < frameCount; ++i)
				{
					stream.Seek(lookups[i], SeekOrigin.Begin);
					Frames.Add(new FrameEdit(bin));
				}
			}
			stream.Close();
		}

		public AnimIdx(BinaryReader bin, int extra)
		{
			Palette = new ushort[0x100];
			idxextra = extra;
			for (int i = 0; i < 0x100; ++i)
			{
				Palette[i] = (ushort)(bin.ReadUInt16() ^ 0x8000);
			}

			var start = (int)bin.BaseStream.Position;
			int frameCount = bin.ReadInt32();

			var lookups = new int[frameCount];

			for (int i = 0; i < frameCount; ++i)
			{
				lookups[i] = start + bin.ReadInt32();
			}

			Frames = new List<FrameEdit>();

			for (int i = 0; i < frameCount; ++i)
			{
				bin.BaseStream.Seek(lookups[i], SeekOrigin.Begin);
				Frames.Add(new FrameEdit(bin));
			}
		}

		public unsafe Bitmap[] GetFrames()
		{
			if ((Frames == null) || (Frames.Count == 0))
			{
				return null;
			}
			var bits = new Bitmap[Frames.Count];
			for (int i = 0; i < bits.Length; ++i)
			{
				FrameEdit frame = Frames[i];
				int width = frame.width;
				int height = frame.height;
				if (height == 0 || width == 0)
				{
					continue;
				}
				var bmp = new Bitmap(width, height, Settings.PixelFormat);
				BitmapData bd = bmp.LockBits(
					new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, Settings.PixelFormat);
				var line = (ushort*)bd.Scan0;
				int delta = bd.Stride >> 1;

				int xBase = frame.Center.X - 0x200;
				int yBase = frame.Center.Y + height - 0x200;

				line += xBase;
				line += yBase * delta;
				for (int j = 0; j < frame.RawData.Length; ++j)
				{
					FrameEdit.Raw raw = frame.RawData[j];

					ushort* cur = line + (((raw.offy) * delta) + ((raw.offx) & 0x3FF));
					ushort* end = cur + (raw.run);

					int ii = 0;
					while (cur < end)
					{
						*cur++ = Palette[raw.data[ii++]];
					}
				}
				bmp.UnlockBits(bd);
				bits[i] = bmp;
			}
			return bits;
		}

		public void AddFrame(Bitmap bit)
		{
			if (Frames == null)
			{
				Frames = new List<FrameEdit>();
			}
			Frames.Add(new FrameEdit(bit, Palette, 0, 0));
		}

		public void ReplaceFrame(Bitmap bit, int index)
		{
			if ((Frames == null) || (Frames.Count == 0))
			{
				return;
			}
			if (index > Frames.Count)
			{
				return;
			}
			Frames[index] = new FrameEdit(bit, Palette, (Frames[index]).Center.X, (Frames[index]).Center.Y);
		}

		public void RemoveFrame(int index)
		{
			if (Frames == null)
			{
				return;
			}
			if (index > Frames.Count)
			{
				return;
			}
			Frames.RemoveAt(index);
		}

		public void ClearFrames()
		{
			if (Frames == null)
			{
				return;
			}
			Frames.Clear();
		}

#if false
	//Soulblighter Modification
		public void GetGifPalette(Bitmap bit)
		{
			using (MemoryStream imageStreamSource = new MemoryStream())
			{
				System.Drawing.ImageConverter ic = new System.Drawing.ImageConverter();
				byte[] btImage = (byte[])ic.ConvertTo(bit, typeof(byte[]));
				imageStreamSource.Write(btImage, 0, btImage.Length);
				GifBitmapDecoder decoder = new GifBitmapDecoder(imageStreamSource, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
				BitmapPalette pal = decoder.Palette;
				int i;
				for (i = 0; i < 0x100; i++)
				{
					this.Palette[i] = 0;
				}
				try
				{
					i = 0;
					while (i < 0x100)//&& i < pal.Colors.Count)
					{

						int Red = pal.Colors[i].R / 8;
						int Green = pal.Colors[i].G / 8;
						int Blue = pal.Colors[i].B / 8;
						int contaFinal = (((0x400 * Red) + (0x20 * Green)) + Blue) + 0x8000;
						if (contaFinal == 0x8000)
							contaFinal = 0x8001;
						this.Palette[i] = (ushort)contaFinal;
						i++;
					}
				}
				catch (System.IndexOutOfRangeException)
				{ }
				catch (System.ArgumentOutOfRangeException)
				{ }
				for (i = 0; i < 0x100; i++)
				{
					if (this.Palette[i] < 0x8000)
						this.Palette[i] = 0x8000;
				}
			}
		}
#endif

		public unsafe void GetImagePalette(Bitmap bit)
		{
			int count = 0;
			var bmp = new Bitmap(bit);
			BitmapData bd = bmp.LockBits(
				new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, Settings.PixelFormat);
			var line = (ushort*)bd.Scan0;
			int delta = bd.Stride >> 1;
			ushort* cur = line;
			int i = 0;
			while (i < 0x100)
			{
				Palette[i] = 0;
				i++;
			}
			int y = 0;
			while (y < bmp.Height)
			{
				cur = line;
				for (int x = 0; x < bmp.Width; x++)
				{
					ushort c = cur[x];
					if (c != 0)
					{
						bool found = false;
						i = 0;
						while (i < Palette.Length)
						{
							if (Palette[i] == c)
							{
								found = true;
								break;
							}
							i++;
						}
						if (!found)
						{
							Palette[count++] = c;
						}
						if (count >= 0x100)
						{
							break;
						}
					}
				}
				for (i = 0; i < 0x100; i++)
				{
					if (Palette[i] < 0x8000)
					{
						Palette[i] = 0x8000;
					}
				}
				if (count >= 0x100)
				{
					break;
				}
				y++;
				line += delta;
			}
		}

		public void PaletteConversor(int seletor)
		{
			int i;
			for (i = 0; i < 0x100; i++)
			{
				int BlueTemp = (Palette[i] - 0x8000) / 0x20;
				BlueTemp *= 0x20;
				BlueTemp = (Palette[i] - 0x8000) - BlueTemp;
				int GreenTemp = (Palette[i] - 0x8000) / 0x400;
				GreenTemp *= 0x400;
				GreenTemp = ((Palette[i] - 0x8000) - GreenTemp) - BlueTemp;
				GreenTemp /= 0x20;
				int RedTemp = (Palette[i] - 0x8000) / 0x400;
				int contaFinal = 0;
				switch (seletor)
				{
					case 1:
						contaFinal = (((0x400 * RedTemp) + (0x20 * GreenTemp)) + BlueTemp) + 0x8000;
						break;
					case 2:
						contaFinal = (((0x400 * RedTemp) + (0x20 * BlueTemp)) + GreenTemp) + 0x8000;
						break;
					case 3:
						contaFinal = (((0x400 * GreenTemp) + (0x20 * RedTemp)) + BlueTemp) + 0x8000;
						break;
					case 4:
						contaFinal = (((0x400 * GreenTemp) + (0x20 * BlueTemp)) + RedTemp) + 0x8000;
						break;
					case 5:
						contaFinal = (((0x400 * BlueTemp) + (0x20 * GreenTemp)) + RedTemp) + 0x8000;
						break;
					case 6:
						contaFinal = (((0x400 * BlueTemp) + (0x20 * RedTemp)) + GreenTemp) + 0x8000;
						break;
				}
				if (contaFinal == 0x8000)
				{
					contaFinal = 0x8001;
				}
				Palette[i] = (ushort)contaFinal;
			}
			for (i = 0; i < 0x100; i++)
			{
				if (Palette[i] < 0x8000)
				{
					Palette[i] = 0x8000;
				}
			}
		}

		public void PaletteReductor(int Redp, int Greenp, int Bluep)
		{
			int i;
			Redp /= 8;
			Greenp /= 8;
			Bluep /= 8;
			for (i = 0; i < 0x100; i++)
			{
				int BlueTemp = (Palette[i] - 0x8000) / 0x20;
				BlueTemp *= 0x20;
				BlueTemp = (Palette[i] - 0x8000) - BlueTemp;
				int GreenTemp = (Palette[i] - 0x8000) / 0x400;
				GreenTemp *= 0x400;
				GreenTemp = ((Palette[i] - 0x8000) - GreenTemp) - BlueTemp;
				GreenTemp /= 0x20;
				int RedTemp = (Palette[i] - 0x8000) / 0x400;
				RedTemp += Redp;
				GreenTemp += Greenp;
				BlueTemp += Bluep;
				if (RedTemp < 0)
				{
					RedTemp = 0;
				}
				if (RedTemp > 0x1f)
				{
					RedTemp = 0x1f;
				}
				if (GreenTemp < 0)
				{
					GreenTemp = 0;
				}
				if (GreenTemp > 0x1f)
				{
					GreenTemp = 0x1f;
				}
				if (BlueTemp < 0)
				{
					BlueTemp = 0;
				}
				if (BlueTemp > 0x1f)
				{
					BlueTemp = 0x1f;
				}
				int contaFinal = (((0x400 * RedTemp) + (0x20 * GreenTemp)) + BlueTemp) + 0x8000;
				if (contaFinal == 0x8000)
				{
					contaFinal = 0x8001;
				}
				Palette[i] = (ushort)contaFinal;
			}
			for (i = 0; i < 0x100; i++)
			{
				if (Palette[i] < 0x8000)
				{
					Palette[i] = 0x8000;
				}
			}
		}

		//End of Soulblighter Modification

		public unsafe void ExportPalette(string filename, int type)
		{
			switch (type)
			{
				case 0:
					using (var Tex = new StreamWriter(new FileStream(filename, FileMode.Create, FileAccess.ReadWrite)))
					{
						for (int i = 0; i < 0x100; ++i)
						{
							Tex.WriteLine(Palette[i]);
						}
					}
					break;
				case 1:
					{
						var bmp = new Bitmap(0x100, 20, Settings.PixelFormat);
						BitmapData bd = bmp.LockBits(
							new Rectangle(0, 0, 0x100, 20), ImageLockMode.WriteOnly, Settings.PixelFormat);
						var line = (ushort*)bd.Scan0;
						int delta = bd.Stride >> 1;
						for (int y = 0; y < bd.Height; ++y, line += delta)
						{
							ushort* cur = line;
							for (int i = 0; i < 0x100; ++i)
							{
								*cur++ = Palette[i];
							}
						}
						bmp.UnlockBits(bd);
						var b = new Bitmap(bmp);
						b.Save(filename, ImageFormat.Bmp);
						b.Dispose();
						bmp.Dispose();
						break;
					}
				case 2:
					{
						var bmp = new Bitmap(0x100, 20, Settings.PixelFormat);
						BitmapData bd = bmp.LockBits(
							new Rectangle(0, 0, 0x100, 20), ImageLockMode.WriteOnly, Settings.PixelFormat);
						var line = (ushort*)bd.Scan0;
						int delta = bd.Stride >> 1;
						for (int y = 0; y < bd.Height; ++y, line += delta)
						{
							ushort* cur = line;
							for (int i = 0; i < 0x100; ++i)
							{
								*cur++ = Palette[i];
							}
						}
						bmp.UnlockBits(bd);
						var b = new Bitmap(bmp);
						b.Save(filename, ImageFormat.Tiff);
						b.Dispose();
						bmp.Dispose();
						break;
					}
			}
		}

		public void ReplacePalette(ushort[] palette)
		{
			Palette = palette;
		}

		public void Save(BinaryWriter bin, BinaryWriter idx)
		{
			if ((Frames == null) || (Frames.Count == 0))
			{
				idx.Write(-1);
				idx.Write(-1);
				idx.Write(-1);
				return;
			}
			long start = bin.BaseStream.Position;
			idx.Write((int)start);

			for (int i = 0; i < 0x100; ++i)
			{
				bin.Write((ushort)(Palette[i] ^ 0x8000));
			}
			long startpos = bin.BaseStream.Position;
			bin.Write(Frames.Count);
			long seek = bin.BaseStream.Position;
			long curr = bin.BaseStream.Position + 4 * Frames.Count;
			foreach (FrameEdit frame in Frames)
			{
				bin.BaseStream.Seek(seek, SeekOrigin.Begin);
				bin.Write((int)(curr - startpos));
				seek = bin.BaseStream.Position;
				bin.BaseStream.Seek(curr, SeekOrigin.Begin);
				frame.Save(bin);
				curr = bin.BaseStream.Position;
			}

			start = bin.BaseStream.Position - start;
			idx.Write((int)start);
			idx.Write(idxextra);
		}

		public void ExportToVD(BinaryWriter bin, ref long indexpos, ref long animpos)
		{
			bin.BaseStream.Seek(indexpos, SeekOrigin.Begin);
			if ((Frames == null) || (Frames.Count == 0))
			{
				bin.Write(-1);
				bin.Write(-1);
				bin.Write(-1);
				indexpos = bin.BaseStream.Position;
				return;
			}
			bin.Write((int)animpos);
			indexpos = bin.BaseStream.Position;
			bin.BaseStream.Seek(animpos, SeekOrigin.Begin);

			for (int i = 0; i < 0x100; ++i)
			{
				bin.Write((ushort)(Palette[i] ^ 0x8000));
			}
			long startpos = (int)bin.BaseStream.Position;
			bin.Write(Frames.Count);
			long seek = (int)bin.BaseStream.Position;
			long curr = bin.BaseStream.Position + 4 * Frames.Count;
			foreach (FrameEdit frame in Frames)
			{
				bin.BaseStream.Seek(seek, SeekOrigin.Begin);
				bin.Write((int)(curr - startpos));
				seek = bin.BaseStream.Position;
				bin.BaseStream.Seek(curr, SeekOrigin.Begin);
				frame.Save(bin);
				curr = bin.BaseStream.Position;
			}

			long length = bin.BaseStream.Position - animpos;
			animpos = bin.BaseStream.Position;
			bin.BaseStream.Seek(indexpos, SeekOrigin.Begin);
			bin.Write((int)length);
			bin.Write(idxextra);
			indexpos = bin.BaseStream.Position;
		}
	}

	public sealed class FrameEdit
	{
		private const int DoubleXor = (0x200 << 22) | (0x200 << 12);

		public struct Raw
		{
			public int run;
			public int offx;
			public int offy;
			public byte[] data;
		}

		public Raw[] RawData { get; private set; }
		public Point Center { get; set; }
		public int width;
		public int height;

		public FrameEdit(BinaryReader bin)
		{
			int xCenter = bin.ReadInt16();
			int yCenter = bin.ReadInt16();

			width = bin.ReadUInt16();
			height = bin.ReadUInt16();
			if (height == 0 || width == 0)
			{
				return;
			}
			int header;

			var tmp = new List<Raw>();
			while ((header = bin.ReadInt32()) != 0x7FFF7FFF)
			{
				var raw = new Raw();
				header ^= DoubleXor;
				raw.run = (header & 0xFFF);
				raw.offy = ((header >> 12) & 0x3FF);
				raw.offx = ((header >> 22) & 0x3FF);

				int i = 0;
				raw.data = new byte[raw.run];
				while (i < raw.run)
				{
					raw.data[i++] = bin.ReadByte();
				}
				tmp.Add(raw);
			}
			RawData = tmp.ToArray();
			Center = new Point(xCenter, yCenter);
		}

		public unsafe FrameEdit(Bitmap bit, ushort[] palette, int centerx, int centery)
		{
			Center = new Point(centerx, centery);
			width = bit.Width;
			height = bit.Height;
			BitmapData bd = bit.LockBits(
				new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, Settings.PixelFormat);
			var line = (ushort*)bd.Scan0;
			int delta = bd.Stride >> 1;
			var tmp = new List<Raw>();

			int X = 0;
			for (int Y = 0; Y < bit.Height; ++Y, line += delta)
			{
				ushort* cur = line;
				int i = 0;
				int j = 0;
				X = 0;
				while (i < bit.Width)
				{
					i = X;
					for (i = X; i <= bit.Width; ++i)
					{
						//first pixel set
						if (i < bit.Width)
						{
							if (cur[i] != 0)
							{
								break;
							}
						}
					}
					if (i < bit.Width)
					{
						for (j = (i + 1); j < bit.Width; ++j)
						{
							//next non set pixel
							if (cur[j] == 0)
							{
								break;
							}
						}
						var raw = new Raw();
						raw.run = j - i;
						raw.offx = j - raw.run - centerx;
						raw.offx += 512;
						raw.offy = Y - centery - bit.Height;
						raw.offy += 512;

						int r = 0;
						raw.data = new byte[raw.run];
						while (r < raw.run)
						{
							ushort col = (cur[r + i]);
							raw.data[r++] = GetPaletteIndex(palette, col);
						}
						tmp.Add(raw);
						X = j + 1;
						i = X;
					}
				}
			}

			RawData = tmp.ToArray();
			bit.UnlockBits(bd);
		}

		public void ChangeCenter(int x, int y)
		{
			for (int i = 0; i < RawData.Length; i++)
			{
				RawData[i].offx += Center.X;
				RawData[i].offx -= x;
				RawData[i].offy += Center.Y;
				RawData[i].offy -= y;
			}
			Center = new Point(x, y);
		}

		private static byte GetPaletteIndex(ushort[] palette, ushort col)
		{
			for (int i = 0; i < palette.Length; i++)
			{
				if (palette[i] == col)
				{
					return (byte)i;
				}
			}
			return 0;
		}

		public void Save(BinaryWriter bin)
		{
			bin.Write((short)Center.X);
			bin.Write((short)Center.Y);
			bin.Write((ushort)width);
			bin.Write((ushort)height);
			if (RawData != null)
			{
				for (int j = 0; j < RawData.Length; j++)
				{
					int newHeader = RawData[j].run | (RawData[j].offy << 12) | (RawData[j].offx << 22);
					newHeader ^= DoubleXor;
					bin.Write(newHeader);
					foreach (byte b in RawData[j].data)
					{
						bin.Write(b);
					}
				}
			}
			bin.Write(0x7FFF7FFF);
		}
	}
}