using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using System.IO;
using UnityEngine.SceneManagement;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
#endif

[ExecuteInEditMode]
public class LevelEditor : MonoBehaviour {

	[HideInInspector]
	public GameObject dummyPlayer, levelEssentials;
	[HideInInspector]
	public bool levelError;

	[BoolToButton("savePrefab")]
	public bool savePrefab = false;
	// Here for spacing rather than actual functionality
	[BoolToButton("")]
	public bool emptyBool = false;

	[BoolToButton("exportLevel")]
	public bool exportLevel = false;
	// Here for spacing rather than actual functionality
	[BoolToButton("")]
	public bool emptyBool2 = false;

	public bool autoSaveLevel = true;
	[VariableToggle("autoSaveLevel", false)]
	public float autoSaveTimeValue = 60f;

	float autoSaveTimer = 0f;
	bool initialSave = false;
	[HideInInspector]
	public bool holdUpdate = true;

	string myLevelName = "";

	RecalculateMesh TerrainPlane;
	int terrainCount;

	[HideInInspector]
	public List<Transform> objs;
	[HideInInspector]
	public List<Component> coreComponents;

	#if UNITY_EDITOR
	void Awake () {
		initialSave = true;
		exportLevel = false;
		holdUpdate = true;
	}

	void Start () {
		exportLevel = false;
		if (EditorApplication.isPlayingOrWillChangePlaymode) {
			if (SceneManager.GetActiveScene ().name != "GameIsland") {
				if (levelEssentials) {
					GameObject.Instantiate (levelEssentials);
				}
			}
			if (dummyPlayer) {
				dummyPlayer.SetActive (false);
			}
		} else if (dummyPlayer) {
			dummyPlayer.SetActive (true);
			holdUpdate = true;
		}
	}

	void Update () {

		autoSaveTimeValue = Mathf.Clamp (autoSaveTimeValue, 15f, 300f);
		
		if (EditorApplication.isPlayingOrWillChangePlaymode) {
			// Don't do anything in playmode
		} else {

			// check that all the default objects are as they should be
			if (CheckBasics () != "") {
				Debug.Log (CheckBasics ());
				return;

			} else {

				// Snap the level to the origin
				SnapVals ();

				// delay saving and other updates
				if (holdUpdate) {
					holdUpdate = false;
					return;
				}

				// make the objects children of the main prefab
				AdoptChildren ();

				// remove empty transforms
				objs.RemoveAll(Transform => Transform == null);

				// Keep the gameObjects and Prefabs inside the level
				KeepTheKidsInside(transform);

			}

			autoSaveTimer += Time.deltaTime;

			if((autoSaveLevel && autoSaveTimer > autoSaveTimeValue) || (initialSave && autoSaveTimer > 1.5f)){
				SavePrefab (true);
				autoSaveTimer = 0f;
			}

			SavePrefab (savePrefab);
			ExportLevelPackage (exportLevel);
		}
	}

	void AdoptChildren () {
		// create an array of all the transforms in the Scene
		Transform[] allObjs = FindObjectsOfType<Transform> ();
		// cycle through all the transforms and set the Level Prefab as the parent object (not including Canvases and Cameras)
		foreach (Transform obj in allObjs)
			if (obj.parent == null && obj.tag != "Safe")
				obj.SetParent (transform);
	}

