package classes.away3d.loaders
{
    import classes.away3d.arcane;
    import classes.away3d.containers.*;
    import classes.away3d.core.base.*;
    import classes.away3d.core.utils.*;
    import classes.away3d.loaders.data.*;
	
	import classes.away3d.materials.BitmapFileMaterial;
	import classes.away3d.loaders.utils.CentralMaterialLibrary;
    
    import flash.events.*;
    import flash.net.*;
	
	import flash.geom.Matrix3D;
	import flash.geom.Vector3D;
	
	use namespace arcane;
	
    /**
    * File loader for the AC3D file format (.ac). 
	* This parser was written from AC3D 6.715 outputs. .ac file format version 11.
    * Untested in prior versions
	* Note: the "texture" tag url's might require some edits as AC3D doesn't offer a relative export
	*
	* If all textures are set into same folder, the parser supports a sourcespath variable.
	* url textures becomes then "sourcesPath/+[skiped part url found in file]filename.jpg"
	
		example:
		private function loadACFile():void
		{
			var loader:Loader3D = new Loader3D();
			loader.addOnSuccess(onLoaderSuccess);
			loader.addOnError(onLoaderError);
			
			var ACParser:AC3D = new AC3D({scaling:100, sourcesPath:"myImages"});
			loader.loadGeometry("exportedACfiles/bigroom.ac", ACParser);
		}
		
		private function onLoaderSuccess(e:Loader3DEvent):void 
		{
			var obj:Object3D = e.loader.handle;
			_scene.addChild(obj);
		}
		
		private function onLoaderError(e:Event):void 
		{
			trace("Error loading .ac file");
		}
	* 
	* unsupported tags: "MATERIAL", "numsurf","kids","crease","texrep","refs lines of","url","data" and "numvert lines of":
	*/
	
    public class AC3D extends AbstractParser
    {
		public function getVersionFromHex(char:String):int
		{
			switch (char) 
			{
				case "A": 
				case "a":           
					return 10;
				case "B":
				case "b":
					return 11;
				case "C":
				case "c":
					return 12;
				case "D":
				case "d":
					return 13;
				case "E":
				case "e":
					return 14;                
				case "F":
				case "f":
					return 15;
				default:
					return new Number(char);
			}    
		}

		private var mainContainer:ObjectContainer3D;
		private var activeContainer:ObjectContainer3D;
		 
    	/** @private */
        arcane override function prepareData(data:*):void
        {
			mainContainer = activeContainer = null;
			 
        	var ac:String = Cast.string(data);
        	 
			var lines:Array = ac.split('\n');
			if(lines.length == 1) lines = ac.split(String.fromCharCode(13));
			var trunk:Array;
			var materialList:Array = [];
			var materialIndexList:Array = [];
			var textureList:Array = [];
			var meshList:Array = [];
			var containersList:Array = [];
			var activeMesh:Mesh;
			var tmpos:Vector3D;
			var vertexes:Array;
			var uvs:Array;
			var nameid:String;
			var parsesV:Boolean;
			var isQuad:Boolean;
			var quadCount:int;
			var refscount:int;
			var invalidPoly:Boolean;
			var lastType:String = "";
			var tUrl:String = "";
			var matrix:Matrix3D;
			var kidsCount:int = 0;
			var m:Mesh;
			var cont:ObjectContainer3D;
			
			//version ac3d --> AC3D[b] --> hex value for file format
			var version:String = lines[0].substring(lines[0].length-1, lines[0].length);
			
			trace("AC3D file version: "+getVersionFromHex(version));
			lines.shift();

            for each (var line:String in lines)
            {
                trunk = line.replace("  "," ").replace("  "," ").replace("  "," ").split(" ");
				
                switch (trunk[0])
                {
					//XX Not used for now XX
					case "MATERIAL"://MATERIAL "ac3dmat1" rgb 1 1 1  amb 0.2 0.2 0.2  emis 0 0 0  spec 0.2 0.2 0.2  shi 128  trans 0
						materialList.push(line);//push the whole line for now
						break;
					case "numsurf"://integer
					case "crease"://45.000000. 
					case "texrep":// %f %f tiling
					case "refs lines of":
					case "url":
					case "data":
					case "numvert lines of":
						break;
					
					case "kids"://howmany children in the upcomming object. Probably need it later on, to couple with container/group generation
						kidsCount = parseInt(trunk[1]);
						break;
					
					case "OBJECT":
					
						if(activeMesh != null){
							CentralMaterialLibrary.addMaterial(activeMesh.material, activeMesh, activeMesh.name, textureList[textureList.length-1]);
							buildMeshGeometry(activeMesh, vertexes, uvs , tmpos);
							tmpos = null;
							activeMesh = null;
						}
						
						if(trunk[1] == "world"){
							if(mainContainer == null){
								mainContainer = new ObjectContainer3D();
								_container = mainContainer;
								mainContainer.name = "ac_"+containersList.length;
								containersList.push(mainContainer);
								lastType = "world";
							}
							activeContainer = mainContainer;
						}
						
						if(trunk[1] == "poly"){
							m = new Mesh();
							//lets add geom+matrix, probably useless, but now I know for sure its there...
							m.geometry = new Geometry();
							matrix = new Matrix3D();
						 	matrix.rawData = Vector.<Number>([	1,0,0,0,
																					0,1,0,0,
																					0,0,1,0,
																					0,0,0,1]);
							m.transform = matrix;
							 
							vertexes = [];
							uvs = [];
							
							activeMesh = m;
							activeContainer.addChild(activeMesh);
							//add default name in case none is provided few lines down -->todo check uniqueNames
							m.name = "m_"+meshList.length;
							meshList.push(m);
							parsesV = true;//in case of groups, numvert might not be there
							lastType = "poly";
						}
						
						if(trunk[1] == "group"){
							cont = new ObjectContainer3D();
							activeContainer.addChild(cont);
							cont.name = "c_"+containersList.length;
							containersList.push(cont);
							activeContainer = cont;
							lastType = "group";
						}
						
						break;
					
					case "name":
						nameid = line.substring(6, line.length-1);
						
						if(lastType == "poly"){
							activeMesh.name = nameid;
						} else{
							activeContainer.name = nameid;
						}
						break;
					
					case "numvert":// count vertices in mesh
						parsesV = true;
						break;
					
					case "SURF"://0x30
						if(invalidPoly)
							invalidPoly = false;
						break;
						
					case "refs":
						refscount = parseInt(trunk[1]);
						if(refscount == 4){
							isQuad = true;
							quadCount = 0;
						} else if( refscount<3 || refscount > 4){
							trace("Unsupported polygon type with "+refscount+" sides. Triangulate in AC3D");
							invalidPoly = true;
						} else{
							isQuad = false;
						}
						parsesV = false;
						break;
					
					case "mat":
						materialIndexList.push(trunk[1]);
						break;
 
					case "texture":
						//removing the quotes
						tUrl = trunk[1].replace("\"", "");
						tUrl = unescape(tUrl);
						
						if(sourcesPath != "")
							tUrl = resolveUrl(tUrl);
						
						textureList.push(tUrl);//once we know more, we can register to centralLib
						activeMesh.material = new BitmapFileMaterial(tUrl);
						break;
					
					case "loc"://%f %f %f
						/*
						The translation of the object.  Effectively the definition of the centre of the object.  This is
						relative to the parent - i.e. not a global position.  If this is not found then
						the default centre of the object will be 0, 0, 0.
						*/
						tmpos = new Vector3D(parseFloat(trunk[1])*scaling, parseFloat(trunk[2])*scaling, parseFloat(trunk[3])*scaling);

					case "rot"://%f %f %f  %f %f %f  %f %f %f
						/*The 3x3 rotation matrix for this objects vertices.  Note that the rotation is relative
						to the object's parent i.e. it is not a global rotation matrix.  If this token
						is not specified then the default rotation matrix is 1 0 0, 0 1 0, 0 0 1 */
						//Not required as ac 3d applys rotation to vertexes during export
						//Might be required for containers later on
						//matrix = new Matrix3D();
						//3.5.3 & >
						/*matrix.rawData = Vector.<Number>([parseFloat(trunk[1]),parseFloat(trunk[2]),parseFloat(trunk[3]),0,
																			parseFloat(trunk[4]),parseFloat(trunk[5]),parseFloat(trunk[6]),0,
																			parseFloat(trunk[7]),parseFloat(trunk[8]),parseFloat(trunk[9]),0,
																			0,0,0,1]);*/
						//to do : <3.5.3 and Prefab version
						/*matrix.sxx = parseFloat(trunk[1]);
						matrix.sxy = parseFloat(trunk[2]);
						matrix.sxz = parseFloat(trunk[3]);
						matrix.tx = 0;
						matrix.syx = parseFloat(trunk[4]);
						matrix.syy = parseFloat(trunk[5]);
						matrix.syz = parseFloat(trunk[6]);
						matrix.ty = 0;
						matrix.szx = parseFloat(trunk[7]);
						matrix.szy = parseFloat(trunk[8]);
						matrix.szz = parseFloat(trunk[9]);
						matrix.tz = 1;*/
						
						//activeMesh.transform = matrix;
						
						break;
	 					
                     default:
					 	if(trunk[0] == "" || invalidPoly)
							break;
							
					 	if(parsesV){
							//v0, v1, v2 --> x mirrored for right to left handed system
							vertexes.push(new Vertex(-(parseFloat(trunk[0]))* scaling, parseFloat(trunk[1])* scaling, parseFloat(trunk[2])* scaling));
							
						} else{

							if(isQuad){
								quadCount++;
								if(quadCount == 4){
									uvs.push(uvs[uvs.length-2], uvs[uvs.length-1]);
									uvs.push(parseInt(trunk[0]), new UV(parseFloat(trunk[1]), parseFloat(trunk[2])));
									uvs.push(uvs[uvs.length-10], uvs[uvs.length-9]);
									
								} else{
									uvs.push(parseInt(trunk[0]), new UV(parseFloat(trunk[1]), parseFloat(trunk[2])));
								}
								
							} else {
								
								uvs.push(parseInt(trunk[0]), new UV(parseFloat(trunk[1]), parseFloat(trunk[2])));
							}
						}
						
				}
			}
			
			if(activeMesh != null){
				CentralMaterialLibrary.addMaterial(activeMesh.material, activeMesh, activeMesh.name, textureList[textureList.length-1]);
				buildMeshGeometry(activeMesh, vertexes, uvs, tmpos);
				tmpos = null;
				activeMesh = null;
			}
			
			materialList = materialIndexList = textureList = meshList = containersList = [];
			
        }
		private function resolveUrl(fileurl:String):String
		{
			var split:Array = fileurl.split("/");
			var extra:String = (sourcesPath.substring(sourcesPath.length-1, sourcesPath.length) == "/")? "" : "/" ;
			return sourcesPath+extra+split[split.length-1];
		}
		
		private function buildMeshGeometry(activeMesh:Mesh, vertexes:Array, uvs:Array, tmpos:Vector3D = null):void
		{
			var uv0:UV;
			var uv1:UV;
			var uv2:UV;
			
			for (var i:int = 0;i<uvs.length;i+=6){
				uv0 = uvs[i+1];
				uv1 = uvs[i+3];
				uv2 = uvs[i+5];
				
				activeMesh.geometry.addFace(new Face(vertexes[uvs[i]], vertexes[uvs[i+2]],vertexes[uvs[i+4]], null, uv0, uv1, uv2) );
			}
			
			if(tmpos != null){
				activeMesh.x = tmpos.x;
				activeMesh.y = tmpos.y;
				activeMesh.z = tmpos.z;
			}
		}
		 
		/** @private */
        protected override function getFileType():String
        {
        	return "Ac3d";
        }
        
    	/**
    	 * A scaling factor for all geometry in the model. Defaults to 1.
    	 */
        public var scaling:Number;
		
		/**
    	* An optional unique location url for sources. url textures becomes composed with "sourcesPath/+[skiped url in file]filename.jpg"
    	*/
		public var sourcesPath:String;
		
		/**
		 * Creates a new <code>AC3D</code> object.
		 * 
		 * @param	init	[optional]	An initialisation object for specifying default instance properties.
		 * 
		 * @see away3d.loaders.AC3D#parse()
		 * @see away3d.loaders.AC3D#load()
		 */
		public function AC3D(init:Object = null)
        {	
			super(init);

			sourcesPath = ini.getString("sourcesPath", "");
			scaling = ini.getNumber("scaling", 1);
			binary = false;
        }

		/**
		 * Creates a 3d mesh object from the raw ascii data of a .ac file (AC3D).
		 * 
		 * @param	data				The ascii data of a loaded file.
		 * @param	init				[optional]	An initialisation object for specifying default instance properties.
		 * 
		 * @return						An Object3D representating the ac file.
		 */
        public static function parse(data:*, init:Object = null):Object3D
        {
            return Loader3D.parse(data, AC3D, init).handle;
        }
    	
    	/**
    	 * Loads and parses a .ac file into an Object3D object.
    	 * 
    	 * @param	url					The url location of the file to load.
    	 * @param	init				[optional]	An initialisation object for specifying default instance properties.
    	 * 
    	 * @return						A 3d loader object that can be used as a placeholder in a scene while the file is loading.
    	 */
        public static function load(url:String, init:Object = null):Loader3D
        {
            return Loader3D.load(url, Obj, init);
        }
    }
}