using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Animations;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine.SceneManagement;
#endif

[SelectionBase]
[ExecuteAlways]
public class ModularTrack : MonoBehaviour {
	
	//[ReadOnly]
	public TrackPiece track;
	Vector3 centerPoint = Vector3.zero, pivotPoint = Vector3.zero;
	//[ReadOnly]
	public ModularTrack startLink, endLink;
	//[ReadOnly]
	public TrackManagerScript trackManager;
	//[ReadOnly]
	public ParentConstraint myParentPiece;
	//[ReadOnly]
	public bool isItemTrack = false, isRamp = false;
	//[ReadOnly]
	public GameObject testObject1, testObject2;

    [Header("Debug")]
    [SerializeField] DebugChannelSO m_debugChannel;
    Dictionary<Vector3, Vector3> m_collisionPoints = new Dictionary<Vector3, Vector3>();
    float m_debugGizmoScale = 1;

	void Start () {
    #if UNITY_EDITOR
        CheckCollision();
        if (EditorApplication.isPlayingOrWillChangePlaymode) {
            if (m_collisionPoints.Count > 0)
            {
                Debug.Break();
            }

			if (!track.pivotObject.gameObject.GetComponent<BoxCollider> ()) {
				track.pivotObject.gameObject.AddComponent<BoxCollider> ();
				track.pivotObject.gameObject.GetComponent<BoxCollider> ().isTrigger = true;
				track.pivotObject.gameObject.GetComponent<BoxCollider>().center = new Vector3(0, 13f, 0f);
				track.pivotObject.gameObject.GetComponent<BoxCollider>().size = new Vector3 (26f, 26f, 1f);
                if (!track.pivotObject.gameObject.GetComponent<Checkpoint>())
                    track.pivotObject.gameObject.AddComponent<Checkpoint>();
            }
		}
#else
        // In an actual game, 26x1x1 trigger areas are added to the end of each track.
        // Passing through these is required for the racer to complete the race.
		if (!track.pivotObject.gameObject.GetComponent<BoxCollider> ()) {
			track.pivotObject.gameObject.AddComponent<BoxCollider> ();
			track.pivotObject.gameObject.GetComponent<BoxCollider> ().isTrigger = true;
			track.pivotObject.localScale = new Vector3 (26f, 1f, 1f);
            if (!track.pivotObject.gameObject.GetComponent<Checkpoint>())
                track.pivotObject.gameObject.AddComponent<Checkpoint>();
		}
#endif
    }

#if UNITY_EDITOR
    public void CreateTrackLink (ModularTrack newTrack) {
		// Update new track values
		newTrack.startLink = endLink;
		newTrack.endLink = endLink.startLink;

		// Update prior link
		endLink.startLink = newTrack;

		// Undate endlink
		endLink = newTrack;
	}

	public void AdjustLinkPositions (ModularTrack modTrack) {
		if (!modTrack.startLink) {
			return;
		}

		PrefabUtility.RecordPrefabInstancePropertyModifications (modTrack.gameObject);

        Undo.RegisterCompleteObjectUndo(modTrack, "Update track positions");

        modTrack.transform.position = modTrack.startLink.track.pivotObject.position;
		modTrack.transform.rotation = modTrack.startLink.track.pivotObject.rotation;
		
        Physics.SyncTransforms ();

		if (modTrack.endLink) {
			AdjustLinkPositions (modTrack.endLink);
		}
	}

	public bool CollisionCheckLoop (ModularTrack currentTrack) {
		if (!currentTrack.endLink) {
			return true;
		} else if (!currentTrack.endLink.CheckCollision ()) {
			return false;
		} else if (currentTrack.endLink.endLink) {
			return CollisionCheckLoop (currentTrack.endLink);
		} else {
			return true;
		}
	}

