package CharacterExport
{
	import Defs.*;
	import flash.display.*;
	import djarts.display.*;
	import flash.geom.Rectangle;
	import djarts.core.*;
	import djarts.utils.CountWaiter;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import HeroUI.HeroSprite;
	import flash.utils.ByteArray;
	import GraphicExport.*;
	import djarts.utils.SimpleTimer;
	
	import org.aszip.zip.ASZip;
	import org.aszip.compression.CompressionMethod;
	import org.aszip.saving.Method;
	import flash.net.FileReference;
	
	import com.adobe.images.PNGEncoder;
	import flash.utils.getQualifiedClassName;
	import flash.net.*;
	
	import flash.events.*;

	public class CharacterExporter
	{
		protected var loaded = false;
				
		// the graphic we are working on
		protected var heroDef:HeroDef;
		
		protected var baseGraphicId;
		protected var graphicDef:GraphicDef;
		protected var graphicId;
		protected var graphic:MovieClip;
		protected var graphicClass:Class;
		
		protected var graphicName:String = "";
		public function set GraphicName(val:String) { graphicName = val;} 
		public function get GraphicName():String { return (graphicName == "") ? getQualifiedClassName(graphic) : graphicName; }
		
		protected var characterDef:CharacterDef;
		protected var externalAnimationData:ExternalAnimationInfo;
		protected var force;
		
		public function get BaseGraphicId() { return baseGraphicId; }
		public function get GraphicId() { return graphicId; }
		public function get GraphicClass():Class { return graphicClass; }
		
		protected var isLocalLibrary = false;
		protected var library:PieceLibrary;
		public function get Library():PieceLibrary { return library; }
		
		protected var loadWaiter:CountWaiter;
		
		public var Upscale = 1.0;
		
		protected var exportSequences:Array;
		protected var fullSequence:Array;
		
		// formatType
		public static const NORMAL = 0;
		public static const OLDMONSTER = 1;
		
		protected var formatType;
		protected var holder:MovieClip;
		public function CharacterExporter(holder:MovieClip = null)
		{
			this.holder = holder;
		}
			
		public function GetNewSequence(sequence):MovieClip
		{
			if (!loaded) return null;

			var className = flash.utils.getQualifiedClassName(graphic);

			if (className == "flash.display::MovieClip")
			{
				graphic.transform.matrix = new Matrix();
				return GetSequenceClip(graphic, sequence);
			}
			
			if (SingleSequenceClip)
			{
				graphic.transform.matrix = new Matrix();
				return graphic;
			}

			if (characterDef)
			{
				return new CharacterTypeGraphic(graphicId, characterDef, force, sequence, externalAnimationData);
			}
			
			var base:MovieClip = new (graphicClass)();
			return GetSequenceClip(base, sequence);
		}
		
		public function GetSequenceName(sequence):String
		{
			var base:MovieClip = new (graphicClass)();
			return CharacterExporter.GetSequenceName(base, sequence);
		}
		
		protected var sequences:Array = [];
		public function get Sequences():Array { return sequences; }
		
		protected var doneCallback;
		
		public function ProcessFromClip(graphic:MovieClip, callback, library:PieceLibrary = null)
		{
			this.doneCallback = callback;
			Reset();
			
			this.baseGraphicId = -1;
			this.graphicId = -1;
			//this.graphicDef = null;
			this.graphic = graphic;
			
			if (!library)
			{
				isLocalLibrary = true;
				library = new PieceLibrary();
			}
			this.library = library;
			
			if (SingleSequenceClip)
			{
				exportSequences = [1];
				fullSequence = [true];
			}
			else
			{
				//exportSequences = [1];
				//fullSequence = [false];
				exportSequences = [];
				fullSequence = [];
				for (var i = 0; i < graphic.totalFrames; i++){
					exportSequences[i] = i+1;
					fullSequence[i] = true;
				}
			}
			
			loaded = true;
			graphicClass = Object(graphic).constructor;
			
			GenerateSequences();
		}

		var defaultExportType = null;
		
		public function Process(baseGraphicId, callback, library:PieceLibrary = null, defaultExportType = null, scale = 1)
		{
			this.baseGraphicId = baseGraphicId;
			this.defaultExportType = defaultExportType;
			this.Upscale = scale;
			
			if (!library)
			{
				isLocalLibrary = true;
				library = new PieceLibrary();
			}
			this.library = library;
			
			this.doneCallback = callback;
			Reset();
			
			this.graphicDef = GameData.Instance().GetGraphicDefByID(baseGraphicId);
			
			if (graphicDef.ExportParams.hasOwnProperty("from_hero_id"))
			{
				this.heroDef = GameData.Instance().GetHeroDefByID(graphicDef.ExportParams.from_hero_id);
				if (heroDef.UseCharacterGraphics)
				{
					this.characterDef = heroDef.characterDef;
					this.force = characterDef.GenerateForce();
					graphicId = GraphicFactory.Instance().ReverseLookup(characterDef.Body);
				} else {
					graphicId = heroDef.AnimalGraphicID;
				}
			} else {
				graphicId = baseGraphicId;
			}
			
			
			this.loadWaiter.Wait();			
			var g = GraphicFactory.Instance().GetGraphicByID(graphicId, null, loadWaiter.WaitDone);
			if (GraphicFactory.Instance().LoadedOK(g))
			{
				loadWaiter.WaitDone();
			}
			
			if (characterDef)
			{
				for (var i in force)
				{
					var info = force[i];
					if (info.hasOwnProperty("frame"))
					{
						var partGraphicId = GraphicFactory.Instance().ReverseLookup(info.frame);
						if (partGraphicId > 0)
						{
							loadWaiter.Wait();
							var g = GraphicFactory.Instance().GetGraphicByID(partGraphicId, null, loadWaiter.WaitDone);
							if (GraphicFactory.Instance().LoadedOK(g))
							{
								loadWaiter.WaitDone();
							}
						}
					}
				}
			}
			
			this.loadWaiter.Go();
		}
		
		public function Reset()
		{
			loaded = false;
			
			heroDef = null;
			graphicDef = null;
			graphicId = null;
			graphic = null;
			graphicClass = null;
			characterDef = null;
			
			sequences.splice(0);
			
			if (isLocalLibrary)
			{
				library.Reset();
			}
			
			this.loadWaiter = new CountWaiter(GraphicsLoaded);
		}
		
		protected function GraphicsLoaded(w:CountWaiter)
		{
			if (!loaded && w == this.loadWaiter)
			{
				loaded = true;
				
				graphic = GraphicFactory.Instance().GetGraphicByID(this.graphicId);
				graphicClass = Object(graphic).constructor;
				
				if (characterDef)
				{
					externalAnimationData = CacheManager.Instance().GetExternalData(graphic);
				}
				
				GenerateSequences();
			}
		}
		
		protected function GenerateSequences()
		{
			
			var exportType = defaultExportType == null ? "monster" : defaultExportType;
			if (graphicDef != null && graphicDef.ExportParams.hasOwnProperty("export_type"))
			{
				exportType = graphicDef.ExportParams.export_type;
			}
			switch (exportType)
			{
				case "character":
					exportSequences = [1,4,16];
					fullSequence = [true,false,true];
					break;
				case "escort":
					exportSequences = [1];
					fullSequence = [true];
					break;
				case "monster":
				default:
					exportSequences = [];
					fullSequence = [];
					for (var i = 0; i < graphic.totalFrames; i++)
					{
						exportSequences[i] = i+1;
						fullSequence[i] = true;
					}
					break;				
			}			
			
			formatType = NORMAL;
			if (this.characterDef == null && this.graphic.numChildren > 1 && !this.SingleSequenceClip)
			{
				var hasSingleLengthClip = true;
				var lengthClip = null;
				for (var i = 0; i < graphic.numChildren; i++)
				{
					var child = graphic.getChildAt(i);
					if (child is MovieClip)
					{
						if (child.totalFrames > 1)
						{
							if (lengthClip)
							{
								hasSingleLengthClip = false;
								break;
							} else {
								lengthClip = child;
							}
						}
					}
				}
				if (hasSingleLengthClip)
				{
					formatType = OLDMONSTER;
				}
			}
			
			NextSequence();
		}
		
		var nextX = 50;
		var nextY = 200;
		var sequenceIndex = 0;
		public function NextSequence()
		{
			var i = sequenceIndex;
			sequenceIndex++;
			
			var sequence:CharacterSequence = new CharacterSequence(this, exportSequences[i] - 1, fullSequence[i], formatType);
			
			sequence.ProducePieces();
			//sequence.DebugPieces(i * 150);
			
			sequences.push(sequence);
			var includeOriginal = true;
			if (holder)
			{
				/*
				if (includeOriginal)
				{
					if (characterDef != null)
					{
						var char:CharacterGenerator = new CharacterGenerator();
						char.Generate(characterDef, false, false, 1.0, false);
						var clip:CacheByFrame = char.GetData();
						holder.addChild(clip);
						clip.gotoAndStop(exportSequences[i]);
						clip.Frame.gotoAndPlay(1);
						clip.x = nextX;
						clip.y = nextY;
					}
					else
					{
						var original:CacheByFrame = GraphicFactory.Instance().GetCachedClip(GraphicFactory.Instance().GetGraphicName(graphicId), true);
						
						holder.addChild(original);
						original.gotoAndStop(exportSequences[i]);
						original.Frame.gotoAndPlay(1);
						
						original.x = nextX;
						original.y = nextY;
					}
				}
				*/
				
				
				var c:CachedMovieClip = sequence.GetDebugClip();
				holder.addChild(c);
				c.gotoAndPlay(1);
				c.x = nextX + 75;
				c.y = nextY;
				
				
				nextX += includeOriginal ? 150 : 75;

				if (nextX > 2400)
				{
					nextX = 50;
					nextY += 150;
				}
				
			}
			
			if (sequenceIndex == exportSequences.length)
			{
				SequencesComplete();
			} else {
				var t:SimpleTimer = new SimpleTimer(0.01, function(){ NextSequence(); } );
			}
		}
		
		public function SequencesComplete()
		{
			var pieces:Array = GetAllPieces();
			if (holder)
			{				
				var startX = 800;
				var startY = 100;
				var count = 0;
				var max = 0;
				for (var i in pieces)
				{
					var p:Piece = pieces[i];
					var b:Bitmap = new Bitmap(p.FullData);
					holder.addChild(b);
					b.x = startX;
					b.y = startY;
					startY += b.height + 5;
					max = Math.max(max, b.width);
					if (count == 20)
					{
						count = 0;
						startX += max + 5;
						startY = 100;
						max = 0;
					}
					count++;
				}
			}
			
			for (var i in pieces)
			{
				for (var j in pieces)
				{
					if (i != j)
					{
						if (pieces[i].Matches(pieces[j].FullData, null))
						{
							_strace("MATCHING PIECES: " + i + "  " + j);
						}
					}
				}
			}
			
			if (this.isLocalLibrary)
			{
				this.library.EnsureAllUsedPiecesHaveNames();
			}
			
			if (doneCallback) doneCallback();
		}
		
		public function GetAllPieces():Array
		{
			return library.GetAllUsedPieces();
		}
		
		protected var templateCallback;
		public function ProduceTemplate(callback)
		{
			this.templateCallback = callback;
			
			var output:ASZip = new ASZip(CompressionMethod.GZIP);
			
			var allPieces:Array = GetAllPieces();
			for (var i = 0; i < allPieces.length; i++)
			{
				var imgBytes:ByteArray = PNGEncoder.encode(allPieces[i].FullData);
				output.addFile(imgBytes, allPieces[i].Name + ".png");
			}
			
			var allPiecesUsed = false;
			var frame = 1;
			var piecesOutput:Array = [];
			var templateText = getQualifiedClassName(graphic) + "\n";
			while (!allPiecesUsed)
			{
				for (var i = 0; i < exportSequences.length; i++)
				{
					var newPiece = false;
					var pieces:Array = sequences[i].PieceSequences
					for (var j in pieces)
					{
						var pieceSeq:PieceSequence = pieces[j];
						var pieceIndex = allPieces.indexOf(pieceSeq.GetPiece());
						if (pieceSeq.GetFrame(frame).Present && !piecesOutput[pieceIndex])
						{
							newPiece = true;
							break;
						}
					}
					if (newPiece)
					{
						var sortOrder = 
						pieces = pieces.concat();
						pieces.sort(function(a:PieceSequence,b:PieceSequence)
									{
										return a.GetFrame(frame).Depth - b.GetFrame(frame).Depth;
									}
								);
						for (var j = 0; j < pieces.length; j++)
						{
							var pieceSeq:PieceSequence = pieces[j];
							var piece:Piece = pieceSeq.GetPiece();
							var pieceIndex = allPieces.indexOf(piece);
							var pieceFrame:PieceFrameInfo = pieceSeq.GetFrame(frame);
							if (pieceFrame.Present)
							{
								templateText += "piece" + pieceIndex + " " + piece.Name + " " +
												piece.CenterPoint.x + " " +
												piece.CenterPoint.y + " " +
												pieceFrame.Transform.a + " " +
												pieceFrame.Transform.b + " " +
												pieceFrame.Transform.c + " " +
												pieceFrame.Transform.d + " " +
												pieceFrame.Transform.tx + " " + 
												pieceFrame.Transform.ty + "\n";
								piecesOutput[pieceIndex] = true;
							}
						}
						
						templateText += "end frame\n";
						
						var usedCount = 0;
						for (var j in piecesOutput)
						{
							if (piecesOutput[j]) usedCount++;
						}
						if (usedCount >= allPieces.length) allPiecesUsed = true;
					}
					if (allPiecesUsed) break;
				}
				frame++;
			}	
			
			var b:ByteArray = new ByteArray();
			b.writeUTFBytes(templateText);
			output.addFile(b, "template.txt");
			
			var loader:URLLoader = new URLLoader(new URLRequest("loadTemplate.jsfl"));
			loader.dataFormat = URLLoaderDataFormat.BINARY;
			loader.addEventListener(Event.COMPLETE, function(e) {TemplateLoadComplete(loader, output);});
		}
		
		public function ProduceTemplateType2(clipName, excludeNames:Array, sampleAnimationIndices:Array, callback)
		{
			this.templateCallback = callback;
			
			var output:ASZip = new ASZip(CompressionMethod.GZIP);
			
			var templateText = clipName + "\n\n";
			
			var allPieces:Array = GetAllPieces();
			for (var i = 0; i < allPieces.length; i++)
			{
				var imgBytes:ByteArray = PNGEncoder.encode(allPieces[i].FullData);
				output.addFile(imgBytes, allPieces[i].Name + ".png");
				
				templateText += allPieces[i].Name + " " + allPieces[i].CenterPoint.x + " " + allPieces[i].CenterPoint.y + "\n";
			}
			
			templateText += "\n";
			
			var allPiecesUsed = false;
			var frame = 1;
			var piecesOutput:Array = [];
			
			while (!allPiecesUsed)
			{
				for (var i = 0; i < exportSequences.length; i++)
				{
					var newPiece = false;
					var pieces:Array = sequences[i].PieceSequences
					for (var j in pieces)
					{
						var pieceSeq:PieceSequence = pieces[j];
						var pieceIndex = allPieces.indexOf(pieceSeq.GetPiece());
						if (excludeNames.indexOf(pieceSeq.GetPiece().Name) == -1 && pieceSeq.GetFrame(frame).Present && !piecesOutput[pieceIndex])
						{
							newPiece = true;
							break;
						}
					}
					if (newPiece)
					{
						var sortOrder = 
						pieces = pieces.concat();
						pieces.sort(function(a:PieceSequence,b:PieceSequence)
									{
										return a.GetFrame(frame).Depth - b.GetFrame(frame).Depth;
									}
								);
						for (var j = 0; j < pieces.length; j++)
						{
							var pieceSeq:PieceSequence = pieces[j];
							var piece:Piece = pieceSeq.GetPiece();
							var pieceIndex = allPieces.indexOf(piece);
							var pieceFrame:PieceFrameInfo = pieceSeq.GetFrame(frame);
							if (pieceFrame.Present && excludeNames.indexOf(piece.Name) == -1)
							{
								templateText += "piece" + pieceIndex + " " + piece.Name + " " +
												//piece.CenterPoint.x + " " +
												//piece.CenterPoint.y + " " +
												pieceFrame.Transform.a + " " +
												pieceFrame.Transform.b + " " +
												pieceFrame.Transform.c + " " +
												pieceFrame.Transform.d + " " +
												pieceFrame.Transform.tx + " " + 
												pieceFrame.Transform.ty + "\n";
								piecesOutput[pieceIndex] = true;
							}
						}
						
						templateText += "\n";
						
						var usedCount = 0;
						for (var j in piecesOutput)
						{
							if (piecesOutput[j]) usedCount++;
						}
						if (usedCount >= allPieces.length - excludeNames.length) allPiecesUsed = true;
					}
					if (allPiecesUsed) break;
				}
				frame++;
			}
			
			templateText += "end clip\n";
			
			for (var i in sampleAnimationIndices)
			{
				var index = sampleAnimationIndices[i];
				var seq:CharacterSequence = Sequences[index];
				templateText += "\n" + seq.AnimationName + " " + seq.Length + " " + seq.PieceSequences.length + "\n";
				for (var j = 0; j < seq.PieceSequences.length; j++)
				{
					var ps:PieceSequence = seq.PieceSequences[j];
					templateText += ps.GetPiece().Name + "\n";
					for (var k = 0; k < seq.Length; k++)
					{
						var info:PieceFrameInfo = ps.GetFrame(k + 1);
						templateText += info.Present ? "1" : "0";
						if (info.Present)
						{
							templateText += " " + info.Depth + " " + info.Transform.a + " " + info.Transform.b + " " + info.Transform.c + " " + info.Transform.d + " " + info.Transform.tx + " " + info.Transform.ty;
						}
						templateText += "\n";
					}
				}
			}
			
			var b:ByteArray = new ByteArray();
			b.writeUTFBytes(templateText);
			output.addFile(b, "template.txt");
			
			/*
			var loader:URLLoader = new URLLoader(new URLRequest("loadTemplate2.jsfl"));
			loader.dataFormat = URLLoaderDataFormat.BINARY;
			loader.addEventListener(Event.COMPLETE, function(e) {TemplateLoadComplete2(loader, output, clipName);});
			*/
			var loaderBytes:ByteArray = new ByteArray();
			loaderBytes.writeUTFBytes(TEMPLATE2);
			output.addFile(loaderBytes, clipName + "_Load.jsfl");
			
			OutputProjectZip(output, clipName);
		}
		
		private function TemplateLoadComplete(loader:URLLoader, output:ASZip)
		{			
			output.addFile(loader.data, "loadTemplate.jsfl");
			
			loader = new URLLoader(new URLRequest("empty.fla"));
			loader.dataFormat = URLLoaderDataFormat.BINARY;
			loader.addEventListener(Event.COMPLETE, function(e) {EmptyFLALoadComplete(loader, output);});
		}
		
		private function TemplateLoadComplete2(loader:URLLoader, output:ASZip, fileName)
		{			
			output.addFile(loader.data, fileName + "_Load.jsfl");
			
			OutputProjectZip(output, fileName);
		}
		
		private function EmptyFLALoadComplete(loader:URLLoader, output:ASZip)
		{
			output.addFile(loader.data, getQualifiedClassName(graphic) + ".fla");
			
			OutputProjectZip(output, getQualifiedClassName(graphic));
		}
		
		private function OutputProjectZip(output:ASZip, fileName)
		{	
			var outputBytes:ByteArray = output.saveZIP(Method.LOCAL);
			var fr:FileReference = new FileReference();
			fr.addEventListener(Event.COMPLETE, SaveTemplateComplete);
			fr.addEventListener(Event.CANCEL, SaveTemplateComplete);
			fr.save(outputBytes, fileName + ".zip");
		}
		
		protected function SaveTemplateComplete(e:Event)
		{
			if (templateCallback != null) templateCallback();
		}
		
		
		// ====================
		// Utils
		// ====================
		static var maxDepth = 99;
		public static function SafeGotoFrame2(clip:MovieClip, frame)
		{
			if (clip is CharacterTypeGraphic)
			{
				(clip as CharacterTypeGraphic).SetFrame(frame);
				return;
			}
			
			if (clip.currentFrame == 0) return;			
			frame = Math.max(1, frame);
			
			if (clip.currentFrame == frame) return;
			
			var diff = frame - clip.currentFrame;
			
			AdvanceChildFrames(clip, diff, 0);
			
			frame = ((frame - 1) % clip.totalFrames) + 1;
			
			while (clip.currentFrame != frame)
			{
				if (frame > clip.currentFrame)
				{
					clip.nextFrame();
				} else {
					clip.prevFrame();
				}
			}
		}
		
		protected static function AdvanceChildFrames(clip:MovieClip, diff, depth)
		{			
			if (depth > maxDepth) return;
			
			for (var i = 0; i < clip.numChildren; i++)
			{
				var c = clip.getChildAt(i);
				if (c is MovieClip)
				{
					AdvanceChildFrames(c, diff, depth+1);
				}
			}
			if (clip.currentFrame == 0) return;
			var newFrame = clip.currentFrame + diff;
			newFrame = ((newFrame - 1) % clip.totalFrames) + 1;			
			while (clip.currentFrame != newFrame)
			{
				if (newFrame > clip.currentFrame)
				{
					clip.nextFrame();
				} else {
					clip.prevFrame();
				}
			}
		}
		
		public static function SafeGotoFrame(clip:MovieClip, frame)
		{
			if (clip is CharacterTypeGraphic)
			{
				(clip as CharacterTypeGraphic).SetFrame(frame);
			} else {
				frame = Math.max(1, Math.min(clip.totalFrames, frame));
				if (frame == clip.currentFrame || clip.currentFrame == 0) return;
				
				var dir = frame > clip.currentFrame ? 1 : -1;
				while (clip.currentFrame != frame)
				{
					RecurseDir(clip, dir);
				}
			}
		}
		
		protected static function RecurseDir(clip:MovieClip, dir)
		{
			for (var i = 0; i < clip.numChildren; i++)
			{
				var child = clip.getChildAt(i);
				if (child is MovieClip)
				{
					RecurseDir(child, dir);
				}
			}
			if (clip.currentFrame != 0)
			{
				if (dir == 1)
				{
					clip.nextFrame();
				} else {
					clip.prevFrame();
				}
			}
		}
		
		public static function GetChildren(clip:MovieClip):Array
		{
			if (clip is CharacterTypeGraphic)
			{
				return (clip as CharacterTypeGraphic).GetGraphicChildren();
			}
			var ret:Array = [];
			for (var i = 0; i < clip.numChildren; i++)
			{
				ret.push(clip.getChildAt(i));
			}
			return ret;
		}
		
		public static function RoundRect(r:Rectangle)
		{
			r.left = Math.floor(r.left);
			r.top = Math.floor(r.top);
			r.right = Math.ceil(r.right);
			r.bottom = Math.ceil(r.bottom);
		}
		
		public static function ScaleRect(r:Rectangle, s)
		{
			r.left *= s;
			r.top *= s;
			r.right *= s;
			r.bottom *= s;
		}
		
		public static function GetSequenceName(base:MovieClip, sequence):String
		{
			Utils.RecursivelyStop(base, sequence + 1);
			if (base.currentLabel == null) return "UnknownAnimation";
			return base.currentLabel;
		}
		public var SingleSequenceClip = false;
		public static function GetSequenceClip(base:MovieClip, sequence):MovieClip
		{			
			Utils.RecursivelyStop(base, sequence + 1);
			
			if (base.numChildren == 1 && base.getChildAt(0) is MovieClip)
			{
				return base.getChildAt(0) as MovieClip;
			}
			
			var ret:MovieClip = new MovieClip();
			while (base.numChildren > 0)
			{
				ret.addChild(base.getChildAt(0));
			}			
			return ret;
		}
		
		public static function TransformRect(r:Rectangle, m:Matrix)
		{
			var p1:Point = new Point(r.left, r.top);
			var p2:Point = new Point(r.right, r.top);
			var p3:Point = new Point(r.left, r.bottom);
			var p4:Point = new Point(r.right, r.bottom);
			
			p1 = m.transformPoint(p1);
			p2 = m.transformPoint(p2);
			p3 = m.transformPoint(p3);
			p4 = m.transformPoint(p4);
			
			r.left = Math.min(p1.x, p2.x, p3.x, p4.x);
			r.top = Math.min(p1.y, p2.y, p3.y, p4.y);
			r.right = Math.max(p1.x, p2.x, p3.x, p4.x);
			r.bottom = Math.max(p1.y, p2.y, p3.y, p4.y);
		}
		
		private static function HasAnyNamedChildren(m)
		{
			if (m is MovieClip)
			{
				for (var i = 0; i < m.numChildren; i++)
				{
					var c = m.getChildAt(i);
					if (c is MovieClip)
					{
						if (!c.name.match("^instance\\d+$")) return true;
						if (HasAnyNamedChildren(c as MovieClip)) return true;
					}
				}
			}
			return false;
		}
		
		public static function IsFullPiece(baseClip:DisplayObject, parentClip:MovieClip, clip:DisplayObject)
		{
			if (baseClip is CharacterTypeGraphic && baseClip == parentClip) return false;
			if (clip is MovieClip && baseClip is CharacterTypeGraphic)
			{
				var ctg:CharacterTypeGraphic = baseClip as CharacterTypeGraphic;
				if (ctg.IsForcedPiece(clip)) return true;
			}
			
			//if (HasAnyNamedChildren(clip)) return false;
			
			//if (!clip.name.match("^instance\\d+$")) return true;
				
			if (parentClip.totalFrames != 1) return false;
			
			//if (clip == baseClip || (clip.parent && clip.parent == baseClip)) return false;
			if (clip is MovieClip)
			{
				var m:MovieClip = clip as MovieClip;				
				if (m.totalFrames > 1) return false;
				
				if (!m.name.match("^instance\\d+$")) return false;
				
				for (var i = 0; i < m.numChildren; i++)
				{
					var c = m.getChildAt(i);
					if (!IsFullPiece(baseClip, m, c)) return false;
				}
			}
			return true;
		}
		
		public static function Decompose(m:Matrix)
		{
			var s:Point = ScaleFromMat(m);
			m = m.clone();
			
			var sxUnit = s.x >= 0 ? 1 : -1;
			var syUnit = s.y >= 0 ? 1 : -1;
			
			m.scale(sxUnit, syUnit);
			
			//m.a /= sxUnit;
			//m.d /= syUnit;
			var rot = RotFromMat(m);
			return {sx:s.x, sy:s.y, rot:rot};
		}
		
		public static function RotFromMat(m:Matrix)
		{
			return Math.atan2(-m.c, m.a);
		}
		
		public static function ScaleFromMat(m:Matrix):Point
		{
			var xAxisX = m.a;
			var xAxisY = m.c;
			
			var yAxisX = m.b;
			var yAxisY = m.d;
			
			var sx = Math.sqrt(xAxisX * xAxisX + xAxisY * xAxisY);
			if (m.a < 0) sx *= -1;
			
			var sy = Math.sqrt(yAxisX * yAxisX + yAxisY * yAxisY);
			if (m.d < 0) sy *= -1;
			
			return new Point(sx, sy);
		}
		
		/*
		 * angle difference
		 *  (range -Math.PI to Math.PI)
		 */
		public static function GetAngleDiff(a, b)
		{
			var angleDiff = b - a;
			
			while (angleDiff > Math.PI)
			{
				angleDiff -= Math.PI * 2;
			}
			while (angleDiff < -Math.PI)
			{
				angleDiff += Math.PI * 2;
			}
			return angleDiff;
		}
			
		/*
		 * angle difference
		 *  (range 0 to Math.PI)
		 */
		public static function GetAngleDiffAbs(a, b)
		{
			var angleDiff = GetAngleDiff(a, b);
			while (angleDiff < 0)
			{
				angleDiff += Math.PI * 2;
			}
			
			if (angleDiff > Math.PI)
			{
				angleDiff = Math.PI * 2 - angleDiff;
			}
			
			return angleDiff;
		}
		
		public static function GetTransform(from:Matrix, to:Matrix)
		{
			var i:Matrix = from.clone();
			i.invert();
			var m:Matrix = to.clone();
			m.concat(i);
			return m;
		}
		
		public static function MatricesMatch(a:Matrix, b:Matrix)
		{
			if (Math.abs(a.a - b.a) > 0.001) return false;
			if (Math.abs(a.b - b.b) > 0.001) return false;
			if (Math.abs(a.c - b.c) > 0.001) return false;
			if (Math.abs(a.d - b.d) > 0.001) return false;
			if (Math.abs(a.tx - b.tx) > 0.001) return false;
			if (Math.abs(a.ty - b.ty) > 0.001) return false;
			return true;
		}
		
/*
 * START IMPORT PROGRAM (jsfl)
 */
		private static const TEMPLATE2:String = ( <![CDATA[



function _trace(str)
{
	fl.outputPanel.trace(str);
}

var templateFolder = "OriginalTemplatePieces";
var sampleAnimationsFolder = "SampleAnimations";

(function()
{
	var scriptPath = fl.scriptURI;
	var folderIndex = scriptPath.lastIndexOf("/");
	var scriptFolder = scriptPath.substr(0, folderIndex + 1);
	
	_strace(scriptFolder);
	
	var templatePath = scriptFolder + "template.txt";

	var template = FLfile.read(templatePath);
	var lines = template.split("\n");
	
	var name = lines.shift();
	
	lines.shift();
	
	var flaPath = scriptFolder + name + ".fla";
	
	if (FLfile.exists(flaPath))
	{
		alert("The file '" + name + ".fla' already exists. Did you already run the script? If you want to re-generate this project, delete '" + name + ".fla' and run this script again.");
		return;
	}
	
	var doc = fl.createDocument();
	var lib = doc.library;
	
	fl.saveDocument(doc, flaPath);
	
	lib.newFolder(templateFolder);
	
	var clipFolder = name + "Clips";
	var clipFolderNotExported = name + "Clips_NotExported";
	
	var exportedNames = {};
	
	var pieceNames = [];
	var line;
	while ((line = lines.shift()) != "")
	{
		var parts = line.split(" ");
		var pieceName = parts[0];
		pieceNames.push(pieceName);
		
		doc.importFile(scriptFolder + pieceName + ".png", true, false, false);
		lib.moveToFolder(templateFolder, pieceName + ".png", true);
		
		var clipName = "piece_" + pieceName + "_clip";
		
		var cx = Number(parts[1]);
		var cy = Number(parts[2]);
		
		var clipItem;
		if (!lib.itemExists(clipFolderNotExported + "/" + clipName))
		{
			if (!lib.itemExists(clipFolderNotExported))
			{
				lib.newFolder(clipFolderNotExported);
			}
			
			lib.addNewItem("movie clip", clipName);
			lib.editItem(clipName);
			
			var clipFile = pieceName + ".png";
			var index = lib.findItemIndex(templateFolder + "/" + clipFile);
			var clipPNGItem = lib.items[index];
			
			doc.addItem({x:0, y:0}, clipPNGItem);
			doc.selection[0].x = -cx;
			doc.selection[0].y = -cy;
			
			var tl = doc.getTimeline();
			tl.layers[0].name = "Template Piece";
			tl.layers[0].locked = true;
			tl.addNewLayer(pieceName, "normal", true);
			
			doc.exitEditMode();
			
			lib.moveToFolder(clipFolderNotExported, clipName, true);
		}
	}
	
	lib.addNewItem("movie clip", name);
	var index = lib.findItemIndex(name);
	var animationItem = lib.items[index];

	animationItem.linkageExportForAS = true;
	animationItem.linkageExportInFirstFrame = true;
	animationItem.linkageClassName = name;	
	
	lib.editItem(name);
	
	var inNewClip = true;
	var offsetX = 0;
	var offsetY = 0;
	while (inNewClip)
	{
		while ((line = lines.shift()) != "")
		{
			if (line == "end clip")
			{
				inNewClip = false;
				lines.shift();
				break;
			}
			
			var parts = line.split(" ");
			var pieceName = parts[1];
			//var cx = Number(parts[2]);
			//var cy = Number(parts[3]);
			var a = Number(parts[2]);
			var b = Number(parts[3]);
			var c = Number(parts[4]);
			var d = Number(parts[5]);
			var tx = Number(parts[6]);
			var ty = Number(parts[7]);
			
			var clipName = "piece_" + pieceName + "_clip";
			
			lib.editItem(name);
			
			if (!lib.itemExists(clipFolder + "/" + clipName))
			{
				if (lib.itemExists(clipFolderNotExported + "/" + clipName))
				{
					exportedNames[clipName] = true;
					
					if (!lib.itemExists(clipFolder))
					{
						lib.newFolder(clipFolder);
					}					
					
					lib.moveToFolder(clipFolder, clipFolderNotExported + "/" + clipName);
				}
			}
			
			if (lib.itemExists(clipFolder + "/" + clipName))
			{
				var index = lib.findItemIndex(clipFolder + "/" + clipName);
				clipItem = lib.items[index];
				
				doc.addItem({x:0, y:0}, clipItem);

				doc.selection[0].matrix =
					//fl.Math.concatMatrix(					
					//	{a:1, b:0, c:0, d:1, tx:-cx, ty:-cy},
						{a:a, b:b, c:c, d:d, tx:tx + offsetX, ty:ty};
					//);
				doc.selection[0].name = "piece_" + pieceName + "_clip";
			}
			else
			{
				_strace("Missing Piece " + clipName);
			}			
			
		}
		offsetX += 200;
	}
	
	doc.exitEditMode();
	
	lib.newFolder(sampleAnimationsFolder);
	
	//alert("Clips Imported");

	while (lines.length > 0)
	{
		var animDesc = lines.shift();
		var parts = animDesc.split(" ");
		if (parts.length != 3) break;
		var animName = parts[0];
		var animLength = parts[1];
		var animParts = parts[2];
		
		lib.addNewItem("movie clip", name + animName);
		lib.editItem(name + animName);
		
		var timeline = doc.getTimeline();
		timeline.layers[0].name = "depth" + 0;
		
		while (timeline.layerCount < animParts)
		{
			timeline.addNewLayer("depth" + timeline.layerCount, "normal", false);			
		}
		
		var parts = [];
		var partFrames = [];
		
		for (var i = 0; i < animParts; i++)
		{
			var partName = lines.shift();
			
			var clipName = "piece_" + partName + "_clip";
			var clipLoc = clipFolderNotExported + "/" + clipName;
			if (exportedNames[clipName])
			{
				clipLoc = clipFolder + "/" + clipName;
			}
			var index = lib.findItemIndex(clipLoc);
			var item = lib.items[index];
			
			parts.push(item);
			
			var frames = [];
			
			for (var k = 0; k < animLength; k++)
			{
				var frameInfo = lines.shift();

				var lineParts = frameInfo.split(" ");
				var info = {present:lineParts[0] == "1"};
				if (info.present)
				{
					info.index = i;
					info.depth = Number(lineParts[1]);
					info.transform = {
						a:Number(lineParts[2]),
						b:Number(lineParts[3]),
						c:Number(lineParts[4]),
						d:Number(lineParts[5]),
						tx:Number(lineParts[6]),
						ty:Number(lineParts[7]),
					};
				}
				
				frames.push(info);
			}
			
			partFrames.push(frames);
		}
		
		var lastOrder = "";
		
		for (var i = 0; i < animLength; i++)
		{
			var order = "";

			var usedParts = [];
			for (var k = 0; k < animParts; k++)
			{
				if (partFrames[k][i].present)
				{
					usedParts.push(partFrames[k][i]);
				}
			}
			usedParts.sort(function(a, b) { return a.depth - b.depth; });
			for (var j = 0; j < usedParts.length; j++)
			{
				order += usedParts[j].index + "|";
			}
			
			if (order != lastOrder)
			{								
				if (i != 0)
				{
					for (var j = 0; j < animParts; j++)
					{
						timeline.currentLayer = j;
						timeline.insertBlankKeyframe(i);
					}
				}
				
				timeline.currentFrame = i;
				
				for (var j = 0; j < usedParts.length; j++)
				{
					timeline.setSelectedLayers(animParts - j - 1, true);
					doc.addItem({x:0, y:0}, parts[usedParts[j].index]);
				}
				
				lastOrder = order;
				
			}
			else
			{
				for (var j = 0; j < animParts; j++)
				{
					timeline.currentLayer = j;
					timeline.insertKeyframe(i);
				}
			}
			
			for (var j = 0; j < usedParts.length; j++)
			{
				if (timeline.layers[animParts - j - 1].frames[i].elements.length == 0)
				{
					_strace("UHOH" + animParts + " " + i + " " + j + "    " + usedParts.length);
				}
				timeline.layers[animParts - j - 1].frames[i].elements[0].matrix = usedParts[j].transform;
			}
			
			//break;
		}
		
		lines.shift();
		
		timeline.currentFrame = 1;
		
		doc.exitEditMode();
		lib.moveToFolder(sampleAnimationsFolder, name + animName, true);
	}
	
	doc.save(true);
	
})();
		
		]]> ).toString();
/*
 * END PROGRAM
 */
	}
}