using UnityEngine;
using System.Collections;

public class TerrainGenerator : MonoBehaviour
{
	public static MoveCameraUp CamController	= null;

	public const float FLYING_OBJ_MIN_DIST	= 80.0f;

	public const float OBJ_DISTANCE			= 150.0f;
	public const float CLOUDS_DISTANCE		= 170.0f;

	public const float BACKGROUND_SIZE		= 500;
	public const float BACKGROUND_SIZE_H	= BACKGROUND_SIZE * 0.5f;

	public int TILES_NUM					= 14;

	public const int OBJ_RANDOM_NUM			= 2;
	
	public const float CAM_LEFT_OFF			= 300.0f;
	public const float CAM_RIGHT_OFF		= 700.0f;

	public const int BRANCH_NORMAL			= 0;
	public const int BRANCH_BROKEN			= 1;
	public const int BRANCH_ROTTEN			= 2;
	public const int BRANCH_RUBBER			= 3;
	public const int BRANCHES_TYPES			= 4;

	public const float MAX_CAM_DIST			= 15.0f;

	public Camera m_cam						= null;

	public float CloudsSpawnTime			= 5;
	public float RandomObjSpawnTime			= 15;

	public float MinBranchPerTrunk			= 6;
	public float MaxBranchPerTrunk			= 8;

	public float MinBranchSeparation		= 2;
	public float MaxBranchSeparation		= 4;

	public float FirstLevelMeters			= 180;
	public float NextLevelMetersMult		= 2.5f;

	public float BranchReductionPerLevel	= 0.8f;
	public float BranchSeparationPerLevel	= 1.2f;

	public float CamMultyAccePerLevel		= 1.2f;

	public float NormalBranchProb			= 0.8f;
	public float BrokenBranchProb			= 0.5f;
	public float RottenBranchProb			= 0.5f;

	public float NormalBranchProbChange		= 0.85f;
	public float BrokenBranchProbChange		= 1.15f;
	public float RottenBranchProbChange		= 1.15f;

	public int MinStarsPerTrunk				= 1;
	public int MaxStarsPerTrunk				= 4;
	public float MaxStartSepFromBranch		= 2.5f;

	private float m_startPosX				= 500.0f;
	private float m_lastTrunkY				= 0.0f;
	private float m_lastTrunkSize			= 0.0f;
	private float m_monkeyZ					= 0.0f;

	private ArrayList m_trunks				= null;
	private ArrayList m_stars				= null;
	private ArrayList m_clouds				= null;
	private ArrayList m_objs				= null;

	private bool m_branchsEnabled			= false;

	private float m_totTime					= 0.0f;
	private float m_nextTime				= 0.0f;

	private float m_objTotTime				= 0.0f;
	private float m_objNextTime				= 0.0f;

	private float m_lastBackY				= 250.0f;
	private float m_nextbackY				= 750.0f;
	
	private int m_curLvl					= 0;

	void Start()
	{
		Random.seed = 0xFFFFFC;

		m_trunks	= new ArrayList();
		m_stars		= new ArrayList();
		m_clouds	= new ArrayList();
		m_objs		= new ArrayList();

		GameObject Monkey = GameObject.Find( "Player" );

		if ( null != Monkey )
		{
			Player.TerrainGen = this;
			m_monkeyZ = Monkey.transform.position.z;
		}

		if ( null != m_cam )
		{
			CamController = m_cam.GetComponent<MoveCameraUp>();
		}

		SpawnBackground(true);
		SpawnTrunk( "obj_tile_0", false, false );
		SpawnTrunk( "obj_tile_1", true, false );
		SpawnTrunk( "obj_tile_2", true, false );
		SpawnTrunk( "obj_tile_1", true, false );
		SpawnTrunk( "obj_tile_2", true, false );
		SpawnTrunk( "obj_tile_1", true, false );
		SpawnTrunk( "obj_tile_2", true, false );

		GameObject wp = GameObject.Find( "obj_woodpecker" );
		if ( null != wp )
		{
			wp.GetComponent<Woodpecker>().Begin();
		}
	}

	void Update()
	{
		CloudsUpdate();
		ObjectUpdate();
		TrunksUpdate();
		BackgroundsUpdate();
		CheckOffScreen();
		LevelCheck();
	}