	public bool CheckCollision () {
        if (SceneManager.GetActiveScene().name.Contains("Lobby") || SceneManager.GetActiveScene().name.Contains("Custom"))
            return true;

        m_collisionPoints = new Dictionary<Vector3, Vector3>();

        if (isRamp) {
			return CheckRampCollision ();
		} else if (isItemTrack) {
			return true;
		}else {
			GameObject worldCenter = new GameObject ("Center");
			Instantiate (worldCenter, GetComponent<MeshFilter> ().sharedMesh.bounds.center, Quaternion.identity, worldCenter.transform);
			Transform centerObj = worldCenter.transform.GetChild (0); 

			worldCenter.transform.rotation = transform.rotation;
			worldCenter.transform.localScale = transform.localScale;

			Vector3 centerPos = centerObj.position;

			worldCenter.transform.position = track.pivotObject.transform.position;
			worldCenter.transform.rotation = track.pivotObject.transform.rotation;
			worldCenter.transform.localScale = new Vector3 (transform.localScale.x, 1, -1);

			Vector3 pivotPos = centerObj.position;

			UnityEditor.EditorApplication.delayCall += () => {
				DestroyImmediate (worldCenter);
			};

			centerPoint = centerPos + transform.position;
			pivotPoint = pivotPos;

			Collider[] hitChecks = Physics.OverlapBox (centerPoint, GetComponent<MeshFilter> ().sharedMesh.bounds.size / 2.1f, transform.rotation);
			Collider[] secondaryChecks = Physics.OverlapBox (pivotPoint, GetComponent<MeshFilter> ().sharedMesh.bounds.size / 2.1f, track.pivotObject.transform.rotation);

            //DebugValueChecks ();

			for (int i = 0; i < hitChecks.Length; i++) {
				for (int s = 0; s < secondaryChecks.Length; s++) {
					if (hitChecks [i].transform == transform || hitChecks[i].tag != "TrackPiece") {

						continue;
					}
					if (hitChecks [i].transform.IsChildOf (transform)) {
						continue;
					}
					if (hitChecks [i] == secondaryChecks [s]) {
						if (endLink == hitChecks [i].GetComponent<ModularTrack> ()) {
							continue;
						} else {

                            if (m_debugChannel)
                                m_debugChannel.Raise(this, gameObject.name + " at " + gameObject.transform.position + ", " + gameObject.transform.eulerAngles + " & " + hitChecks [i].name + " at " + hitChecks [i].transform.position + ", " + hitChecks [i].transform.eulerAngles, DebugChannelSO.Severity.Error);

                            EditorUtility.DisplayDialog("Collision Detected!", "A collision between two or more track pieces has been detected." +
                                " To undo your last change, close this dialog box and use the Undo option in the 'Edit' tab at the top of your screen," +
                                "or press 'Ctrl' and 'Z' keys at the same time.", "Undo");

                            m_collisionPoints.Add(transform.position, hitChecks[i].transform.position);

                            return true;
						}
					}
				}
			}
		}
		return true;
	}

	public bool CheckRampCollision () {
		
		for (int r = 0; r < transform.childCount; r++) {
            GameObject worldCenter = new GameObject("Center");
            Instantiate(worldCenter, transform.GetChild(r).GetComponent<MeshFilter>().sharedMesh.bounds.center, Quaternion.identity, worldCenter.transform);

            Transform centerObj = worldCenter.transform.GetChild(0);

            worldCenter.transform.rotation = transform.GetChild(r).rotation;
            worldCenter.transform.localScale = transform.GetChild(r).localScale;

            Vector3 centerPos = centerObj.position;

            worldCenter.transform.position = transform.GetChild(r).GetChild(0).position;
            worldCenter.transform.rotation = transform.GetChild(r).GetChild(0).rotation;
            worldCenter.transform.localScale = new Vector3(transform.GetChild(r).localScale.x, 1, -1);

            Vector3 pivotPos = centerObj.position;

            EditorApplication.delayCall += () =>
            {
                DestroyImmediate(worldCenter);
            };

            centerPoint = centerPos + transform.GetChild(r).position;
            pivotPoint = pivotPos;

            Collider[] hitChecks = Physics.OverlapBox(centerPoint, transform.GetChild(r).GetComponent<MeshFilter>().sharedMesh.bounds.size / 2.1f, transform.GetChild(r).rotation);
            Collider[] secondaryChecks = Physics.OverlapBox (pivotPoint, transform.GetChild(r).GetComponent<MeshFilter> ().sharedMesh.bounds.size / 2.1f, transform.GetChild(r).GetChild(0).rotation);



			//DebugValueChecks ();

			for (int i = 0; i < hitChecks.Length; i++) {
				for (int s = 0; s < secondaryChecks.Length; s++) {
					if (hitChecks [i].transform == transform || hitChecks[i].tag != "TrackPiece") {
						continue;
					}
					if (hitChecks [i].transform.IsChildOf (transform)) {
						continue;
					}
					if (hitChecks [i] == secondaryChecks [s]) {
						if (endLink == hitChecks [i].GetComponent<ModularTrack> ()) {
							continue;
						} else {
                            if (m_debugChannel)
                                m_debugChannel.Raise(this, gameObject.name + " at " + gameObject.transform.position + ", " + gameObject.transform.eulerAngles + " & " + hitChecks [i].name + " at " + hitChecks [i].transform.position + ", " + hitChecks [i].transform.eulerAngles, DebugChannelSO.Severity.Error);

                            EditorUtility.DisplayDialog("Collision Detected!", "A collision between two or more track pieces has been detected." +
                                " To undo your last change, close this dialog box and use the Undo option in the 'Edit' tab at the top of your screen," +
                                "or press 'Ctrl' and 'Z' keys at the same time.", "Okay");

                            m_collisionPoints.Add(transform.position, hitChecks[i].transform.position);

                            return true;
						}
					}
				}
			}
		}

		return true;
	}

