using UnityEngine;
using System.Collections;

public class ThunderGeneratorFall : ThunderGenerator {
	
	
	// exposed
	public Transform  StartPoint = null;
    public GameObject TargetMesh = null;
	
	public float MovementSpeed 	   = 30.0f;
	public float MovementAmplitude = 1.0f;
	
	public float ThunderTreeLeafPosition = 0.5f;
	public int ThunderTreeConcentrate    = 5;
	
	// calss containing data for a leaf
	class ThunderLeafData
	{
		public ThunderLeafData oParent     = null;
		public ThunderLeafData[] oChildren = null;
		
		public int iDepth = 0;
		public Vector3 ThunderStart;
		public Vector3 ThunderEnd;
	}
	
	// current number of leaf ( in the end Nb leaf == NbStrike )
	int iNbTotalLeaf = 0;
	
	// leaf root ( parent of all leaf )
	ThunderLeafData oThunderLeafRoot = null;
	
	ThunderLeafData[] oThunderLeafArray;// make an array for easy acces to every leaf.
	
	// variable use to faster access to data
	Matrix4x4 MeshLocalSpaceTransform;
	Vector3[] MeshVertices;
	
	//-------------------------------------------------------------------
    public ThunderGeneratorFall() : base()
    {

    }
	
	//-------------------------------------------------------------------
	// initi thunder path
	public override void InitThunders()
	{
		MeshFilter oTargetMeshFilter = TargetMesh.GetComponent<MeshFilter>();
		
		#if UNITY_EDITOR
			// verification
	        if ( oThunder == null )
	        {
	            Debug.LogError("No oThunder call SetThunder(..)?");
	            return;
	        }
			
			if ( oThunderStrikeArray == null )
	        {
	            Debug.LogError("No oThunder call SetThunder(..)?");
	            return;
	        }
			
			if ( oTargetMeshFilter == null )
	        {
	            Debug.LogError("No MeshFilter component in target gameobject.");
	            return;
	        }
		#endif	
		
		MeshLocalSpaceTransform = TargetMesh.transform.localToWorldMatrix;
		MeshVertices = oTargetMeshFilter.mesh.vertices;
		
		// construct thunder	
		ConstructLeafStructure();
		ConstructEndPositionWithRandomVertex( oThunderLeafRoot, true );
		ConstructPath();
		
		StartCoroutine( RefreshEndPositionWithRandomVertex() );
	}
	
	//-------------------------------------------------------------------
	void ConstructLeafStructure()
	{
		oThunderLeafRoot = new ThunderLeafData();
		
		oThunderLeafArray 	= new ThunderLeafData[oThunderStrikeArray.Length];
		oThunderLeafArray[0] = oThunderLeafRoot;
			
		iNbTotalLeaf = 1;
		
		oThunderLeafRoot.iDepth = 0;
		oThunderLeafRoot.ThunderStart = StartPoint.position;
		
		// add leaf until we have all strike availabe taken
		while ( iNbTotalLeaf < oThunderStrikeArray.Length )
		{
			AddLeaf( oThunderLeafRoot );
		}
	}
	
