using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;

public class CamController : MonoBehaviour {

	[System.Serializable]
	public class PositionSettings {
		public Vector3 targetPosOffset = new Vector3 (0f, 3.4f, 0);
		public float distanceFromTarget = -8f, maxZoom = -2f, minZoom = 15f, smooth = 0.05f;
		public bool smoothFollow = true;

		[HideInInspector]
		public float newDistance = -8f; // set by zoom input
		[HideInInspector]
		public float adjustmentDistance = -8f;
	}

	[System.Serializable]
	public class OrbitSettings {
		public float xRotation = -20f, yRotation = -180f, maxXRotation = 25f, minXRotation = -50f, vOrbitSmooth = 0.5f, hOrbitSmooth = 0.5f;
	}

	[System.Serializable]
	public class CollisionHandler {
		public LayerMask collisionLayer;
		public bool colliding = false;
		public Vector3[] adjustedCameraClipPoints, desiredCameraClipPoints;
		public Camera myCam;
		public float averageDist;
		public Vector3 hitVector;

		public void Initialize (Camera cam) {
			myCam = cam;
			adjustedCameraClipPoints = new Vector3[5];
			desiredCameraClipPoints = new Vector3[5];
		}

		public void UpdateCameraClipPoints (Vector3 cameraPosition, Quaternion atRotation, ref Vector3[] intoArray) {
			if (!myCam)
				return;

			intoArray =  new Vector3[5];

			float z = myCam.nearClipPlane;
			float x = Mathf.Tan (myCam.fieldOfView / 3.41f) * z;
			float y = x / myCam.aspect;

			intoArray [0] = (atRotation * new Vector3 (-x, y, z)) + cameraPosition;
			intoArray [1] = (atRotation * new Vector3 (x, y, z)) + cameraPosition;
			intoArray [2] = (atRotation * new Vector3 (-x, -y, z)) + cameraPosition;
			intoArray [3] = (atRotation * new Vector3 (x, -y, z)) + cameraPosition;
			intoArray [4] = cameraPosition - myCam.transform.forward*0.25f;

		}

		public bool CollisionDetectedAtClipPoints (Vector3[] clipPoints, Vector3 fromPosition) {
			for (int i = 0; i < clipPoints.Length; i++) {
				Ray ray = new Ray (fromPosition, clipPoints [i] - fromPosition);
				float distance = Vector3.Distance (clipPoints [i], fromPosition);
				if (Physics.Raycast (ray, distance, collisionLayer)) {
					return true;
				}
			}

			return false;
		}

		public float GetAdjustedDistanceWithRayFrom (Vector3 from) {
			float distance = -1f;

			for (int i = 0; i < desiredCameraClipPoints.Length; i++) {
				Ray ray = new Ray (from, desiredCameraClipPoints [i] - from);
				//Ray testRay = new Ray (
				RaycastHit hit;
				if (Physics.Raycast (ray, out hit, Mathf.Infinity, collisionLayer)) {
					if (distance == -1f) {
						distance = hit.distance;
						hitVector = desiredCameraClipPoints [i];
					} else {
						if (hit.distance < distance) {
							distance = hit.distance;
							hitVector = desiredCameraClipPoints [i];
						}
					}
					Debug.DrawLine (from, hit.point, Color.blue);
					//Debug.Log (hit.distance);
				}
			}

			if (distance == -1f) {
				return 0f;
			} else {
				return distance;
			}
		}

		public void CheckColliding (Vector3 targetPosition) {
			if (CollisionDetectedAtClipPoints (desiredCameraClipPoints, targetPosition)) {
				colliding = true;
			} else {
				colliding = false;
			}
		}
	}

	public PositionSettings position = new PositionSettings ();
	public CollisionHandler collision = new CollisionHandler ();
	public OrbitSettings orbit = new OrbitSettings ();

	PlayerBase pBase;

	public Transform target;
	public Transform desiredPos;
	public float val = 0;

	Vector3 targetPos = Vector3.zero, destination = Vector3.zero, adjustedDestination = Vector3.zero, camVel = Vector3.zero;
	float vOrbitInput = 0, hOrbitInput = 0;

	void Start () {
		val = 0;
	}

	void Update () {
		vOrbitInput = hOrbitInput = 0;
		if (!target) {
			SetCameraTarget ();
		}
	}