	void TrunksUpdate()
	{
		if ( ( m_cam.transform.position.y + MAX_CAM_DIST ) > m_lastTrunkY )
		{
			SpawnTrunk();
		}
	}

	void SpawnTrunk()
	{
		int num = Random.Range( 1, TILES_NUM + 1 );

		if ( !SpawnTrunk( "obj_tile_" + num, true, true ) )
		{
			SpawnTrunk();
		}
	}

	bool SpawnTrunk( string which, bool addBranches, bool addStars )
	{
		GameObject Obj = Instantiate( Resources.Load( which ) ) as GameObject;

		if ( null != Obj )
		{
			Difficulty Dfc = Obj.GetComponent<Difficulty>();

			if ( Dfc.difficulty == m_curLvl - 1 || Dfc.difficulty == m_curLvl )
			{
				Obj.transform.position = new Vector3( m_startPosX, m_lastTrunkY, m_monkeyZ + 1.0f );

				Trunk Trk = Obj.GetComponentInChildren<Trunk>();

				m_lastTrunkSize		= Trk.Obj().collider.bounds.size.y; // fix collider
				m_lastTrunkY		+= m_lastTrunkSize;

				m_trunks.Add( Obj );

				if ( addStars )
				{
					AddStars();
				}

				Woodpecker[] Pckr = Obj.GetComponentsInChildren<Woodpecker>();

				if ( Pckr.Length > 0 )
				{
					for ( int i = 0; i < Pckr.Length; i++ )
					{
						Pckr[i].Begin();
					}
				}

				return true;
			}

			DestroyObject( Obj );
		}
		else
		{
			Debug.Log("Trunk not instantiated. Failed to load.");
		}

		return false;
	}

	void CheckOffScreen()
	{
		if ( null != m_trunks && m_trunks.Count > 0 )
		{
			int i;
			GameObject Go = null;

			for ( i = 0; i < m_trunks.Count; i++ )
			{
				Go = ( m_trunks[i] as GameObject );

				if (
					null != Go &&
					Go.transform.position.y < m_cam.transform.position.y - 15.0f)
				{
					m_trunks.RemoveAt(i);
					DestroyObject(Go);
				}
			}

			Go = null;

			for ( i = 0; i < m_stars.Count; i++ )
			{
				Go = m_stars[i] as GameObject;

				if (
					null != Go &&
					Go.transform.position.y < m_cam.transform.position.y - MAX_CAM_DIST )
				{
					m_stars.RemoveAt( i );
					DestroyObject( Go );
				}
			}

			for ( i = 0; i < m_clouds.Count; i++ )
			{
				( m_clouds[i] as Cloud ).Update();
			}

			for ( i = 0; i < m_objs.Count; i++ )
			{
				( m_objs[i] as RandomObj ).Update();
			}
		}
	}

	public void BranchsEnable()
	{
		if ( !m_branchsEnabled )
		{
			for ( int i = 0; i < m_trunks.Count; i++ )
			{
				GameObject Obj = m_trunks[i] as GameObject;

				BranchEvents[] Gos = Obj.GetComponentsInChildren<BranchEvents>();

				if ( null != Gos && Gos.Length > 0 )
				{
					for ( int z = 0; z < Gos.Length; z++ )
					{
						Gos[z].TriggerEnable();
					}
				}

				m_branchsEnabled = false;
			}
		}
	}

	public void BranchsDisable()
	{
		if ( m_branchsEnabled )
		{
			for ( int i = 0; i < m_trunks.Count; i++ )
			{
				GameObject Obj = m_trunks[i] as GameObject;
				
				BranchEvents[] Gos = Obj.GetComponentsInChildren<BranchEvents>();

				if ( null != Gos && Gos.Length > 0 )
				{
					for ( int z = 0; z < Gos.Length; z++ )
					{
						Gos[z].TriggerDisable();
					}
				}
				
				m_branchsEnabled = false;
			}
		}
	}