	//-----------------------------------------------
	void AddLeaf( ThunderLeafData oParentLeaf )
	{
		if ( oParentLeaf.oChildren == null )
		{
			int iNewDepth 		= oParentLeaf.iDepth + 1;
			int iNbNewLeaf   	= iNewDepth * 2;
			
			iNbNewLeaf = Mathf.Min( iNbNewLeaf, oThunderStrikeArray.Length - iNbTotalLeaf );
			
			if ( iNbNewLeaf != 0 )
			{
				oParentLeaf.oChildren = new ThunderLeafData[iNbNewLeaf];
			
				for ( int i = 0; i < ( iNbNewLeaf ); i++ )
				{
					// init leaf
					oParentLeaf.oChildren[i] 		 = new ThunderLeafData();
					oParentLeaf.oChildren[i].oParent = oParentLeaf;
					oParentLeaf.oChildren[i].iDepth  = iNewDepth;
					
					oThunderLeafArray[ iNbTotalLeaf + i ] = oParentLeaf.oChildren[i];
				}	
				
				iNbTotalLeaf += iNbNewLeaf;
			}

		}
		else
		{
			for ( int i = 0; i < oParentLeaf.oChildren.Length; i++ )
			{
				AddLeaf( oParentLeaf.oChildren[i] );
			}	
		}
		
	}
	//-------------------------------------------------------------------
	// find a vertex near the vertex choosen by the parent ( more concentrate thunder ), use random to avoid going throw the entire array
	int GetRandomIndexOfVertexNearPosition( Vector3 vPosition )
	{		
		int iNearVertexIndex 	= Random.Range( 0, MeshVertices.Length);
		float iNearestDistance 	= Vector3.Distance( MeshLocalSpaceTransform.MultiplyPoint( MeshVertices[ iNearVertexIndex ] ), vPosition );
		
        for ( int i = 0; i < ThunderTreeConcentrate; i++ )// ( number of position taken randomly, to find the nearest position )
        {
			int iNewIndex 		= Random.Range( 0, MeshVertices.Length);
			float fNewDistance  = Vector3.Distance( MeshLocalSpaceTransform.MultiplyPoint( MeshVertices[ iNewIndex ] ), vPosition );
				
			if ( fNewDistance < iNearestDistance  )
			{
				iNearestDistance = fNewDistance;
				iNearVertexIndex = iNewIndex;
			}
        }
		
		return iNearVertexIndex;
	}

	//-------------------------------------------------------------------
	void ConstructEndPositionWithRandomVertex( ThunderLeafData oParentLeaf, bool bPropagateOnChildrens )
	{
		Vector3 vParentEndPosition;
		
		if ( oParentLeaf.oParent == null ) // for the root we take the start point
		{
			vParentEndPosition = StartPoint.position;
		}
		else
		{
			vParentEndPosition = oParentLeaf.oParent.ThunderEnd;
		}
		
		// find a vertex near the vertex choosen by the parent ( more concentrate thunder ), use random to avoid going throw the entire array
		oParentLeaf.ThunderEnd = MeshLocalSpaceTransform.MultiplyPoint( MeshVertices[ GetRandomIndexOfVertexNearPosition( vParentEndPosition ) ] );

		if ( bPropagateOnChildrens )
		{
			if ( oParentLeaf.oChildren != null ) 
			{
				for ( int i = 0; i < oParentLeaf.oChildren.Length; i++ )
				{
					ConstructEndPositionWithRandomVertex( oParentLeaf.oChildren[i], bPropagateOnChildrens ); 
				}
			}
		}
	}
	
	//-------------------------------------------------------------------
	// start position of every leaf is on the parent tunhder leaf.
	void ConstructStartPosition( ThunderLeafData oParentLeaf )
	{
		if ( oParentLeaf.oChildren != null ) // is final leaf
		{
			for ( int i = 0; i < oParentLeaf.oChildren.Length; i++ )
			{
				oParentLeaf.oChildren[i].ThunderStart = oParentLeaf.ThunderStart + ( oParentLeaf.ThunderEnd - oParentLeaf.ThunderStart ) * ThunderTreeLeafPosition;//* ( 0.7f + Random.Range( -0.3f, 0.3f) );
			}
			
			for ( int i = 0; i < oParentLeaf.oChildren.Length; i++ )
			{
				ConstructStartPosition( oParentLeaf.oChildren[i] );
			}
			
		}
		
	}
	
	// a coroutine, which periodically refresh targeted vertex of the mesh
	IEnumerator RefreshEndPositionWithRandomVertex()
	{
		while ( true )
		{
			ConstructEndPositionWithRandomVertex( oThunderLeafArray[ Random.Range( 0, oThunderLeafArray.Length ) ], false );
			yield return new WaitForSeconds( 0.03f );
		}
	}