	string CheckBasics () {
		
		// set start value for number of terrains
		terrainCount = 1;

		// cycle through the objects to find terrain
		foreach (Transform child in transform) {
			
			if (child.GetComponent<RecalculateMesh> ()) {

				// increase the terrain count
				terrainCount++;

				// make the frst terrain the main one
				if (!TerrainPlane)
					TerrainPlane = child.GetComponent<RecalculateMesh> ();

				// remove any additional terrains
				if (TerrainPlane && TerrainPlane != child.GetComponent<RecalculateMesh>()) {
					UnityEditor.EditorApplication.delayCall += () => { DestroyImmediate (child.gameObject); };
					terrainCount--;
				}

				// set the terrain count to 0 if there's no terrain
				if (!TerrainPlane)
					terrainCount = 0;
			}

			// remove the object if it is a duplicate of the Level Prefab
			if (child.GetComponent<LevelEditor> ())
				UnityEditor.EditorApplication.delayCall += () => { DestroyImmediate (child.gameObject); };
		}

		// add a LootRandomizer if there is none
		if (!GetComponent<LootRandomizer> ())
			UnityEditor.EditorApplication.delayCall += () => { gameObject.AddComponent<LootRandomizer> (); };

		// display terrain error message
		if (terrainCount == 0)
			return "NO TERRAIN PLANE: There is no Terrain Plane attached to the gameobject. Please add a unique Terrain Plane from the Level Prefabs folder to the gameobject.";

		return "";
	}

	void KeepTheKidsInside (Transform parent) {
		foreach (Transform kid in parent) {

			// get the index of the object from the list, and if it's not in the list, add it
			int index = GetObjectIndex (kid);

			// determine the object type
			string objectType = CheckObjectComponents (kid);

			// contain all the objects in the level
			ContainLevelObjects (objs [index].GetComponent<Collider> ());

			// update the object based on whether or not it is inside the level
			if (objectType == "Tinker") {
				Bounds tinkerBounds = new Bounds (kid.position, Vector3.zero);
				foreach (Collider tinker in kid.GetComponentsInChildren<Collider>()) {
					
					if (tinker.GetComponent<Tinkercad> ())
						continue;
					
					if (!tinker.name.Contains ("group")) {
						ContainLevelObjects (tinker);
						continue;
					}

					tinkerBounds.Encapsulate (tinker.bounds);
				}
				Vector3 tinkerCenter = tinkerBounds.center - kid.transform.position;
				tinkerBounds.center = tinkerCenter;
				kid.GetComponent<BoxCollider> ().size = new Vector3 (tinkerBounds.size.x / kid.localScale.x, tinkerBounds.size.y / kid.localScale.y, tinkerBounds.size.z / kid.localScale.z);
				kid.GetComponent<BoxCollider> ().center = new Vector3 (tinkerBounds.center.x / kid.localScale.x, tinkerBounds.center.y / kid.localScale.y, tinkerBounds.center.z / kid.localScale.z);
			}

			// if the object is not a special type, cycle through its children
			if (objectType == "" && kid.childCount > 0)
				KeepTheKidsInside (kid);
		}
	}