	void SpawnStar()
	{
		GameObject Obj = Instantiate( Resources.Load( "obj_star" ) ) as GameObject;

		GameObject Trunk = m_trunks[ m_trunks.Count - 1 ] as GameObject;

		BranchEvents[] Gos = Trunk.GetComponentsInChildren<BranchEvents>();

		if ( null != Gos && Gos.Length > 0 )
		{
			BranchEvents Branch = ( Gos[Random.Range( 0, Gos.Length )] as BranchEvents );

			Obj.transform.position = new Vector3(
				Branch.Obj().transform.position.x + Random.Range( -( Branch.Obj().collider.bounds.size.x ), ( Branch.Obj().collider.bounds.size.x ) ),
				Branch.Obj().transform.position.y + Random.Range( 0.0f, MaxStartSepFromBranch ),
				m_monkeyZ
			);

			m_stars.Add( Obj );
		}
		else
		{
			DestroyObject( Obj );
		}
	}

	void BackgroundsUpdate()
	{
		if ( m_cam.transform.position.y >= m_lastBackY )
		{
			SpawnBackground();
		}
	}

	void ObjectUpdate()
	{
		m_objTotTime += Time.deltaTime;

		if ( m_objTotTime > m_objNextTime )
		{
			m_objTotTime = 0;
			m_objNextTime = RandomObjSpawnTime;

			CreateRandomObj();
		}
	}

	void CreateRandomObj()
	{
		GameObject Obj = Instantiate( Resources.Load( "obj_random_" + Random.Range( 1, OBJ_RANDOM_NUM + 1 ) ) ) as GameObject;

		float side;
		float startX;

		if ( Random.Range( 0.0f, 1.0f ) < 0.5f )
		{
			startX = CAM_LEFT_OFF;
			side = 1;
		}
		else
		{
			side = -1;
			startX = CAM_RIGHT_OFF;
		}

		Obj.transform.position = new Vector3( startX, m_lastTrunkY, OBJ_DISTANCE );

		m_objs.Add( new RandomObj( Obj, side, Random.Range( 15.0f, 25.0f ) ) );
	}

	void CloudsUpdate()
	{
		m_totTime += Time.deltaTime;

		if ( m_totTime > m_nextTime )
		{
			m_totTime = 0;
			m_nextTime = CloudsSpawnTime;

			CreateCloud();
		}
	}

	void CreateCloud()
	{
		GameObject Obj = Instantiate( Resources.Load( "obj_cloud" ) ) as GameObject;

		float side;
		float startX;

		if ( Random.Range( 0.0f, 1.0f ) < 0.5f )
		{
			startX = CAM_LEFT_OFF;
			side = 1;
		}
		else
		{
			side = -1;
			startX = CAM_RIGHT_OFF;
		}

		Obj.transform.position = new Vector3( startX, m_lastTrunkY, CLOUDS_DISTANCE );

		m_clouds.Add( new Cloud( Obj, side, Random.Range( 15.0f, 25.0f ) ) );
	}

	void AddStars()
	{
		int num = Mathf.RoundToInt( Random.Range( MinStarsPerTrunk, MaxStarsPerTrunk ) );

		for ( int i = 0; i < num; i++ )
		{
			SpawnStar();
		}
	}

	void SpawnBackground()
	{
		SpawnBackground( false );
	}

	void SpawnBackground( bool first )
	{
		if ( first )
		{
			GameObject Obj = Instantiate( Resources.Load( "obj_back_floor" ) ) as GameObject;
			Obj.transform.position = new Vector3( BACKGROUND_SIZE, BACKGROUND_SIZE_H, BACKGROUND_SIZE_H );
		}
		else
		{
			GameObject Obj = Instantiate( Resources.Load( "obj_back" ) ) as GameObject;
			Obj.transform.position = new Vector3( BACKGROUND_SIZE, m_nextbackY, BACKGROUND_SIZE_H );

			m_lastBackY = m_nextbackY;
			m_nextbackY += BACKGROUND_SIZE;
		}
	}
	public void RemoveRandomObj( RandomObj Obj )
	{
		m_objs.Remove( Obj );
		DestroyObject( Obj.Obj() );
	}

	public void RemoveCloud( Cloud Cl )
	{
		m_clouds.Remove( Cl );
		DestroyObject( Cl.Obj() );
	}

