using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Plane : MonoBehaviour {
	[Range(-1f, 1f)]
	public float steerRoll = 0f;
	[Range(-1f, 1f)]
	public float steerPitchXXX = 0f;
	[Range(-1f, 1f)]
	public float steerAltitude = 0f;
	[Range(0f, 1f)]
	public float SteerMult = 1f;
	public float SteerLerp = 1f;
	public float MaxVelocity = 2f;

	public float PredictTime = 1f;
	public int PredictSpheres = 10;


	public GameObject interiorHandle;
	public GameObject interior;

	Rigidbody body;
	Transform[] projectedPathSpheres;
	// Use this for initialization
	void Start () {
		var parent = new GameObject("Projected Path Spheres");
		projectedPathSpheres = new Transform[PredictSpheres];
		for(int i = 0; i<projectedPathSpheres.Length; i++) {
			var sphere = projectedPathSpheres[i] = GameObject.CreatePrimitive(PrimitiveType.Sphere).transform;
			sphere.parent = parent.transform;
			DestroyImmediate(sphere.GetComponent<Collider>());
			sphere.gameObject.layer = LayerMask.NameToLayer("ExteriorObject");
			var rend = sphere.GetComponent<Renderer>();
			rend.material.color = Color.green;	//TODO: dependent on 'i'

			//NOTE: Actually it's useful to see the shadow of the path
			//rend.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
		}
		body = GetComponent<Rigidbody>();
		body.velocity = transform.forward * MaxVelocity;

		PredictTime = Game.instance.beatPeriod;
	}
	

	public AnimationCurve liftByAngle;
	public AnimationCurve dragByAngle;

	public float dirByBodyVelLerp = 1f;
	void DrawF(Vector3 f, Color c) {
		Debug.DrawLine(transform.position, transform.position + f * 10f, c);
	}

	void FixedUpdate() {

		//https://www.khanacademy.org/partner-content/mit-k12/mit-k12-physics/v/the-forces-on-an-airplane
		//http://www.explainthatstuff.com/howplaneswork.html
		//So lift goes up with angle of attack (nose up) to a point of stall in which it decreases
		//https://en.wikipedia.org/wiki/Airfoil#/media/File:Lift_drag_graph.JPG


		var unrotRight = Vector3.ProjectOnPlane(transform.right, Vector3.up);
		var unrotFwd = Vector3.ProjectOnPlane(transform.forward, Vector3.up);
		Debug.DrawLine(transform.position, transform.position + unrotRight * 10f, Color.red);
		Debug.DrawLine(transform.position, transform.position + unrotFwd * 10f, Color.black);

		var dotRight = Vector3.Dot(transform.up, unrotRight);
		var dotFwd = Vector3.Dot(transform.up, unrotFwd);
		var angRight = (Mathf.Acos(dotRight) * Mathf.Rad2Deg - 90f);
		var angFwd = (Mathf.Acos(dotFwd) * Mathf.Rad2Deg - 90f);

		//Debug.Log("RANG=" + angRight);
		//Debug.Log("FANG=" + angFwd);


		//All these values are hacked to make it playable
		var weight = Vector3.down;
		//var lift = Vector3.Project(transform.up, Vector3.up);

		var lift = Vector3.up * (1f+Vector3.Dot(transform.up, Vector3.forward));// //* liftByAngle.Evaluate(angFwd)*0.1f;

		var thrust = transform.forward;
		var drag = transform.forward * Mathf.Min(0f, MaxVelocity - Vector3.Dot(transform.forward, body.velocity));
		//var drag = unrotFwd * dragByAngle.Evaluate(angFwd) * 0.1f;
		var force = lift + thrust + drag + weight;

		transform.rotation = Quaternion.Lerp(
			transform.rotation
			, Quaternion.LookRotation(body.velocity, transform.up)
			, dirByBodyVelLerp * Time.fixedDeltaTime
			);


		DrawF(weight, Color.gray);
		DrawF(lift, Color.magenta);
		DrawF(thrust, Color.blue);
		DrawF(drag, Color.cyan);

		
		force.y *= 9f;  //<-- "Hack" to make more realistic falling without needing to have fast forward speeds

		body.AddForce(force);
		//body.AddTorque(new Vector3(SteerPitch - Vector3.Dot(body.velocity, , 0f, SteerRoll));

		//TODO: calc torque pitch for altitude!

		var altitudeRange = 30f;
		var aimAltitude = Mathf.Lerp(0, altitudeRange, Mathf.InverseLerp(-1, 1, steerAltitude));
		var modPitch = (aimAltitude - transform.position.y)/altitudeRange;

		//*******************************************************
		//******* LAST MINUTE HARD CODING (F*CK REALISM!) *******
		//*******************************************************

		//Quaternion.RotateTowards(
		//	transform.rotation
		//	, Quaternion.LookRotation(Quaternion.Euler(transform.up * steerRoll) * transform.forward, transform.up)
		//	, Time.fixedDeltaTime * SteerLerp
		//	);

		//Debug.Log("MOD PITCH=" + modPitch);

		//modPitch *= 0.1f;
		//modPitch *= 1.5f;
		modPitch *= 2f;


		//Debug.Log("RDOT = " + dotRight.ToString("0.00"));
		//Debug.Log("FDOT = " + dotFwd.ToString("0.00"));
		//Debug.Log("ALT = " + transform.position.y);
		var torqueRoll = dotRight - steerRoll * SteerMult;
		////var torquePitch = -dotFwd + modPitch;
		var torquePitch = modPitch;//0f;//-dotFwd + modPitch;
		//var torque = new Vector3(torquePitch, 0f, torqueRoll);
		//body.AddRelativeTorque(torque);
		body.AddRelativeTorque(torqueRoll * Vector3.forward);
		//body.AddTorque(torquePitch * unrotRight, ForceMode.VelocityChange);
		body.AddTorque(torquePitch * unrotRight);


		var np = transform.position;
		if(np.x < -30) np.x = 480;
		if(np.x > 500) np.x = 0;
		if(np.z < -30) np.z = 480;
		if(np.z > 500) np.z = 0;
		if(np != transform.position)
			transform.position = np;
		//Debug.DrawLine(transform.position + unrotRight * 10f, transform.position + unrotRight * 10f + torque * 10f, Color.red);
		//Debug.DrawLine(transform.position + unrotFwd * 10f, transform.position + unrotFwd * 10f + torque * 10f, Color.black);

	}

	Vector3 goodPosition;
	Vector3 goodFwd;
	void OnTriggerEnter(Collider col) {
		if(col.CompareTag("Checkpoint")) {
			Destroy(col.gameObject);
			Game.instance.checkpoints++;
		}
			
	}

	void OnCollisionEnter(Collision col) {
		//Respawn
		if(col.collider.GetComponent<Terrain>() != null) {
			goodPosition.y = Mathf.Max(15f, goodPosition.y);
			transform.position = goodPosition;
			transform.rotation = Quaternion.LookRotation(Vector3.ProjectOnPlane(goodFwd, Vector3.up), Vector3.up);
			body.velocity = transform.forward * MaxVelocity;
			body.angularVelocity = Vector3.zero;
		}
	}

	// Update is called once per frame
	void Update () {

		//Save last good position when relatively un-tilted and ok
		if(Vector3.Dot(transform.up, Vector3.up) > 0.85f) {
			goodPosition = transform.position;
			goodFwd = transform.forward;
		}

		interior.transform.position = interiorHandle.transform.position;
		interior.transform.rotation = interiorHandle.transform.rotation;

		int i = 0;
		foreach(var line in PredictiveLine(projectedPathSpheres.Length)) {
			var p0 = line.Key;
			var p1 = line.Value;
			var sphere = projectedPathSpheres[i++];
			//sphere.position = transform.position + p1;
			sphere.position = p1;
		}
		//sphere.position = transform.position + velocity
	}
	

	IEnumerable<KeyValuePair<Vector3, Vector3>> PredictiveLine(int amt) {
		var pos = transform.position;
		var vel = body!=null ? body.velocity : transform.forward;
		Gizmos.color = Color.magenta;
		//var period = PredictTime - Time.time % PredictTime;
		var period = Game.instance ? Game.instance.nextBeatTime : 1f;
		for(int i = 0; i < amt; i++) {
			var next = pos + vel * period;
			period = PredictTime;

			yield return new KeyValuePair<Vector3, Vector3>(pos, next);
			pos = next;

			//FIXME: repeated code
			var weight = Vector3.down;
			var lift = transform.up;
			var thrust = transform.forward;
			var drag = transform.forward * Mathf.Min(0f, MaxVelocity - Vector3.Dot(transform.forward, vel));
			var accel = weight + lift + thrust + drag;
			vel += accel * PredictTime;
		}
	}

	public float rollAngle {
		get {
			var ang = -transform.localRotation.eulerAngles.z;
			if(ang < -180f) ang += 360f;
			return ang;
		}
	}

	void OnDrawGizmos() {
		var body = GetComponent<Rigidbody>();
		Gizmos.DrawLine(transform.position, transform.position+body.velocity);

		Gizmos.color = Color.magenta;
		foreach(var line in PredictiveLine(PredictSpheres)) {
			var p0 = line.Key;
			var p1 = line.Value;

			if(Input.GetKey(KeyCode.D)) {
				Debug.DrawLine(p0, p1, Color.cyan, 15f);
			}

			Gizmos.DrawLine(p0, p1);
			//Gizmos.DrawSphere(p1, 0.5f);
		}
		if(Vector3.Dot(transform.up, Vector3.up) <= 0.85f) {
			Gizmos.color = Color.red;
			Gizmos.DrawWireSphere(transform.position, 5f);
		}
	}


}