	void SetCameraTarget () {
		PlayerBase[] players = FindObjectsOfType<PlayerBase> ();
		PlayerBase me = null;
		if (players.Length > 0) {
			foreach (PlayerBase p in players) {
				if (p.photonView && p.photonView.IsMine && PhotonNetwork.IsConnected) {
					me = p;
					break;
				} else if (!p.photonView || !PhotonNetwork.IsConnected) {
					me = p;
					break;
				}
			}
		}

		if (me) {
			pBase = me;
			target = me.camPivot;
			MoveToTarget ();
			if (!collision.myCam) {
				collision.Initialize (GetComponent<Camera> ());
				collision.UpdateCameraClipPoints (transform.position, transform.rotation, ref collision.adjustedCameraClipPoints);
				collision.UpdateCameraClipPoints (destination, transform.rotation, ref collision.desiredCameraClipPoints);
			}
		}
	}



	void FixedUpdate () {
		if (!target)
			return;

		MoveToTarget ();
		LookAtTarget ();
		OrbitTarget ();
		UpdateCollisionVals ();
	}

	void MoveToTarget () {
		//position.adjustmentDistance = CheckDistance (position.adjustmentDistance);

		targetPos = target.position + Vector3.up * position.targetPosOffset.y + Vector3.forward * position.targetPosOffset.z + transform.TransformDirection (Vector3.right * position.targetPosOffset.x);
		destination = Quaternion.Euler (-pBase.camPivot.eulerAngles.x, pBase.camPivot.eulerAngles.y + 180f, 0) * -Vector3.forward * position.distanceFromTarget;
		destination += targetPos;

		if (collision.colliding) {
			adjustedDestination = Quaternion.Euler (-pBase.camPivot.eulerAngles.x, pBase.camPivot.eulerAngles.y + 180f, 0) * Vector3.forward * position.adjustmentDistance;
			adjustedDestination += targetPos;

			if (position.smoothFollow) {
				transform.position = Vector3.SmoothDamp (transform.position, adjustedDestination, ref camVel, position.smooth);
			} else {
				transform.position = adjustedDestination;
			}
		} else {
			if (position.smoothFollow) {
				transform.position = Vector3.SmoothDamp (transform.position, destination, ref camVel, position.smooth);
			} else {
				transform.position = destination;
			}
		}
	}

	void LookAtTarget () {
		Quaternion targetRot = Quaternion.LookRotation (targetPos - transform.position);
		targetRot.eulerAngles += new Vector3 (15f, 0f, 0f);
		transform.rotation = Quaternion.Lerp (transform.rotation, targetRot, 100 * Time.deltaTime);
	}

	void OrbitTarget () {

		vOrbitInput = Input.GetAxis ("Mouse Y");
		hOrbitInput = Input.GetAxis ("Mouse X");
		orbit.xRotation += vOrbitInput * orbit.vOrbitSmooth;
		orbit.yRotation += -hOrbitInput * orbit.hOrbitSmooth;

		if (orbit.xRotation > orbit.maxXRotation) {
			orbit.xRotation = orbit.maxXRotation;
		}

		if (orbit.xRotation < orbit.minXRotation) {
			orbit.xRotation = orbit.minXRotation;
		}
	}

	void UpdateCollisionVals () {
		if (!collision.myCam) {
			collision.Initialize (GetComponent<Camera> ());
		}
		collision.UpdateCameraClipPoints (transform.position, transform.rotation, ref collision.adjustedCameraClipPoints);
		collision.UpdateCameraClipPoints (destination, transform.rotation, ref collision.desiredCameraClipPoints);

		for (int i = 0; i < 5; i++) {
			Debug.DrawLine (targetPos, collision.desiredCameraClipPoints [i], Color.white);
			Debug.DrawLine (targetPos, collision.adjustedCameraClipPoints [i], Color.green);
		}

		collision.CheckColliding (targetPos);
		position.adjustmentDistance = collision.GetAdjustedDistanceWithRayFrom (targetPos);
	}

	float CheckDistance (float distance) {
		if (distance > position.maxZoom) {
			return position.maxZoom;
		} else if (distance < position.minZoom) {
			return position.minZoom;
		} else {
			return distance;
		}
	}
}