	void LevelUp()
	{
		Debug.Log( "Level Up" );
		
		m_curLvl++;

		FirstLevelMeters *= NextLevelMetersMult;

		MinBranchPerTrunk *= BranchReductionPerLevel;
		MaxBranchPerTrunk *= BranchReductionPerLevel;

		MinBranchSeparation *= BranchSeparationPerLevel;
		MaxBranchSeparation *= BranchSeparationPerLevel;

		NormalBranchProb *= NormalBranchProbChange;
		BrokenBranchProb *= BrokenBranchProbChange;
		RottenBranchProb *= RottenBranchProbChange;

		if ( CamController != null )
		{
			CamController.CamAcceleration *= CamMultyAccePerLevel;
		}
	}

	void LevelCheck()
	{
		if ( m_cam.transform.position.y > FirstLevelMeters )
		{
			LevelUp();
		}
	}

	/**
	* @return The 3 closest trunks
	*/
	public ArrayList GetNearestTrunks( GameObject _from )
	{
		ArrayList Arr = new ArrayList();
		GameObject closest = null;

		if ( null != _from )
		{
			while ( Arr.Count < 3 )
			{
				float MaxDist = 999;

				for ( int i = 0; i < m_trunks.Count; i++ )
				{
					GameObject Obj = m_trunks[i] as GameObject;
					float tDist = Vector3.Distance( Obj.transform.position, _from.transform.position );

					if ( tDist < MaxDist && !Arr.Contains( Obj ) )
					{
						closest = Obj;
						MaxDist = tDist;
					}
				}

				Arr.Add( closest );
			};
		}
		else
		{
			Debug.Log( "_from is null" );
		}

		if ( Arr.Count == 0 )
		{
			Debug.Log( "GetNearestTrunks returned null" );
		}
		
		return Arr;
	}

	/**
	* @return The second nearest branch to object, discarting the first one, that assume to be the current one.
	*/
	public GameObject GetSecondNearestBranch( GameObject _from )
	{
		GameObject Obj		= null;
		GameObject Closest	= null;

		if ( null != _from )
		{
			float MaxDist = 9999;
			ArrayList Trunks = GetNearestTrunks( _from );

			for ( int i = 0; i < Trunks.Count; i++ )
			{
				BranchEvents[] Branchs = ( Trunks[i] as GameObject ).GetComponentsInChildren<BranchEvents>();

				if ( Branchs.Length == 0 )
				{
					Debug.Log( "No branchs in the trunk" );
				}

				for ( int z = 0; z < Branchs.Length; z++ )
				{
					float tDist = Vector3.Distance( _from.transform.position, Branchs[z].Obj().transform.position );

					if (
						_from.transform.position.y < Branchs[z].Obj().transform.position.y &&
						( _from.transform.position.x != Branchs[z].Obj().transform.position.x && _from.transform.position.y != Branchs[z].Obj().transform.position.y ) &&
						tDist < MaxDist
					)
					{
						Obj		= Closest;
						Closest = Branchs[z].Obj();
						MaxDist = tDist;
					}
				}
			}
		}
		else
		{
			Debug.Log( "_from is null" );
		}

		if ( Closest != null && Obj == null )
		{
			Obj = Closest;
		}

		return Obj;
	}

	public GameObject GetNearestBranch( GameObject _from )
	{
		GameObject Obj		= null;

		if ( null != _from )
		{
			float MaxDist = 9999;
			ArrayList Trunks = GetNearestTrunks( _from );

			for ( int i = 0; i < Trunks.Count; i++ )
			{
				BranchEvents[] Branchs = ( Trunks[i] as GameObject ).GetComponentsInChildren<BranchEvents>();

				if ( Branchs.Length == 0 )
				{
					Debug.Log( "No branchs in the trunk" );
				}

				for ( int z = 0; z < Branchs.Length; z++ )
				{
					float tDist = Vector3.Distance( _from.transform.position, Branchs[z].Obj().transform.position );

					if ( tDist < MaxDist )
					{
						Obj = Branchs[z].Obj();
						MaxDist = tDist;
					}
				}
			}
		}
		else
		{
			Debug.Log( "_from is null" );
		}

		return Obj;
	}
}
