package classes.away3d.extrusions{
	
	import classes.away3d.arcane;
	import classes.away3d.animators.utils.*;
	import classes.away3d.core.base.*;
	import classes.away3d.core.geom.*;
	import classes.away3d.materials.*;
	import classes.away3d.primitives.*;
	
	import flash.geom.*;
		
	use namespace arcane;
	
	public class PathExtrusion extends AbstractPrimitive
	{
		private var varr:Array;
		private var xAxis:Vector3D = new Vector3D();
    	private var yAxis:Vector3D = new Vector3D();
    	private var zAxis:Vector3D = new Vector3D();
		private var _worldAxis:Vector3D = new Vector3D(0,1,0);
		private var _trans:Matrix3D = new Matrix3D();
		
		private var _path:Path;
		private var _profile:Array;
		private var _scales:Array;
		private var _rotations:Array;
		private var _materials:Array;
		private var _subdivision:int = 2;
		private var _coverAll:Boolean = true;
		private var _flip:Boolean = false;
		private var _mapfit:Boolean;
		private var _closePath:Boolean = false;
		private var _alignToPath:Boolean = true;
		private var _smoothScale:Boolean = true;
		private var _isClosedProfile:Boolean = false;
		private var _doubles:Array = [];
		private var _matIndex:int;
		private var _segIndex:int = -1;
		private var _segvstart:Number = 0;
		private var _segv:Number;
		
        private function orientateAt(target:Vector3D, position:Vector3D):void
        {
            zAxis = target.subtract(position);
            zAxis.normalize();
    
            if (zAxis.length > 0.1)
            {
                xAxis = _worldAxis.crossProduct(zAxis);
                xAxis.normalize();
    
                yAxis = xAxis.crossProduct(zAxis);
                yAxis.normalize();
    			
    			
    			var rawData:Vector.<Number> = _trans.rawData;
    			
                rawData[0] = xAxis.x;
                rawData[1] = xAxis.y;
                rawData[2] = xAxis.z;
    
                rawData[4] = -yAxis.x;
                rawData[5] = -yAxis.y;
                rawData[6] = -yAxis.z;
    
                rawData[8] = zAxis.x;
                rawData[9] = zAxis.y;
                rawData[10] = zAxis.z;
				
				_trans.rawData = rawData;
            }
        }
		
		private function generate(points:Array, offsetV:int = 0, closedata:Boolean = false):void
		{
			if (_materials.length)
				_matIndex = _materials.length;
			
			var uvlength:int = (points.length-1) + offsetV;
			
			for(var i:int = 0;i< points.length-1;++i){
				varr = [];
				extrudePoints( points[i], points[i+1], (1/uvlength)*((closedata)? i+(uvlength-1) : i), uvlength, ((closedata)? i+(uvlength-1) : i)/_subdivision);
				
				if(i ==0 && _isClosedProfile){
					_doubles = varr.concat();
				}
			}
			varr = null;
			_doubles = null;
		}
		
		
		private function extrudePoints(points1:Array, points2:Array, vscale:Number, indexv:int, indexp:int):void
		{
			var i:int;
			var j:int;
			var stepx:Number;
			var stepy:Number;
			var stepz:Number;
			
			var uva:UV;
			var uvb:UV;
			var uvc:UV;
			var uvd:UV;
			
			var va:Vertex;
			var vb:Vertex;
			var vc:Vertex;
			var vd:Vertex;
			
			var u1:Number;
			var u2:Number;
			var index:int = 0;
			
			var countloop:int = points1.length;
			
			if(_mapfit){
				var dist:Number = 0;
				var tdist:Number;
				var bleft:Vector3D;
				for(i = 0;i<countloop; ++i){
					for(j = 0;j< countloop; ++j){
						if(i != j){
							tdist = points1[i].distance(points1[j]);
							if(tdist>dist){
								dist = tdist;
								bleft = points1[i];
							}
						}
					}
				}
				 
				
			} else{
				var bu:Number = 0;
				var bincu:Number = 1/(countloop-1);
			}
			var v1:Number = 0;
			var v2:Number = 0;
			
			function getDouble(x:Number, y:Number, z:Number ):Vertex
			{
				for(var i:int = 0;i<_doubles.length; ++i){
					if( _doubles[i].x == x && _doubles[i].y == y && _doubles[i].z == z){
						return _doubles[i];
					}
				}
				return createVertex( x, y, z); 
			}
			 
			for( i = 0; i < countloop; ++i){
				stepx = points2[i].x - points1[i].x;
				stepy = points2[i].y - points1[i].y;
				stepz = points2[i].z - points1[i].z;
				
				for( j = 0; j < 2; ++j){
					if(_isClosedProfile &&  _doubles.length > 0){
						varr.push( getDouble(points1[i].x+(stepx*j) , points1[i].y+(stepy*j), points1[i].z+(stepz*j) )   );
					} else {
						varr.push( createVertex( points1[i].x+(stepx*j) , points1[i].y+(stepy*j), points1[i].z+(stepz*j)) );
					}
				}
			}
			
			var mat:Material;
			 
			if(!_coverAll){
				
				if(indexp>_segIndex){
					_segIndex = indexp;
					_segvstart = 0;
					_segv = 1/(_subdivision);
					if(_materials != null)
						_matIndex = (_matIndex+1 > _materials.length-1)? 0 : _matIndex+1;
				}
				
			} else{
				
				if(_materials!= null && !_coverAll)
						_matIndex = (_matIndex+1 > _materials.length-1)? 0 : _matIndex+1;
			}
			
			mat = (_materials!= null && !_coverAll)? _materials[_matIndex] : null;
			
			for( i = 0; i < countloop-1; ++i){
				
				if(_mapfit){
					 
					u1 = points1[i].distance(bleft)/dist;
					u2 = points1[i+1].distance(bleft)/dist;
					
				} else{
					u1 = bu;
					bu += bincu;
					u2 = bu;
				}
				 
				v1 = (_coverAll)? vscale : _segvstart;
				v2 = (_coverAll)? vscale+(1/indexv) : _segvstart + _segv;
				 
				uva = createUV( u1 , v1);
				uvb = createUV( u1 , v2 );
				uvc = createUV( u2 , v2 );
				uvd = createUV( u2 , v1 );

				va = varr[index];
				vb = varr[index+ 1];
				vc = varr[index+3];
				vd = varr[index+2];
				 
				if(flip){
					addFace(createFace(vb,va,vc, mat, uvb, uva, uvc ));
					addFace(createFace(vc,va,vd, mat, uvc, uva, uvd));
					
				}else{
					addFace(createFace(va,vb,vc, mat, uva, uvb, uvc ));
					addFace(createFace(va,vc,vd, mat, uva, uvc, uvd));
				}
				  
				if(_mapfit)
					u1 = u2;
				 
				index += 2;
			}
			
			if(!_coverAll)
				_segvstart += _segv;
						 
		}
		
		/**
		 * @inheritDoc
		 */
    	protected override function buildPrimitive():void
    	{
    		super.buildPrimitive();
    		
			if(_path != null && _path.length != 0 && _profile != null && _profile.length > 1){
				
				_worldAxis = _path.worldAxis;
				
				var aSegPoints:Array = PathUtils.getPointsOnCurve(_path, _subdivision);
				var aPointlist:Array = [];
				var aSegresult:Array = [];
				var atmp:Array;
				var tmppt:Vector3D = new Vector3D(0,0,0);
				 
				var i:int;
				var j:int;
				var k:int;
				
				var nextpt:Vector3D;
				
				if(_closePath)
					var lastP:Array = [];
				
				var rescale:Boolean = (_scales != null);
				if(rescale) var lastscale:Vector3D = (_scales[0] == null)? new Vector3D(1, 1, 1) : _scales[0];
					
				var rotate:Boolean = (_rotations != null);
				
				if(rotate && _rotations.length > 0){
					var lastrotate:Vector3D = _rotations[0] ;
					var nextrotate:Vector3D;
					var aRotates:Array = [];
					var tweenrot:Vector3D;
				}
				 
				if(_smoothScale && rescale){
					var nextscale:Vector3D = new Vector3D(1, 1, 1);
					var aScales:Array = [lastscale];
				}
				
				var tmploop:int = _profile.length;
				for (i = 0; i <aSegPoints.length; ++i) {
					if(rotate){
						lastrotate = (_rotations[i] == null) ? lastrotate : _rotations[i];
						nextrotate = (_rotations[i+1] == null) ? lastrotate : _rotations[i+1];
						aRotates = [lastrotate];
						aRotates = aRotates.concat(PathUtils.step( lastrotate, nextrotate,  _subdivision));
					}
					
					if(rescale)  lastscale = (_scales[i] == null)? lastscale : _scales[i];
					 
					if(_smoothScale && rescale ){
						nextscale = (_scales[i+1] == null) ? (_scales[i] == null)? lastscale : _scales[i] : _scales[i+1];
						aScales = aScales.concat(PathUtils.step( lastscale, nextscale, _subdivision));
					}
					
					
					for(j = 0; j<aSegPoints[i].length;++j){
						 
						atmp = [];
						atmp = atmp.concat(_profile);
						aPointlist = [];
						
						if(rotate)
							tweenrot = aRotates[j];

						if(_alignToPath) {
							_trans = new Matrix3D();
							if(i == aSegPoints.length -1 && j==aSegPoints[i].length-1){
								
								if(_closePath){
									nextpt = aSegPoints[0][0];
									orientateAt(nextpt, aSegPoints[i][j]);
								} else{
									nextpt = aSegPoints[i][j-1];
									orientateAt(aSegPoints[i][j], nextpt);
								}
								
							} else {
								nextpt = (j<aSegPoints[i].length-1)? aSegPoints[i][j+1]:  aSegPoints[i+1][0];
								orientateAt(nextpt, aSegPoints[i][j]);
							}
						}
						
						for (k = 0; k <tmploop; ++k) {
						
							if(_alignToPath) {
								tmppt = new Vector3D();
								tmppt.x = atmp[k].x * _trans.rawData[0] + atmp[k].y * _trans.rawData[4] + atmp[k].z * _trans.rawData[8] + _trans.rawData[12];
								tmppt.y = atmp[k].x * _trans.rawData[1] + atmp[k].y * _trans.rawData[5] + atmp[k].z * _trans.rawData[9] + _trans.rawData[13];
								tmppt.z = atmp[k].x * _trans.rawData[2] + atmp[k].y * _trans.rawData[6] + atmp[k].z * _trans.rawData[10] + _trans.rawData[14];
								
								if(rotate)
									tmppt = PathUtils.rotatePoint(tmppt, tweenrot);
								 
								tmppt.x +=  aSegPoints[i][j].x;
								tmppt.y +=  aSegPoints[i][j].y;
								tmppt.z +=  aSegPoints[i][j].z;
								
							} else {
								
								tmppt = new Vector3D(atmp[k].x+aSegPoints[i][j].x, atmp[k].y+aSegPoints[i][j].y, atmp[k].z+aSegPoints[i][j].z);
							}
							
							aPointlist.push(tmppt );
							
							if(rescale && !_smoothScale){
									tmppt.x *= lastscale.x;
									tmppt.y *= lastscale.y;
									tmppt.z *= lastscale.z;
							}
							
						}
						
						if(_closePath && i == aSegPoints.length-1 &&  j == aSegPoints[i].length -1) 
								break;
						
						if(_closePath)
							lastP = aPointlist;
								
						aSegresult.push(aPointlist);
						 
					}
					
				}
				 
				if(rescale && _smoothScale){
					for (i = 0; i < aScales.length; ++i) {
				
						 for (j = 0;j < aSegresult[i].length; ++j) {
							aSegresult[i][j].x *= aScales[i].x;
							aSegresult[i][j].y *= aScales[i].y;
							aSegresult[i][j].z *= aScales[i].z;
						 }
					}
					
					aScales = null;
				}
				
				if(rotate)
					aRotates = null;
				 
				if(_closePath){
					var stepx:Number;
					var stepy:Number;
					var stepz:Number;
					var c:Array;
					var c2:Array = [[]];
					 
					for( i = 1; i < _subdivision+1; ++i){
						c = [];
						for(j = 0; j < lastP.length; ++j){
							stepx = (aSegresult[0][j].x - lastP[j].x)/_subdivision;
							stepy = (aSegresult[0][j].y - lastP[j].y)/_subdivision;
							stepz = (aSegresult[0][j].z - lastP[j].z)/_subdivision;
							c.push( new Vector3D( lastP[j].x+(stepx*i) , lastP[j].y+(stepy*i), lastP[j].z+(stepz*i)) );
						}
						c2.push(c);
					}
					
					c2[0] = lastP;
					generate(c2, (_coverAll)? aSegresult.length : 0, _coverAll);
					c = c2 = null;
				}
				
				generate(aSegresult, (_closePath && _coverAll)? 1 : 0, (_closePath && !_coverAll));
				
				aSegPoints = null;
				varr = null;
				 
				type = "PathExtrude";
				url = "Extrude";
			
			} else {
				throw new Error("PathExtrude error: at least 2 Vector3D are required in points. Path definition requires at least 1 object with 3 parameters: {v0:Vector3D, va:Vector3D ,v1:Vector3D}, all properties being Vector3D.");
			} 
		}
		
		 /**
    	 * Defines the <code>Path</code> object representing path to extrude along. Required.
    	 * 
    	 * @see away3d.core.geom.Path
    	 */ 
		public function get path():Path
    	{
    		return _path;
    	}
		
		public function set path(val:Path):void
    	{
    		_path = val;
    		_primitiveDirty = true;
    	}
    	
		/**
    	 * Defines an Array of Vector3D objects representing the profile information to be projected along the Path object. Required.
    	 */
		public function get profile():Array
    	{
    		return _profile;
    	}
		
		public function set profile(val:Array):void
    	{
    		_profile = val;
    		
    		if (_profile != null)
				_isClosedProfile = (_profile[0].x == _profile[_profile.length-1].x && _profile[0].y == _profile[_profile.length-1].y && _profile[0].z == _profile[_profile.length-1].z);
			
			_primitiveDirty = true;
    	}
    	
		/**
    	 * An optional Array of <code>Vector3D</code> objects that defines a series of scales to be set on each PathCommand.
    	 */
		public function get scales():Array
    	{
    		return _scales;
    	}
    	
		public function set scales(val:Array):void
    	{
    		_scales = val;
    		_primitiveDirty = true;
    	}
		
		/**
    	 * An optional Array of <code>Vector3D</code> objects that defines a series of rotations to be set on each PathCommand.
    	 */
		public function get rotations():Array
    	{
    		return _rotations;
    	}
    	
		public function set rotations(val:Array):void
    	{
    		_rotations = val;
    		_primitiveDirty = true;
    	}
		
		/**
		 * An optional Array of <code>Vector3D</code> objects that defines a series of materials to be set on each PathCommand.
    	 */
		public function get materials():Array
    	{
    		return _materials;
    	}
    	
		public function set materials(val:Array):void
    	{
    		_materials = val;
    		_primitiveDirty = true;
    	}
				 
		/**
    	 * Defines the subdivisions created in the mesh for each PathCommand. Defaults to 2, minimum 2.
    	 */ 
		public function get subdivision():int
		{
			return _subdivision;
		}
		
		public function set subdivision(val:int):void
		{
			val = (val<2)? 2 : val;
			
			if (_subdivision == val)
				return;
			
			_subdivision = val;
			_primitiveDirty = true;
		}
		
		/**
    	 * Defines if the texture(s) should be stretched to cover the entire mesh or per step between segments. Defaults to true.
    	 */
		public function get coverAll():Boolean
		{
			return _coverAll;
		}
		
		public function set coverAll(val:Boolean):void
		{
			if (_coverAll == val)
				return;
			
			_coverAll = val;
			_primitiveDirty = true;
		}
		
		/**
    	 * Defines if the texture(s) should be projected on the geometry evenly spreaded over the source bitmapdata or using distance/percent. Default is false.
		 * Note that it is NOT suitable if a scale array is being used. The mapping considers first and last profile points are the most distant from each other. most left and most right on the map.
    	 */
		public function get mapfit():Boolean
		{
			return _mapfit;
		}
		
		public function set mapfit(val:Boolean):void
		{
			if (_mapfit == val)
				return;
			
			_mapfit = val;
			_primitiveDirty = true;
		}
		
		/**
    	 * Defines if the generated faces should be inversed. Default false.
    	 */
		public function get flip():Boolean
		{
			return _flip;
		}
		
		public function set flip(val:Boolean):void
		{
			if (_flip == val)
				return;
			
			_flip = val;
			_primitiveDirty = true;
		}
		
		/**
    	 * Defines if the last PathCommand should join the first one and close the loop. Defaults to false.
    	 */
		public function get closePath():Boolean
		{
			return _closePath;
		}
		
		public function set closePath(val:Boolean):void
		{
			if (_closePath == val)
				return;
			
			_closePath = val;
			_primitiveDirty = true;
		}
		
		/**
    	 * Defines if the array of profile points should be orientated on path or not. Default true. Note that Path object's worldaxis property might need to be changed. default = 0,1,0.
    	 * 
    	 * @see #profile
    	 */
		public function get aligntoPath():Boolean
		{
			return _alignToPath;
		}
		
		public function set alignToPath(val:Boolean):void
		{
			if (_alignToPath == val)
				return;
			
			_alignToPath = val;
			_primitiveDirty = true;
		}
		
		/**
    	 * Defines if a scaling of a PathCommand defined from the scales array of <code>Vector3D</code> objects should affect the whole PathCommand evenly or be smoothly interpolated from previous PathCommand scale. Defaults to true.
    	 * 
    	 * @see #scales
    	 */
		public function get smoothScale():Boolean
		{
			return _smoothScale;
		}
		
		public function set smoothScale(val:Boolean):void
		{
			if (_smoothScale == val)
				return;
			
			_smoothScale = val;
			_primitiveDirty = true;
		}
		
		/**
		 * Creates a new <code>PathExtrusion</code>
		 * 
		 * @param	path					Defines the <code>Path</code> object representing path to extrude along.
		 * @param	profile					Defines an Array of Vector3D objects representing the profile information to be projected along the Path object.
		 * @param 	scales		[optional]	An optional Array of <code>Vector3D</code> objects that defines a series of scales to be set on each PathCommand.
		 * @param 	rotations	[optional]	An optional Array of <code>Vector3D</code> objects that defines a series of rotations to be set on each PathCommand.
		 * @param 	init		[optional]	An initialisation object for specifying default instance properties.
		 */
		function PathExtrusion(path:Path = null, profile:Array = null, scales:Array = null, rotations:Array = null, init:Object = null)
		{
			super(init);
			
			this.path = path;
			this.profile = profile;
			this.scales = scales;
			this.rotations = rotations;
			
			materials = ini.getArray("materials");
			subdivision = ini.getInt("subdivision", 1, {min:1});
			coverAll = ini.getBoolean("coverAll", true);
			flip = ini.getBoolean("flip", false);
			closePath = ini.getBoolean("closePath", false);
			alignToPath = ini.getBoolean("alignToPath", true);
			smoothScale = ini.getBoolean("smoothScale", true);
			mapfit = ini.getBoolean("mapfit", false);
						
			type = "PathExtrusion";
			url = "extrusion";
		}
	}
}