package CharacterExport
{
	import flash.display.*;
	import djarts.core.*;
	import flash.geom.Rectangle;
	import flash.geom.Matrix;
	import flash.text.TextField;
	import djarts.display.CachedMovieClip;
	import flash.geom.Point;
	import flash.geom.ColorTransform;

	public class CharacterSequence
	{
		protected var sequence;
		public function get Sequence() { return sequence; }
		
		protected var length = -1;
		public function get Length() { return length; }
		
		protected var exporter:CharacterExporter;
		public function get Exporter():CharacterExporter { return exporter; }
		protected var graphic;
		
		protected var pieces:Array = [];
		
		protected var pieceSequences:Array = [];
		public function get PieceSequences():Array { return pieceSequences; }
		
		protected var formatType;
		
		protected var animationName:String;
		public function get AnimationName():String { return animationName; }
		
		public var AvatarName:String = "";
		
		public function CharacterSequence(exporter:CharacterExporter, sequence, fullSequence, formatType)
		{
			this.exporter = exporter;
			this.sequence = sequence;
			this.formatType = formatType;
			
			graphic = exporter.GetNewSequence(sequence);
			if (formatType == CharacterExporter.NORMAL)
			{
				length = graphic.totalFrames;
			}
			if (formatType == CharacterExporter.OLDMONSTER)
			{
				length = 1;
				for (var i = 0; i < graphic.numChildren; i++)
				{
					var c = graphic.getChildAt(i);
					if (c is MovieClip && c.totalFrames > 1)
					{
						length = c.totalFrames;
						break;
					}
				}
			}
			length = fullSequence ? length : Math.min(length, 1);
			CharacterExporter.SafeGotoFrame2(graphic, 1);
			
			animationName = exporter.GetSequenceName(sequence);
		}
		
		public function ProducePieces()
		{
			for (var i = 0; i < length; i++)
			{
				ProducePiecesFromFrame(i);
			}
			
			/*
			// test by hiding all alternate usages of Pieces, to make sure we are tracking frame->frame correctly
			var usedPiece = {};
			for (var i = 0; i < pieceSequences.length; i++)
			{
				var pieceIndex = this.pieces.indexOf(pieceSequences[i].GetPiece());
				if (pieceIndex in usedPiece)
				{
					pieceSequences.splice(i, 1);
					i--;
				} else {
					usedPiece[pieceIndex] = true;
				}
			}
			*/
			
			//CombinePieces();
		}
		
		protected function ProducePiecesFromFrame(frame)
		{
			if (formatType == CharacterExporter.OLDMONSTER)
			{
				for (var i = 0; i < graphic.numChildren; i++)
				{
					var child = graphic.getChildAt(i);
					if (child is MovieClip)
					{
						CharacterExporter.SafeGotoFrame2(child, frame + 1);
					}
				}
			} else {
				CharacterExporter.SafeGotoFrame2(graphic, frame + 1);
			}
			
			RecursePieces(graphic, frame + 1, 0, new Matrix());
		}
		
		protected function RecursePieces(graphic:MovieClip, frame, depth, parentMatrix:Matrix)
		{
			
			_trace(depth+" "+graphic.name+" frame:"+frame);
			
			var mat:Matrix = graphic.transform.matrix.clone();
			mat.concat(parentMatrix);
			
			var currentPieces:Array = [];
			
			var allChildren:Array = CharacterExporter.GetChildren(graphic);
			for (var i = 0; i < allChildren.length; i++)
			{
				if (!allChildren[i].visible) continue;
				if (allChildren[i] is MovieClip)
				{
					if (CharacterExporter.IsFullPiece(this.graphic, graphic, allChildren[i]))
					{
						currentPieces.push(allChildren[i]);
					} else {
						depth = GeneratePieceForClips(currentPieces, depth, frame, mat.clone());
						currentPieces.splice(0);
						
						depth = RecursePieces(allChildren[i], frame, depth, mat.clone());
					}
				} else {
					currentPieces.push(allChildren[i]);
				}
			}
			
			depth = GeneratePieceForClips(currentPieces, depth, frame, mat.clone());
			return depth;			
		}
		
		protected function GeneratePieceForClips(clips:Array, depth, frame, parentMatrix:Matrix)
		{
			if (clips.length > 0)
			{				
				var l = clips.length;
				var bounds:Rectangle = null;
				
				var invertMatrix:Matrix = clips[0].transform.matrix.clone();
				invertMatrix.invert();
				
				var useMatrix:Array = [];
				for (var i = 0; i < clips.length; i++)
				{
					var m:Matrix;
					if (i == 0)
					{
						m = new Matrix();
					} else {
						m = clips[i].transform.matrix.clone();
						m.concat(invertMatrix);
					}
					
					var r:Rectangle = CacheManager.Instance().GetAccurateBounds(clips[i]);
					CharacterExporter.TransformRect(r, m);
					if (bounds == null)
					{
						bounds = r.clone();
					} else {
						if (r.left < bounds.left) bounds.left = r.left;
						if (r.top < bounds.top) bounds.top = r.top;
						if (r.right > bounds.right) bounds.right = r.right;
						if (r.bottom > bounds.bottom) bounds.bottom = r.bottom;
					}
					
					useMatrix.push(m);
				}
				
				CharacterExporter.ScaleRect(bounds, exporter.Upscale);
				CharacterExporter.RoundRect(bounds);
				var bd:BitmapData = new BitmapData(Math.max(1, bounds.width), Math.max(1,bounds.height), true, 0x0);
				
				for (var i = 0; i < clips.length; i++)
				{
					var m:Matrix = useMatrix[i];
					m.scale(exporter.Upscale, exporter.Upscale);
					m.translate(-bounds.left, -bounds.top);
					var ct:ColorTransform = clips[i] is Shape ? clips[i].parent.transform.colorTransform : clips[i].transform.colorTransform;
					bd.draw(clips[i], m, ct, null, null, true);
				}
				
				var center:Point = new Point(-bounds.left, -bounds.top);

				var piece:Piece = exporter.Library.GetPieceFromBitmapData(bd, clips[0].parent.name, center);
				if (piece)
				{
					var mat:Matrix = new Matrix(1/exporter.Upscale, 0, 0, 1/exporter.Upscale, center.x - piece.CenterPoint.x, center.y - piece.CenterPoint.y);
					mat.concat(clips[0].transform.matrix);
					mat.concat(parentMatrix);
					
					//mat.a /= exporter.Upscale;
					//mat.d /= exporter.Upscale;
					
					var pieceSequence:PieceSequence = GetPieceSequence(piece, frame, clips[0], mat, depth);
					var info:PieceFrameInfo = pieceSequence.GetFrame(frame);
					if (info)
					{
						info.Present = true;

						info.Transform = mat;
						
						
						
						info.Depth = depth;
						depth++;					
					}
				}
			}
			return depth;
		}
		
		protected function GetPieceSequence(piece:Piece, frame, clip:DisplayObject, newTransform:Matrix, newDepth)
		{
			if (this.pieces.indexOf(piece) == -1) this.pieces.push(piece);
			var options:Array = [];
			for (var i in pieceSequences)
			{
				if (pieceSequences[i].GetPiece() == piece)
				{
					var frameInfo:PieceFrameInfo = pieceSequences[i].GetFrame(frame);
					if (frameInfo && !frameInfo.Present)
					{
						if (pieceSequences[i].CheckMatches(clip))
						{
							return pieceSequences[i];
						} else {
							options.push(pieceSequences[i]);
						}
					}
				}
			}
			
			if (options.length == 0)
			{
				var s:PieceSequence = new PieceSequence(piece, this.length);
				piece.AddUse(sequence);
				pieceSequences.push(s);
				return s;
			}
			
			var scores:Array = [];
			for (var i = 0; i < options.length; i++)
			{
				var score = 0;
				if (frame > 1)
				{
					var prevInfo:PieceFrameInfo = options[i].GetFrame(frame - 1);
					if (prevInfo && prevInfo.Present)
					{
						score = ComputeTransformScore(prevInfo.Transform, prevInfo.Depth, newTransform, newDepth);
					}
				}
				scores.push(score);
			}
			var sorted:Array = scores.sort(Array.NUMERIC | Array.RETURNINDEXEDARRAY);
			return options[sorted[0]];
		}
		
		protected function ComputeTransformScore(transformA:Matrix, depthA, transformB:Matrix, depthB, posVal = 1, scaleVal = 15, rotVal = 20, depthVal = 10)
		{
			var deltaPos = Point.distance(new Point(transformA.tx, transformA.ty), new Point(transformB.tx, transformB.ty));
			deltaPos /= exporter.Upscale;
			
			var partsA = CharacterExporter.Decompose(transformA);
			var partsB = CharacterExporter.Decompose(transformB);
			
			var rotA = partsA.rot;
			var rotB = partsB.rot;
			var deltaRot = CharacterExporter.GetAngleDiffAbs(rotA, rotB);
			
			var deltaScaleX = partsA.sx / partsB.sx;
			if (deltaScaleX < 1) deltaScaleX = 1 / deltaScaleX;
			var deltaScaleY = partsA.sy / partsB.sy;
			if (deltaScaleY < 1) deltaScaleY = 1 / deltaScaleY;
			var deltaScale = Math.max(deltaScaleX, deltaScaleY);
			
			var deltaDepth = Math.abs(depthA - depthB);
			
			return deltaPos * posVal + (deltaScale - 1) * scaleVal + deltaRot * rotVal + deltaDepth * depthVal;
		}
		
		public function DebugPieces(rowY)
		{
			var count:Array = [];
			
			for (var i in pieces)
			{
				count.push(0);
			}
			
			for (var i in pieceSequences)
			{
				var p:Piece = pieceSequences[i].GetPiece();
				var index = pieces.indexOf(p);
				count[index]++;
			}
			
			var nextX = 0;
			for (var i = 0; i < pieces.length; i++)
			{
				var b:Bitmap = new Bitmap(pieces[i].FullData);
				b.scaleX = b.scaleY = 1;
				b.x = nextX;
				b.y = rowY;
				Globals.Instance().GetStage().addChild(b);
				nextX += b.width;
				var t:TextField = new TextField();
				t.textColor = 0xFF0000;
				t.text = "x" + count[i];
				Globals.Instance().GetStage().addChild(t);
				t.x = b.x;
				t.y = b.y + b.height;
			}
		}
		
		public static var ONLYCOMBINEADJACENT = true;
		
		protected function CombinePieces()
		{
			var sequenceByPresent = {};
			for (var i in pieceSequences)
			{
				var str = "";
				for (var j = 0; j < length; j++)
				{
					var info:PieceFrameInfo = pieceSequences[i].GetFrame(j + 1);
					str += (info && info.Present) ? "1" : "0";
				}
				if (!(str in sequenceByPresent))
				{
					sequenceByPresent[str] = [];
				}
				sequenceByPresent[str].push(pieceSequences[i]);
			}
			
			for (var presentStr in sequenceByPresent)
			{
				var all:Array = sequenceByPresent[presentStr];
				
				var firstFrame = ("" + presentStr).indexOf("1") + 1;
				
				var depths:Array = [];
				for (var i = 0; i < all.length; i++)
				{
					depths.push(all[i].GetFrame(firstFrame).Depth);
				}
				var sorted:Array = depths.sort(Array.NUMERIC | Array.RETURNINDEXEDARRAY);
				
				var groups:Array = [];
				var lastGroup:Array = null;
				for (var i = 0; i < all.length; i++)
				{
					var index = sorted[i];
					if (ONLYCOMBINEADJACENT)
					{
						if (lastGroup != null && SequenceMatches(lastGroup[0], all[index]))
						{
							lastGroup.push(all[index]);
						} else {
							lastGroup = [];
							groups.push(lastGroup);
							lastGroup.push(all[index]);
						}
					} else {
						var foundGroup = false;
						for (var j in groups)
						{
							if (SequenceMatches(groups[j][0], all[index]))
							{
								groups[j].push(all[index]);
								foundGroup = true;
								break;
							}
						}
						if (!foundGroup)
						{
							var g:Array = [];
							g.push(all[index]);
							groups.push(g);
						}
					}
				}
			}
			
			for (var i in groups)
			{
				// recovering the original pieces...
				// for now: render the bitmaps? (one will be transformed... but it would be transformed anyways, right?)
				var group:Array = groups[i];
				if (group.length > 1)
				{
					var origSeq:PieceSequence = group[0];
					var invert:Matrix;
					var firstPresent = null;
					for (var j = 0; j < length; j++)
					{
						var info:PieceFrameInfo = origSeq.GetFrame(j + 1);
						if (info && info.Present)
						{
							firstPresent = j + 1;
							invert = info.Transform.clone();
							invert.invert();
							break;
						}
					}
					var bounds:Rectangle = null;
					var transforms:Array = [];
					for (var j = 0; j < group.length; j++)
					{
						var p:Piece = group[j].GetPiece();
						p.RemoveUse(sequence);
						var m:Matrix = new Matrix();
						if (j > 0)
						{
							//m.translate(-group[j].CenterPoint.x, -group[j].CenterPoint.y);
							m.concat(invert);
							m.concat(group[j].GetFrame(firstPresent).Transform);
							//m.translate(origSeq.CenterPoint.x, origSeq.CenterPoint.y);
						}
						var r:Rectangle = p.FullData.rect.clone();
						CharacterExporter.TransformRect(r, m);
						
						if (bounds == null)
						{
							bounds = r;
						} else {
							if (r.left < bounds.left) bounds.left = r.left;
							if (r.right > bounds.right) bounds.right = r.right;
							if (r.top < bounds.top) bounds.top = r.top;
							if (r.bottom > bounds.bottom) bounds.bottom = r.bottom;							
						}
						transforms.push(m);
					}
					CharacterExporter.RoundRect(bounds);
					var bd:BitmapData = new BitmapData(bounds.width, bounds.height, true, 0x0);
					for (var j = 0; j < group.length; j++)
					{
						transforms[j].translate(-bounds.left, -bounds.top);
						bd.draw(group[j].GetPiece().FullData, transforms[j], null, null, null, true);
					}

					var p:Piece = this.exporter.Library.GetPieceFromBitmapData(bd, "CombinedPiece");
					var seq:PieceSequence = new PieceSequence(p, this.length);
					p.AddUse(sequence);
					seq.CopyTransformsFrom(origSeq, group[group.length - 1].GetFrame(firstPresent).Depth);
					
					this.pieceSequences.push(seq);
					
					this.pieces.push(p);
				}
			}
			
			for (var i in groups)
			{
				if (groups[i].length > 1)
				{
					for (var j in groups[i])
					{
						var index = this.pieceSequences.indexOf(groups[i][j]);
						if (index == -1)
						{
							throw new Error("huh?");
						} else {
							pieceSequences.splice(index, 1);
						}
					}
				}
			}
			
			
			for (var i = 0; i < pieces.length; i++)
			{
				if (pieces[i].GetUses(sequence) == 0)
				{
					pieces.splice(i, 1);
					i--;
				}
			}
			
		}
		
		protected function SequenceMatches(a:PieceSequence, b:PieceSequence)
		{
			var relative:Matrix = null;
			for (var i = 0; i < length; i++)
			{
				var infoA:PieceFrameInfo = a.GetFrame(i + 1);
				var infoB:PieceFrameInfo = b.GetFrame(i + 1);
				if (infoA && infoB && infoA.Present && infoB.Present)
				{
					var m:Matrix = CharacterExporter.GetTransform(infoA.Transform, infoB.Transform);
					if (relative == null)
					{
						relative = m;
					} else {
						if (ComputeTransformScore(relative, 0, m, 0, 4, 15, 5, 0) > 4) return false;
						//if (!CharacterExporter.MatricesMatch(relative, m)) return false;
					}
				}			
			}
			return true;
		}
		
		public function ProduceFrame(frame, excludeNames:Array = null):MovieClip
		{
			var depths:Array = [];
			for (var i = 0; i < pieceSequences.length; i++)
			{
				depths.push(pieceSequences[i].GetFrame(frame).Depth);
			}
			
			var sorts:Array = depths.sort(Array.NUMERIC | Array.RETURNINDEXEDARRAY);
			
			var m:MovieClip = new MovieClip();
			for (var i = 0; i < sorts.length; i++)
			{
				var ps:PieceSequence = pieceSequences[sorts[i]];
				var info:PieceFrameInfo = ps.GetFrame(frame);
				if (info.Present && (excludeNames == null || excludeNames.indexOf(ps.GetPiece().Name) == -1))
				{
					var p:Piece = ps.GetPiece();
					var b:Bitmap = new Bitmap(p.FullData);
					
					var mat:Matrix = new Matrix();
					mat.translate(-p.CenterPoint.x, -p.CenterPoint.y);
					mat.concat(info.Transform);
					
					//mat.scale(1 / exporter.Upscale, 1 / exporter.Upscale);
					
					//b.smoothing = true;
					b.transform.matrix = mat;
					
					m.addChild(b);
				}
			}
			return m;
		}
		
		public function ProduceFrameBitmapData(frame, excludeNames:Array = null)
		{
			var r:Rectangle = null;
			
			for (var i in pieceSequences)
			{
				var ps:PieceSequence = pieceSequences[i];
				var frameInfo:PieceFrameInfo = ps.GetFrame(frame);
				if (frameInfo && frameInfo.Present && (excludeNames == null || excludeNames.indexOf(ps.GetPiece().Name) == -1))
				{
					var piece:Piece = ps.GetPiece();
					var bounds:Rectangle = piece.FullData.rect;
					bounds.x -= piece.CenterPoint.x;
					bounds.y -= piece.CenterPoint.y;
					var mat:Matrix = frameInfo.Transform.clone();
					//mat.scale(1 / exporter.Upscale, 1 / exporter.Upscale);
					CharacterExporter.TransformRect(bounds, mat);
					
					if (r == null)
					{
						r = bounds;
					} else {
						if (bounds.left < r.left) r.left = bounds.left;
						if (bounds.top < r.top) r.top = bounds.top;
						if (bounds.right > r.right) r.right = bounds.right;
						if (bounds.bottom > r.bottom) r.bottom = bounds.bottom;
					}
						
				}
			}
			
			if (r == null) {
				return {data:new BitmapData(1, 1, true, 0x0),center_point:new Point(0, 0)};
			}
			
			CharacterExporter.RoundRect(r);
			var bd:BitmapData;
			try {
				bd = new BitmapData(Math.min(512, r.width), Math.min(512, r.height), true, 0x0);
			} catch (e){
				_trace("failed to create bitmap data");
			}
			
			
			var depths:Array = [];
			for (var i = 0; i < pieceSequences.length; i++)
			{
				depths.push(pieceSequences[i].GetFrame(frame).Depth);
			}
			
			var sorts:Array = depths.sort(Array.NUMERIC | Array.RETURNINDEXEDARRAY);
			
			for (var i = 0; i < sorts.length; i++)
			{
				var ps:PieceSequence = pieceSequences[sorts[i]];
				var info:PieceFrameInfo = ps.GetFrame(frame);
				if (info && info.Present && (excludeNames == null || excludeNames.indexOf(ps.GetPiece().Name) == -1))
				{
					var p:Piece = ps.GetPiece();
					
					var mat:Matrix = new Matrix();
					mat.translate(-ps.GetPiece().CenterPoint.x, -ps.GetPiece().CenterPoint.y);
					mat.concat(info.Transform);
					
					//mat.scale(1 / exporter.Upscale, 1 / exporter.Upscale);
					
					mat.translate(-r.left, -r.top);
					
					bd.draw(p.FullData, mat, null, null, null, true);
				}
			}
			return {data:bd,center_point:new Point(-r.left, -r.top)};
		}
		
		public function GetDebugClip(excludeNames:Array = null):CachedMovieClip
		{
			var c:CachedMovieClip = new CachedMovieClip();
			c.frames = [];
			for (var i = 0; i < this.length; i++)
			{
				var frame = this.ProduceFrameBitmapData(i + 1, excludeNames);
				c.frames.push(frame.data);
				c.framePositions.push(new Point(-frame.center_point.x, -frame.center_point.y));
			}
			c.SetTotalFrames(this.length);
			return c;
		}
	}
}