	void ContainLevelObjects (Collider myCollider) {
		if (!myCollider)
			return;
		Vector3 corner1 = new Vector3 (myCollider.bounds.max.x, myCollider.bounds.max.y, myCollider.bounds.min.z);
		Vector3 corner2 = new Vector3 (myCollider.bounds.max.x, myCollider.bounds.min.y, myCollider.bounds.min.z);
		Vector3 corner3 = new Vector3 (myCollider.bounds.max.x, myCollider.bounds.min.y, myCollider.bounds.max.z);
		Vector3 corner4 = new Vector3 (myCollider.bounds.min.x, myCollider.bounds.max.y, myCollider.bounds.max.z);
		Vector3 corner5 = new Vector3 (myCollider.bounds.min.x, myCollider.bounds.max.y, myCollider.bounds.min.z);
		Vector3 corner6 = new Vector3 (myCollider.bounds.min.x, myCollider.bounds.min.y, myCollider.bounds.max.z);

		Vector3 plusX = (myCollider.bounds.max + corner2) / 2f;
		Vector3 negX = (myCollider.bounds.min + corner4) / 2f;

		Vector3 plusY = (myCollider.bounds.max + corner5) / 2f;
		Vector3 negY = (myCollider.bounds.min + corner3) / 2f;

		Vector3 plusZ = (myCollider.bounds.max + corner6) / 2f;
		Vector3 negZ = (myCollider.bounds.min + corner1) / 2f;

		//DrawDebugBounds (myCollider, corner1, corner2, corner3, corner4, corner5, corner6, plusX, plusY, plusZ, negX, negY, negZ);

		// determine how far the outer bounds is outside the level
		float plusOutsideZ = 190f - plusZ.z;
		float negOutsideZ = -210f - negZ.z;
		float plusOutsideX = 190f - plusX.x;
		float negOutsideX = -210f - negX.x;
		float plusOutsideY = 200f - plusY.y;
		float negOutsideY = -200f - negY.y;

		// make the position inside the level if it is outside
		if (plusOutsideZ < 0f) {
			myCollider.transform.position += new Vector3 (0f, 0f, plusOutsideZ);
		} else if (negOutsideZ > 0f) {
			myCollider.transform.position += new Vector3 (0f, 0f, negOutsideZ);
		}
		if (plusOutsideX < 0f) {
			myCollider.transform.position += new Vector3 (plusOutsideX, 0f, 0f);
		} else if (negOutsideX > 0f) {
			myCollider.transform.position += new Vector3 (negOutsideX, 0f, 0f);
		}
		if (plusOutsideY < 0f) {
			myCollider.transform.position += new Vector3 (0f, plusOutsideY, 0f);
		} else if (negOutsideY > 0f) {
			myCollider.transform.position += new Vector3 (0f, negOutsideY, 0f);
		}

		// determine the largest possible scale for each axis
		Vector3 adjustedScale = new Vector3 (myCollider.transform.localScale.x/myCollider.bounds.size.x,myCollider.transform.localScale.y/myCollider.bounds.size.y, myCollider.transform.localScale.z/myCollider.bounds.size.z);

		// adjust the scale accordingly
		if (myCollider.bounds.size.x > 400f) {
			myCollider.transform.localScale = new Vector3 (adjustedScale.x * 400, myCollider.transform.localScale.y, myCollider.transform.localScale.z);
		}
		if (myCollider.bounds.size.y > 400f) {
			myCollider.transform.localScale = new Vector3 (myCollider.transform.localScale.x, adjustedScale.y * 400, myCollider.transform.localScale.z);
		}
		if (myCollider.bounds.size.z > 400f) {
			myCollider.transform.localScale = new Vector3 (myCollider.transform.localScale.x, myCollider.transform.localScale.y, adjustedScale.z * 400);
		}

	}

	int GetObjectIndex (Transform levelObj) {
		int checkInt = -1;
		if (objs.Count > 0) {
			for (int i = 0; i < objs.Count; i++) {
				if (levelObj == objs[i]) {
					checkInt = i;
					break;
				}
			}
		}
		if (checkInt == -1) {
			objs.Add (levelObj);
			return (GetObjectIndex (levelObj));
		}
		return checkInt;
	}

	// determine the type of object
	string CheckObjectComponents (Transform check) {
		if (dummyPlayer != null && check == dummyPlayer.transform)
			return "Empty";
		else if (check.GetComponent<Tinkercad> ()) {
			if (!check.GetComponent<Collider> ()) {
				check.gameObject.AddComponent<BoxCollider> ();
				UnityEditor.EditorApplication.delayCall += () => { check.GetComponent<Collider> ().isTrigger = true; };
			}
			return "Tinker";
		} else if (check.GetComponent<PotionScript> ())
			return "Potion";
		else if (check.GetComponent<LootChestScript> ())
			return "Loot";
		else if (check.GetComponent<DiscoBall> ())
			return "Disco";
		else if (check.GetComponent<Collider> ())
			return "";
		else if (check.GetComponent<MeshRenderer> () && !check.GetComponent<Collider> ()) {
			UnityEditor.EditorApplication.delayCall += () => { check.gameObject.AddComponent<MeshCollider> (); };
			return "";
		} else
			return "Empty";

	}