	//-------------------------------------------------------------------
	void ConstructPath()
	{
		// update start position, in case an end position change ( if the end position change, the children leaf start position change )
		oThunderLeafRoot.ThunderStart = StartPoint.position;
		ConstructStartPosition( oThunderLeafRoot );
		
		
		// construct path based on tree
		for ( int i = 0; i < oThunderStrikeArray.Length; i++)
        {
			ThunderStrike oCurrentThunderStrinke = oThunderStrikeArray[i];
			
			// trace the path between each point (straight line)
	        for (int j = 0; j < oCurrentThunderStrinke.m_vPathPointArray.Length; j++)
	        {
	            float fd = ((float)j) / (float)(oCurrentThunderStrinke.m_vPathPointArray.Length - 1);
	
	            oCurrentThunderStrinke.m_vOriginPathPointArray[j] = new Vector3();
	            oCurrentThunderStrinke.m_vOriginPathPointArray[j] = oThunderLeafArray[i].ThunderStart * (1.0f - fd) + oThunderLeafArray[i].ThunderEnd * fd;
	
	            oCurrentThunderStrinke.m_vPathPointArray[j] = oCurrentThunderStrinke.m_vOriginPathPointArray[j];
				
				// add a thickness ( strike become thiner with tree depth )
				oCurrentThunderStrinke.m_fParticularThicknees = oThunder.fThunderThickness / ( oThunderLeafArray[i].iDepth + 1 );
	        }
		}
		
		
		// set light range
		oThunder.oLight.range = Vector3.Distance( StartPoint.position, TargetMesh.transform.position ) * 2.0f;	
		
	}
	
	//-------------------------------------------------------------------
	// update thunder
	public override void UpdateThunders()
	{
		#if UNITY_EDITOR
			// verification
	        if ( oThunder == null )
	        {
	            Debug.LogError("No oThunder call SetThunder(..)?");
	            return;
	        }
			
			if ( oThunderStrikeArray == null )
	        {
	            Debug.LogError("No oThunder call SetThunder(..)?");
	            return;
	        }
		#endif	
		
		MeshLocalSpaceTransform = TargetMesh.transform.localToWorldMatrix;
		
		ConstructPath();
		
        float timex = Time.time * MovementSpeed * 0.1365143f ;
        float timey = Time.time * MovementSpeed * 1.21688f ;
        float timez = Time.time * MovementSpeed * 2.5564f ;

		
		// skip end and begin, add a trembeling noise movement
		for ( int i = 0; i < oThunderStrikeArray.Length; i++ )
        {
			ThunderStrike oCurrentThunderStrinke = oThunderStrikeArray[i];
			
			float oCurrentPositionSum;
			
			int iPathlength = oCurrentThunderStrinke.m_vPathPointArray.Length;
			
			for ( int j = 0; j < iPathlength; j++ )
	        {				
				if ( ( j == 0 ) || ( j == ( iPathlength -1 ) ) )// skip first and last point for the random
				{
					oCurrentThunderStrinke.m_vPathPointArray[j] = oCurrentThunderStrinke.m_vOriginPathPointArray[j];
				}
				else
				{	
					// sum of coordinates change for each vertex -> use it with perlin noise to calculate an offest
					oCurrentPositionSum = i + oCurrentThunderStrinke.m_vOriginPathPointArray[j].x + oCurrentThunderStrinke.m_vOriginPathPointArray[j].y +oCurrentThunderStrinke.m_vOriginPathPointArray[j].z;
				
					Vector3 Offset = new Vector3(
				    oPerlinNoise.Noise(timex + oCurrentPositionSum),
	                oPerlinNoise.Noise(timey + oCurrentPositionSum),
	                oPerlinNoise.Noise(timez + oCurrentPositionSum));
				
					// more movement in the center of the electrical line than on start and end.
					// 0.0 < ((float)j / (float)( iPathlength - 1 )) < 1.0
					// Mathf.Abs( ((float)j / (float)( iPathlength - 1 )) - 0.5 ) -> value of 0.5 in the center and 1.0f in extremity
					float coeff = 0.5f - ( Mathf.Abs( ((float)j / (float)( iPathlength - 1 )) - 0.5f ));
					
					// calculate the final position = origin position + perturbation
	            	oCurrentThunderStrinke.m_vPathPointArray[j] = oCurrentThunderStrinke.m_vOriginPathPointArray[j] + Offset * MovementAmplitude * coeff;
					
				}
	        }
		}
		
		// update light
		oThunder.oLight.intensity = oThunder.fLightIntensity * ( 0.5f + 3.0f * oPerlinNoise.Noise( Time.time * MovementSpeed ) );
		
		// Ask to update vertice arrayon the next Thunder Update
		oThunder.bUpdateVertices = true;
	}

}