	void OnDestroy () {

		if(Time.frameCount == 0 || DoNotDelete.EditorApplicationQuit){
            //if (m_debugChannel)
            //    m_debugChannel.Raise(this, "Modular track attempted to close on frame 0 or while the application was closing. Aborting delete protocols.");
			return;
		}

		if (EditorApplication.isPlayingOrWillChangePlaymode || (!EditorApplication.isPlayingOrWillChangePlaymode && EditorApplication.isPlaying)) {
			return;
		}

		if (testObject1) {
			Undo.RegisterCompleteObjectUndo(testObject1, "Test Undo 1");
			testObject1.GetComponent<ModularTrack> ().testObject2 = testObject2;
		}
		if (testObject2) {
			Undo.RegisterCompleteObjectUndo(testObject2, "Test Undo 2");
			testObject2.GetComponent<ModularTrack> ().testObject1 = testObject1;
		}

		// Update the start link
		if (startLink) {
			PrefabUtility.RecordPrefabInstancePropertyModifications (startLink.gameObject);
			Undo.RegisterCompleteObjectUndo(startLink, "Save Start Link Values");
			startLink.endLink = endLink;
		}

		// Handle new end link
		if (endLink) {
			PrefabUtility.RecordPrefabInstancePropertyModifications (endLink.gameObject);
			Undo.RegisterCompleteObjectUndo(endLink, "Save End Link Values");
			// Assign the new link
			endLink.startLink = startLink;

			// Remove itself as the endlink's source
			endLink.GetComponent<ParentConstraint> ().RemoveSource (0);

			// Adjust Positions Quickly
			AdjustLinkPositions (endLink);

			// Create a new source using the startlink
			ConstraintSource startSource = new ConstraintSource ();
			if (startLink) {
				PrefabUtility.RecordPrefabInstancePropertyModifications (startLink.gameObject);
				Undo.RegisterCompleteObjectUndo(startLink, "Save Start Link Values");
				startSource.sourceTransform = startLink.track.pivotObject;
				startSource.weight = 1;
			}

			if(!CollisionCheckLoop(endLink)){
				// Makes student aware of error
				if (EditorUtility.DisplayDialog("Delete Track Piece Error", "Cannot delete this track piece because it causes other pieces to collide with the track.", "Ok")) {
					//Debug.Log ("oh no");
					//Undo.PerformUndo ();
					/*
					GameObject newTrackObj = GameObject.Instantiate (gameObject, transform.position, transform.rotation);
					ModularTrack newTrack = newTrackObj.GetComponent<ModularTrack>();
					newTrackObj.name = gameObject.name;
					newTrackObj.transform.SetSiblingIndex(transform.GetSiblingIndex());

					//activate object
					newTrackObj.SetActive(true);

					// Re-update links
					startLink.endLink = newTrack;
					endLink.startLink = newTrack;
					newTrack.endLink = endLink;
					newTrack.startLink = startLink;

					// Re-update constraints
					// Create a new source using the startlink
					ConstraintSource originalSource = new ConstraintSource ();
					originalSource.sourceTransform = newTrack.track.pivotObject;
					originalSource.weight = 1;

					// Apply the changes to the endLink
					newTrack.endLink.GetComponent<ParentConstraint>().AddSource (originalSource);

					// Apply the changes to the replacement piece
					GetComponent<ParentConstraint>().AddSource (startSource);
					GetComponent<ParentConstraint>().locked = true;
					GetComponent<ParentConstraint>().constraintActive = true;
					*/
				}
			} else {
				// Apply the changes
				endLink.GetComponent<ParentConstraint>().AddSource (startSource);
			}
		}
		//Undo.DestroyObjectImmediate (this);
	}