	void SavePrefab (bool save) {

		if (gameObject.activeInHierarchy && save) {
			
			if (initialSave) {
				myLevelName = gameObject.name;
				initialSave = false;
			}

			if (myLevelName != gameObject.name) {
				gameObject.name = myLevelName;
			}

			EditorApplication.ExecuteMenuItem ("Tools/Polybrush/Auto Apply to Mesh");

			if (AssetDatabase.IsValidFolder ("Assets/Levels")) {
				Object newLevelPrefab = PrefabUtility.SaveAsPrefabAsset (gameObject, "Assets/Levels/" + gameObject.name + ".prefab");
				PrefabUtility.SaveAsPrefabAssetAndConnect (gameObject, "Assets/Levels/" + gameObject.name + ".prefab", InteractionMode.AutomatedAction);
				PrefabUtility.UnpackPrefabInstance (gameObject, PrefabUnpackMode.OutermostRoot, InteractionMode.AutomatedAction);
				EditorSceneManager.SaveScene (EditorSceneManager.GetActiveScene ());
			} else {
				Debug.Log ("The Levels folder has been moved or deleted, please return the Levels folder inside the Assets folder or create a new Levels folder.");
			}
		}
		savePrefab = false;
	}

	void ExportLevelPackage (bool export) {
		if (gameObject.activeInHierarchy && export) {
			if (AssetDatabase.IsValidFolder ("Assets/Levels")) {
				SavePrefab (true);
				// Disable Arcade link for now
				// Application.OpenURL ("https://arcade.blackrocket.com/add-an-asset/");
				string filename = gameObject.name.Substring (0, gameObject.name.Length - 7) + ".unitypackage";
				AssetDatabase.ExportPackage ("Assets/Levels/" + gameObject.name + ".prefab", "Asset Packages/" + filename, ExportPackageOptions.IncludeDependencies | ExportPackageOptions.Interactive);
			} else {
				Debug.Log ("The Levels folder has been moved or deleted, please return the Levels folder inside the Assets folder or create a new Levels folder.");
			}
		}
		exportLevel = false;
	}

	void SnapVals () {
		// Level Prefab Defaults
		transform.position = new Vector3(-10, 0, -10);
		transform.eulerAngles = Vector3.zero;
		transform.localScale = Vector3.one;

		// Dummy Defaults
		if (dummyPlayer) {
			dummyPlayer.transform.localPosition = Vector3.zero;
			dummyPlayer.transform.eulerAngles = Vector3.zero;
			dummyPlayer.transform.localScale = Vector3.one;
		}

	}

	void DrawDebugBounds (Collider myCollider,
		Vector3 corner1, Vector3 corner2, Vector3 corner3, Vector3 corner4, Vector3 corner5, Vector3 corner6,
		Vector3 plusX, Vector3 plusY, Vector3 plusZ, Vector3 negX, Vector3 negY, Vector3 negZ) {

		Debug.DrawLine (myCollider.bounds.max, corner1, Color.blue);
		Debug.DrawLine (corner2, corner3, Color.green);
		Debug.DrawLine (myCollider.bounds.max, corner3, Color.blue);
		Debug.DrawLine (corner1, corner2, Color.yellow);

		Debug.DrawLine (corner4, corner5, new Color32 (228, 114, 37, 255));
		Debug.DrawLine (corner3, corner6, Color.green);
		Debug.DrawLine (corner1, corner5, new Color32 (228, 114, 37, 255));
		Debug.DrawLine (corner2, myCollider.bounds.min, Color.red);

		Debug.DrawLine (myCollider.bounds.max, corner4, Color.blue);
		Debug.DrawLine (corner4, corner6, Color.yellow);
		Debug.DrawLine (corner5, myCollider.bounds.min, Color.red);
		Debug.DrawLine (corner6, myCollider.bounds.min, Color.red);

		Debug.DrawLine (plusX, negX, Color.magenta);
		Debug.DrawLine (plusY, negY, Color.magenta);
		Debug.DrawLine (plusZ, negZ, Color.magenta);
	}

	#else 
	void Start () {
		if (dummyPlayer) {
			dummyPlayer.SetActive (false);
		}
	}
	#endif
}
