package GraphicExport
{
	import Defs.GraphicDef;
	import djarts.utils.CountWaiter;
	import djarts.core.*;
	import djarts.display.*;
	import flash.geom.Rectangle;
	import flash.geom.Point;
	import Packer.SpriteSheet;
	import flash.geom.Matrix;
	import flash.utils.*;
	import flash.display.MovieClip;
	import flash.display.BitmapData;
	import flash.display.Bitmap;
	import djarts.utils.SimpleTimer;
	import flash.display.*;
	import flash.geom.ColorTransform;

	public class IconPacker
	{
		protected var doneCallback = null;
		protected var callbackParam;
		protected var holder:MovieClip;
		protected var jsonSpriteData = {};
		protected var pngSheets = [];

		public var outputFormat = "binary";
		
		public function IconPacker(callback, callbackParam, holder:MovieClip, outputFormat)
		{
			this.doneCallback = callback;
			this.callbackParam = callbackParam;
			this.holder = holder;
			this.outputFormat = outputFormat;

			jsonSpriteData = {};
			jsonSpriteData.format = "spritesheet";
			jsonSpriteData.graphics = [];
			
			output.endian = Endian.LITTLE_ENDIAN;
		}
		
		protected var pageWidth = 2048;
		protected var pageHeight = 2048;
		
		protected var defs:Array;
		
		protected var pad = 3;
		
		public function ExportDefs(defs:Array)
		{
			if (defs.length == 1)
			{ 
				ExportSingle(defs[0])
			}
			else
			{
				this.defs = defs;
				Export();
			}
		}
		
		public function ExportSingle(def:GraphicDef)
		{			
			this.defs = [def];
			var g = GraphicFactory.Instance().GetGraphicByID(def.ID, null, CheckSizesForSingle);
			if (GraphicFactory.Instance().LoadedOK(g))
			{
				CheckSizesForSingle(null, g);
			}
		}
		
		protected function CheckSizesForSingle(caller, g)
		{
			var p:Point = Pow2SizeForGraphic(defs[0]);
			pageWidth = p.x;
			pageHeight = p.y;
			
			Export();
		}
		
		protected function Pow2SizeForGraphic(def:GraphicDef):Point
		{
			var g = GraphicFactory.Instance().GetGraphicByID(def.ID);
			var bounds:Rectangle = CacheManager.Instance().GetAccurateBounds(g, 2048);
			
			if (def.ExportParams.hasOwnProperty("available_sizes"))
			{
				var totalSize = 0;
				for (var i in def.ExportParams.available_sizes)
				{
					if (def.ExportParams.available_sizes[i] == "default")
					{
						var largestSide = Math.max(bounds.width + pad * 2, bounds.height + pad * 2);
						totalSize += largestSide * largestSide;
					} else {
						var parts:Array = def.ExportParams.available_sizes[i].split("x");
						if (parts.length == 2)
						{
							var w = Number(parts[0]) + pad * 2;
							var h = Number(parts[1]) + pad * 2;
							totalSize += w * h;
						}
					}
				}
				
				var size = Math.sqrt(totalSize);
				var exponent = Math.ceil(Math.log(size) / Math.log(2));
				size = Math.pow(2, exponent);
				return new Point(size, size);
			} else {
				var exponentX = Math.ceil(Math.log(bounds.width + pad * 2) / Math.log(2));
				var exponentY = Math.ceil(Math.log(bounds.height + pad * 2) / Math.log(2));
				return new Point(Math.pow(2, exponentX), Math.pow(2, exponentY));
			}
		}
		
		protected var sheets:Array = new Array();
		
		protected var totalSprites = 0;
		protected var output:ByteArray = new ByteArray();
		
		protected var lastGraphic = null;
		
		protected var currentDef:GraphicDef;
		protected function Export()
		{
			var sheet:SpriteSheet = new SpriteSheet(pageWidth, pageHeight);
			sheets.push(sheet);
			holder.AddClip(new Bitmap(sheet.GetBitmap()));
			ExportNext();
		}
		
		protected function ExportNext()
		{			
			_strace("Exporting " + defs.length);
			if (defs.length == 0)
			{
				Done();
				return;
			}
		
			currentDef = defs.shift();
			var g = GraphicFactory.Instance().GetGraphicByID(currentDef.ID, null, GraphicLoaded);
			if (GraphicFactory.Instance().LoadedOK(g))
			{
				GraphicLoaded(null, g);
			}
		}
		
		protected function GraphicLoaded(caller, g)
		{
			_strace(currentDef.Name);
			var bounds:Rectangle = CacheManager.Instance().GetAccurateBounds(g, 2048);
		
			if (currentDef.ExportParams.hasOwnProperty("available_sizes"))
			{
				for (var j in currentDef.ExportParams.available_sizes)
				{
					if (currentDef.ExportParams.available_sizes[j] == "default")
					{
						AddGraphicAtSize(currentDef, g, 1, bounds, Math.ceil(bounds.width), Math.ceil(bounds.height), true);
					} else {
						var parts:Array = currentDef.ExportParams.available_sizes[j].split("x");
						if (parts.length == 2)
						{
							var width = Number(parts[0]);
							var height = Number(parts[1]);
							var scale = Math.min(width / bounds.width, height / bounds.height);
							AddGraphicAtSize(currentDef, g, scale, bounds, width, height);
						}
					}
				}
			} else {
				AddGraphicAtSize(currentDef, g, 1, bounds);
			}
			
			if (holder)
			{
				
				
				if (lastGraphic)
				{
					if (holder.contains(lastGraphic)) holder.removeChild(lastGraphic);
				}
				holder.addChild(g);
				lastGraphic = g;
				g.x = 150;
				g.y = 150;

				bitmapPreview.x = g.x;
				bitmapPreview.y = g.y + 150;
				
				holder.addChild(bitmapPreview);
			}
			
			
			ExportNext();
		}
		
		protected var bitmapPreview:MovieClip = new MovieClip();
		public static const VERSION = 2;
		
		protected function Done()
		{
			var stream:ByteArray = new ByteArray();
			stream.endian = Endian.LITTLE_ENDIAN;
			
			// -1 is an unlikely number of sheets to have, so
			// I use this as a marker that this file has a version number in it
			stream.writeInt(-1);
			stream.writeInt(VERSION);
			
			stream.writeInt(sheets.length);
			for (var i = 0; i < sheets.length; i++)
			{
				stream.writeInt(pageWidth);
				stream.writeInt(pageHeight);
				
				var data:ByteArray = PNGExportHelper.GetExportPNG(sheets[i].GetBitmap());
				stream.writeInt(data.length);
				stream.writeBytes(data);
				
				pngSheets.push(data);
			}
			
			stream.writeInt(totalSprites);
			stream.writeBytes(output);
			
			var jsonPngSet = new JSONPNGSet(jsonSpriteData.graphics[0].Name, jsonSpriteData, pngSheets);
			//jsonSpriteData
			
			if (doneCallback)
			{
				
				doneCallback(outputFormat == "binary" ? stream : jsonPngSet, callbackParam);
			}
			
			var t:SimpleTimer = new SimpleTimer(2, ClearPreview);

		}

		protected function ClearPreview()
		{
			if (holder)
			{
				if (lastGraphic)
				{
					if (holder.contains(lastGraphic)) holder.removeChild(lastGraphic);
				}
				//while(holder.numChildren > 0) holder.removeChildAt(0);
			}
		}
		
		protected function AddGraphicAtSize(def:GraphicDef, g, scale, bounds:Rectangle, availWidth = null, availHeight = null, isDefault = false)
		{	
			totalSprites++;
			output.writeUnsignedInt(def.Name.length);
			output.writeMultiByte(def.Name, "us-ascii");
			
			var spriteData = {};
			jsonSpriteData.graphics.push(spriteData);
			spriteData.name = def.Name;
			
			var hasSize = (availWidth != null && availHeight != null);
			output.writeBoolean(hasSize);
			if (hasSize)
			{
				output.writeBoolean(isDefault);
				output.writeInt(availWidth);
				output.writeInt(availHeight);
				
				spriteData.defaultSize = isDefault;
				spriteData.sizeX = availWidth;
				spriteData.sizeY = availHeight;
			}
			output.writeInt(1);	// num sequences
			output.writeInt(1);	// num frames
			
			spriteData.sequences = [];
			spriteData.sequences.push([]);
			var frame = {};
			spriteData.sequences[0].push(frame);
			
			var b:Rectangle = bounds.clone();
			b.left = Math.floor(bounds.left * scale);
			b.right = Math.ceil(bounds.right * scale);
			b.top = Math.floor(bounds.top * scale);
			b.bottom = Math.ceil(bounds.bottom * scale);
			
			var newSheet = false;
			var found = false;
			for (var s = 0; s < sheets.length; s++)
			{
				var node:RectangleNode = sheets[s].GetAllocator().Insert(b.width + pad * 2, b.height + pad * 2);
				if (node)
				{
					output.writeInt(s);	// sheet index
					
					// rectangle
					output.writeFloat(node.Rect.left + pad);
					output.writeFloat(node.Rect.top + pad);
					output.writeFloat(node.Rect.width - (pad * 2));
					output.writeFloat(node.Rect.height - (pad * 2));
					output.writeFloat(-b.left);
					output.writeFloat(-b.top);

					frame.texture = s;
					frame.tx = node.Rect.left + pad;
					frame.ty = node.Rect.top + pad;
					frame.tw = node.Rect.width - (pad * 2);
					frame.th = node.Rect.height - (pad * 2);
					frame.x = -b.left;
					frame.y = -b.top;
					
					sheets[s].GetBitmap().draw(g, new Matrix(scale, 0, 0, scale, -b.left + pad + node.Rect.left, -b.top + pad + node.Rect.top), null, BlendMode.NORMAL,
							new Rectangle(node.Rect.left + pad,					// note: bitmap space!
										  node.Rect.top + pad,
										  node.Rect.width - pad * 2,
										  node.Rect.height - pad * 2));
					found = true;
					break;
				}
				if (!found && s == sheets.length - 1)
				{
					if (newSheet)
					{
						throw new Error("Bad sprite!");
						return;
					}
					newSheet = true;
					sheets.push(new SpriteSheet(pageWidth, pageHeight));
					
					holder.AddClip(new Bitmap(sheets[sheets.length - 1].GetBitmap()));
				}
			}
			
			
		}

		public function RemoveSmallChildren(clip:MovieClip)
		{
			if (clip.height < 35 && clip.parent != null)
			{
				clip.visible = false;
				_strace("removed clip that was "+clip.height+ " tall");
			}
			for(var i = 0; i < clip.numChildren; i++)
			{
				if (clip.getChildAt(i) is MovieClip)
				{
					RemoveSmallChildren(clip.getChildAt(i) as MovieClip);
				}
			}
		}
		
		public function RecursiveCacheAsBitmap(clip:MovieClip)
		{
			clip.cacheAsBitmap = true;
			for(var i = 0; i < clip.numChildren; i++){
				if (clip.getChildAt(i) is MovieClip)
				{
					RecursiveCacheAsBitmap(clip.getChildAt(i) as MovieClip);
				}
			}
		}

		protected var cw:CountWaiter;
		protected var loadCallback;
		protected function LoadAll(defs:Array, callback)
		{
			loadCallback = callback;
			cw = new CountWaiter(LoadDone);
			
			for (var i in defs)
			{
				cw.Wait();
				GraphicFactory.Instance().MakeLoadOrFailCallbackByID(defs[i].ID, cw.WaitDone);				
				GraphicFactory.Instance().GetGraphicByID(defs[i].ID, null, BlankCallback);
			}
			
			cw.Go();
		}
		
		protected function BlankCallback(...rest)
		{
		}
		
		protected function LoadDone(waiter)
		{
			if (loadCallback) loadCallback();
		}
	}
}