	void OnDrawGizmos () {
        if (!m_debugChannel || !m_debugChannel.DebugGizmosEnabled)
            return;

        Gizmos.color = Color.white;
        Gizmos.DrawSphere (transform.position, 0.5f);

        if (m_collisionPoints.Count > 0)
        {

            Gizmos.color = Color.red;
            foreach (Vector3 _key in m_collisionPoints.Keys)
            {
                float _multiplier = 10;

                Gizmos.DrawSphere(_key + Vector3.up * 1.5f * _multiplier, 0.5f * _multiplier);
                Gizmos.DrawCube(_key + Vector3.up * 2.35f * _multiplier, Vector3.one * 0.5f * _multiplier);
                Gizmos.DrawCube(_key + Vector3.up * 3 * _multiplier, Vector3.one * 0.8f * _multiplier);
                Gizmos.DrawCube(_key + Vector3.up * 4 * _multiplier, Vector3.one * 1.2f * _multiplier);

                Gizmos.DrawRay(_key, m_collisionPoints[_key] - _key);
                Gizmos.color = Color.magenta;
                Gizmos.DrawWireSphere(m_collisionPoints[_key], 2);
            }
        }
    }

	void DebugValueChecks () {
        if (!m_debugChannel)
            return;

		m_debugChannel.Raise(this, "Size: " + GetComponent<MeshFilter> ().sharedMesh.bounds.size);
        m_debugChannel.Raise(this, "Center Main: " + centerPoint);
        m_debugChannel.Raise(this, "Pivot Center: " + pivotPoint);
        m_debugChannel.Raise(this, "Center of Mesh: " + GetComponent<MeshFilter> ().sharedMesh.bounds.center);
	}
#endif

}


[System.Serializable]
public class TrackPiece {
	public Transform mainObject, pivotObject;
}

/*
[System.Serializable]
public class LinkPiece {
	public Transform mainObject, pivotObject;
}

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Animations;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine.SceneManagement;
#endif

[SelectionBase]
[ExecuteAlways]
public class ChainLink : MonoBehaviour {

	public LinkPiece link;
	public ChainLink startLink, endLink;

	#if UNITY_EDITOR

	void OnDestroy () {

		// Stops from running when destroying/recreating objects
		if(Time.frameCount == 0){
			return;
		}

		//Same as above along with preventing this from running in Playmode
		if (EditorApplication.isPlayingOrWillChangePlaymode || (!EditorApplication.isPlayingOrWillChangePlaymode && EditorApplication.isPlaying)) {
			return;
		}

		// Update the start link
		if (startLink) {
			PrefabUtility.RecordPrefabInstancePropertyModifications (startLink.gameObject);
			Undo.RegisterCompleteObjectUndo(startLink, "Save Start Link Values");
			startLink.endLink = endLink;
		}

		// Handle new end link
		if (endLink) {
			PrefabUtility.RecordPrefabInstancePropertyModifications (endLink.gameObject);
			Undo.RegisterCompleteObjectUndo(endLink, "Save End Link Values");
			// Assign the new link
			endLink.startLink = startLink;

			// Remove itself as the endlink's source
			endLink.GetComponent<ParentConstraint> ().RemoveSource (0);

			// Adjust Positions Quickly
			AdjustLinkPositions (endLink);

			// Create a new source using the startlink
			ConstraintSource startSource = new ConstraintSource ();
			if (startLink) {
				PrefabUtility.RecordPrefabInstancePropertyModifications (startLink.gameObject);
				Undo.RegisterCompleteObjectUndo(startLink, "Save Start Link Values");
				startSource.sourceTransform = startLink.link.pivotObject;
				startSource.weight = 1;
			}

			endLink.GetComponent<ParentConstraint>().AddSource (startSource);
		}
	}

	#endif
}
*/