﻿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;
	//bool reset = true;
	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 () 
	{
		// Reset the orbit input values for this frame
		vOrbitInput = hOrbitInput = 0;

		// If we don't have a target, acquire one
		if (!target)
			SetCameraTarget ();
	}

	/// <summary>
	/// Sets the camera target to the local player.
	/// If no local player is found, the target is not set.
	/// </summary>
	void SetCameraTarget () 
	{
		// Get all PlayerBase references in the scene
		PlayerBase[] players = FindObjectsOfType<PlayerBase> ();

		// Create a reference for the local player
		PlayerBase me = null;

		// If we found at least one PlayerBase...
		if (players.Length > 0) 
		{
			// Loop through each player
			foreach (PlayerBase p in players) 
			{
				// If the player is not a network instance or is not connected to a photon network
				if (!p.IsNetworkPlayer() || (!p.photonView || !PhotonNetwork.IsConnected)) 
				{
					// Set the player reference to this player and break out of the loop
					me = p;
					break;
				}
			}
		}

		// If we found ourselves...
		if (me) 
		{
			// Set the pBase reference to the local player
			pBase = me;

			// Set the target as the CameraPivot ref from the player
			target = me.CameraPivot;

			// Move to the given target
			MoveToTarget ();

			// If there is no camera reference in the collision data...
			if (!collision.myCam) 
			{
				// Initialize the collision data with camera attached to this gameobject
				collision.Initialize (GetComponent<Camera> ());

				// Update clip points
				collision.UpdateCameraClipPoints (transform.position, transform.rotation, ref collision.adjustedCameraClipPoints);
				collision.UpdateCameraClipPoints (destination, transform.rotation, ref collision.desiredCameraClipPoints);
			}
		}
	}

	/// <summary>
	/// Moves to a specific _pb reference
	/// </summary>
	/// <param name="_pb"></param>
    public void MoveToSpecificTarget(PlayerBase _pb)
	{
		// Return if we were passed a null reference
		if (_pb == null)
			return;

		// Set the pBase ref to the given reference
        pBase = _pb;

		// If the player base CamPivot reference is missing, set it up
		if (!pBase.CameraPivot)
			pBase.CameraPivot = GameObject.FindGameObjectWithTag("Camera Pivot").transform;

		// Set the target to the camera pivot reference
        target =  pBase.CameraPivot;

		// Move to the new target
        MoveToTarget();

		// Initialize the camera data if we don't have a cam reference
        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);

		// Set the target position to be the target's position plus the offset value.
		targetPos = target.position + Vector3.up * position.targetPosOffset.y + Vector3.forward * position.targetPosOffset.z + transform.TransformDirection (Vector3.right * position.targetPosOffset.x);
		
		destination = Quaternion.Euler (-pBase.CameraPivot.eulerAngles.x, pBase.CameraPivot.eulerAngles.y + 180f, 0) * -Vector3.forward * position.distanceFromTarget;
		destination += targetPos;

		if (collision.colliding) 
		{
			adjustedDestination = Quaternion.Euler (-pBase.CameraPivot.eulerAngles.x, pBase.CameraPivot.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 () 
	{
		if (Cursor.lockState == CursorLockMode.None)
			return;

		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;
		}
	}
}
