package CharacterExport
{
	import flash.utils.*;
	import GraphicExport.*;
	import flash.display.BitmapData;
	import flash.geom.*;
	import flash.display.Sprite;
	import djarts.core.*;
	import com.adobe.serialization.json.JSON;

	public class ExportBuilder
	{
		protected var characters:Array;
		protected var output:ByteArray = new ByteArray();
		public function get Output():ByteArray { return output; }
		
		protected var sheets:Array = [];
		
		protected var sheetWidth, sheetHeight;
		
		protected var pieces:Array = [];
		protected var pieceNodes:Array = [];
		
		public var jsonPngSet:JSONPNGSet = new JSONPNGSet();
		public function GetJSONPNGSet(){ return jsonPngSet; }
		public var PNGSheets:Array = [];
		
		public function ExportBuilder(characters:Array, sheetWidth = -1, sheetHeight = -1, library:PieceLibrary = null)
		{
			output.endian = Endian.LITTLE_ENDIAN;
			this.characters = characters;
			
			if (library)
			{
				pieces = pieces.concat(library.GetAllUsedPieces());
			} else {			
				for (var i = 0; i < characters.length; i++)
				{				
					var char:CharacterExporter = characters[i];
					
					var p:Array = char.GetAllPieces();				
					pieces = pieces.concat(p);
				}
			}
			
			if (sheetWidth == -1)
			{
				sheetWidth = sheetHeight = DetermineMinimumSheetSize(pieces);
			}			

			this.sheetWidth = sheetWidth;
			this.sheetHeight = sheetHeight;
			
			sheets.push(new SpriteSheet(sheetWidth, sheetHeight));
			
			var piecePad = 2;
			
			for (var i = 0; i < pieces.length; i++)
			{
				var piece:Piece = pieces[i];
				var bd:BitmapData = piece.FullData;
				var usedNewSheet = false;
				for (var sheetIndex = 0; sheetIndex < sheets.length; sheetIndex++)
				{
					var sheet:SpriteSheet = sheets[sheetIndex];
					if (!sheet.sheetFull)
					{
						var node:RectangleNode = sheet.GetAllocator().Insert(bd.width + piecePad * 2, bd.height + piecePad * 2);
						if (node)
						{
							pieceNodes.push(node);
							sheet.GetBitmap().copyPixels(bd, bd.rect, new Point(node.Rect.x + piecePad, node.Rect.y + piecePad));
							break;
						}
					}
					if (sheetIndex == sheets.length - 1)
					{
						if (usedNewSheet)
						{
							throw new Error("This piece is probably too big!");
						} else {
							usedNewSheet = true;
							var newSheet:SpriteSheet = new SpriteSheet(sheetWidth, sheetHeight);
							newSheet.SheetIndex = sheets.length;
							sheets.push(newSheet);
						}
					}
				}				
			}
			
			output.writeUnsignedInt(sheetWidth);
			output.writeUnsignedInt(sheetHeight);
			output.writeUnsignedInt(sheets.length);
			for (var i = 0; i < sheets.length; i++)
			{
				var png:ByteArray = PNGExportHelper.GetExportPNG(sheets[i].GetBitmap());
				output.writeUnsignedInt(png.length);
				output.writeBytes(png);
				PNGSheets.push(png);
			}
			
			output.writeUnsignedInt(characters.length);
			
			var jsonOutputObj = {};
			jsonOutputObj.characters = [];
			var outputName = "";
			for (var i = 0; i < characters.length; i++)
			{
				var char:CharacterExporter = characters[i];
				var name = "";
				if (char.BaseGraphicId != -1)
				{
					name = GameData.Instance().GetGraphicDefByID(char.BaseGraphicId).Name;
				} else {
					name = char.GraphicName;
				}
				
				output.writeUTF(name);
				
				WriteCharacterExport(output, char, this.pieces, this.pieceNodes, piecePad);
				
				var jsonCharacterData = WriteCharacterExportJSON(char, this.pieces, this.pieceNodes, piecePad);
				jsonCharacterData.name = name;
				outputName = name;
				jsonOutputObj.characters.push(jsonCharacterData);
			}
			output.compress();
			jsonOutputObj.format = "skeletal";
			jsonPngSet.SetData(outputName, jsonOutputObj, PNGSheets);
		}
		
		public static function WriteCharacterExport(output:ByteArray, char:CharacterExporter, pieces:Array, pieceNodes:Array, piecePad)
		{
			var sequences:Array = char.Sequences;
			output.writeUnsignedInt(sequences.length);
			for (var j = 0; j < sequences.length; j++)
			{
				var seq:CharacterSequence = sequences[j];
				output.writeUnsignedInt(seq.Length);
				
				var ps:Array = seq.PieceSequences;					
				output.writeUnsignedInt(ps.length);
				for (var k = 0; k < ps.length; k++)
				{
					var pieceSequence:PieceSequence = ps[k];
					var piece:Piece = pieceSequence.GetPiece();
					var pieceIndex = pieces.indexOf(piece);
					var node:RectangleNode = pieceNodes[pieceIndex];
					output.writeUnsignedInt(node.Host.HostTexture.SheetIndex);
					output.writeUnsignedInt(node.Rect.left + piecePad);
					output.writeUnsignedInt(node.Rect.top + piecePad);
					output.writeUnsignedInt(node.Rect.width - piecePad * 2);
					output.writeUnsignedInt(node.Rect.height - piecePad * 2);
					
					output.writeInt(piece.CenterPoint.x);
					output.writeInt(piece.CenterPoint.y);
					
					for (var l = 0; l < seq.Length; l++)
					{
						var info:PieceFrameInfo = pieceSequence.GetFrame(l + 1);
						if (info && info.Present)
						{
							output.writeBoolean(true);
							output.writeInt(info.Depth);
							var decompose = CharacterExporter.Decompose(info.Transform);
							output.writeDouble(decompose.rot);
							output.writeDouble(decompose.sx);
							output.writeDouble(decompose.sy);
							output.writeDouble(info.Transform.tx);
							output.writeDouble(info.Transform.ty);
						} else {
							output.writeBoolean(false);
						}
					}
				}
			}
		}
		
		public static function WriteCharacterExportJSON(char:CharacterExporter, pieces:Array, pieceNodes:Array, piecePad)
		{
			var jsonData = {};
			var sequences:Array = char.Sequences;
			jsonData.sequences = [];
			for (var j = 0; j < sequences.length; j++)
			{
				var seq:CharacterSequence = sequences[j];
				//output.writeUnsignedInt(seq.Length);
				
				var ps:Array = seq.PieceSequences;					
				var outputPieceSequences = [];
				for (var k = 0; k < ps.length; k++)
				{
					var outputPiece = {};
					
					var pieceSequence:PieceSequence = ps[k];
					var piece:Piece = pieceSequence.GetPiece();
					var pieceIndex = pieces.indexOf(piece);
					var node:RectangleNode = pieceNodes[pieceIndex];
					
					outputPiece.texture_index = node.Host.HostTexture.SheetIndex;
					outputPiece.x = node.Rect.left + piecePad;
					outputPiece.y = node.Rect.top + piecePad;
					outputPiece.w = node.Rect.width - piecePad * 2;
					outputPiece.h = node.Rect.height - piecePad * 2;

					outputPiece.cx = piece.CenterPoint.x;
					outputPiece.cy = piece.CenterPoint.y;
					
					outputPiece.n = piece.Name;
					
					var outputSeq = [];
					for (var l = 0; l < seq.Length; l++)
					{
						var outputFrameInfo = {};
						var info:PieceFrameInfo = pieceSequence.GetFrame(l + 1);
						if (info && info.Present)
						{
							var decompose = CharacterExporter.Decompose(info.Transform);
							outputFrameInfo.used = true;
							outputFrameInfo.z = info.Depth;
							outputFrameInfo.rot = RoundedOutput(decompose.rot);
							outputFrameInfo.sx = RoundedOutput(decompose.sx);
							outputFrameInfo.sy = RoundedOutput(decompose.sy);
							outputFrameInfo.tx = RoundedOutput(info.Transform.tx);
							outputFrameInfo.ty = RoundedOutput(info.Transform.ty);
						} else {
							outputFrameInfo.used = false;
						}						
						outputSeq.push(outputFrameInfo);
					}
					
					outputPiece.sequence = outputSeq;
					outputPieceSequences.push(outputPiece);
				}
				jsonData.sequences.push(outputPieceSequences);
			}
			
			return jsonData;
		}
		
		public static function RoundedOutput(value)
		{
			var str = ""+value;
			var split = str.split(".");
			if (split.length == 2)
			{
				var decPart = split[1];
				if (decPart.length > 3){
					decPart = decPart.substr(0,3);
				}
				str = split[0] +"."+decPart;
			}
			return str;
		}
		

		public static function DetermineMinimumSheetSize(pieces:Array)
		{
			var sizeFound = false;
			var power = 6;
			var sheetSize = Math.pow(2, power);
			while(!sizeFound)
			{
				var piecePad = 2;
				var sheet:SpriteSheet = new SpriteSheet(sheetSize, sheetSize);
				sizeFound = true;
				for (var i = 0; i < pieces.length; i++)
				{
					var piece = pieces[i];
					var bd:BitmapData;
					if (piece is AnimationPiece)
					{
						bd = piece.Data;
					}
					if (piece is Piece)
					{
						bd = piece.FullData;
					}
					if (!sheet.sheetFull)
					{
						var node:RectangleNode = sheet.GetAllocator().Insert(bd.width + piecePad * 2, bd.height + piecePad * 2);
						if (node == null)
						{
							sizeFound = false;
							break;
						}
					}		
				}
				
				//double the size and try again!
				if (sizeFound == false)
				{ 
					power++;
					sheetSize = Math.pow(2, power);
					//Max size 1024;
					if (sheetSize == 1024) break;
				}

			}

			return sheetSize;
		}
		
	}
}