﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEditor.Experimental;
using System.Threading;
using UnityEngine.SceneManagement;
#endif

[ExecuteInEditMode]
[RequireComponent(typeof(LootRandomizer))]
public class LevelEditor : MonoBehaviour {
	[Header("Level Prefab Components")]
	[SerializeField] [ReadOnly] string m_myLevelName = "";
	[ReadOnly]
	public GameObject dummyPlayer;
	[ReadOnly]
	public Terrain TerrainMain;

	[HideInInspector]
	public bool levelError = false;
	//bool cleared = false;
	//string lastErrorMessage = "";

	[BoolToButton("")] public bool emptyBool6 = false;
	[BoolToButton("savePrefab")] public bool savePrefab = false;
	[BoolToButton("")] public bool emptyBool = false;
	[BoolToButton("startTicker")] public bool startTicker = false;
	[BoolToButton("")] public bool emptyBool5 = false;
	

	[Header("Autosave Settings")]
	[Tooltip("The number of seconds remaining until the next autosave.")]
	[ReadOnly]
	public string TimeRemaining = "Autosave Disabled";
	[Tooltip("The number of seconds between autosave prompts. Setting this value to Zero will disable the autosave function.")]
	[Range(0, 600)]
	[SerializeField] public int AutosaveInterval = 100;

	[SerializeField] bool m_forceStop = false;
	[SerializeField] [ReadOnly] int m_tickersActive = 0;
	bool m_awaitingAutosaveTick = false;
	private int m_autosaveTime = 1000;
	private bool m_initialSave = false;
	private TerrainNormal[] m_hitResults = new TerrainNormal[0];

	public struct TerrainNormal{
		Vector3 position;
		public Vector3 Position {get{return position;}}
		Vector3 normal;
		public Vector3 Normal {get{return normal;}}
		
		public TerrainNormal(RaycastHit _hit){
			position = _hit.point;
			normal = _hit.normal;
		}
	}

	
	enum DebugShape{
		Ray,
		Sphere
	}
	
	[BoolToButton("")] public bool emptyBool7 = false;
	[BoolToButton("updateWalkableTerrain")] public bool updateWalkableTerrain = false;
	[BoolToButton("")] public bool emptyBool3 = false;
	[BoolToButton("hideTerrainDebug")] public bool hideTerrainDebug = false;
	[BoolToButton("")] public bool emptyBool4 = false;
	
	[Header("Slope Display Options")]
	[SerializeField] [Range(2, 5)] float m_checkDistance = 5;
	[SerializeField] DebugShape displayShape;
	[SerializeField] bool showLevelGround = false, showSteepSlopes = false, showUnwalkable = false;
	
	[Header("Other Editor Options")]
	[SerializeField] bool ShowLevelBounds = false;
	[SerializeField] bool dontForceHierarchy = false;
	
	[Header("Debug")]
	[SerializeField] DebugChannelSO m_debugChannel;

	bool m_allowNameEnforcement;
	[HideInInspector]
	public bool holdUpdate = true;
	[HideInInspector]
	public List<Transform> objs;
	[HideInInspector]
	public List<Component> coreComponents;


	void Awake () {
#if UNITY_EDITOR
		holdUpdate = true;
		HideHierarchyElements ();
#endif
#if UNITY_STANDALONE
		if (Application.platform == RuntimePlatform.WebGLPlayer)
		{
			SearchAndDestroyParticles(transform);
			ToggleAllGPUInstancing(transform, false);
		}
		else
			ToggleAllGPUInstancing(transform, true);
			
		SearchAndDestroyOldWands(transform);
#endif
	}

#if UNITY_EDITOR
	void Start () {
		if (EditorApplication.isPlayingOrWillChangePlaymode) {
			if (dummyPlayer) {
				dummyPlayer.SetActive (false);
			}
		} else if (dummyPlayer) {
			dummyPlayer.SetActive (true);
			holdUpdate = true;
		}
	}

	void OnValidate()
    {
	    HandleAutosave();
	    
	    if (m_myLevelName == "" || !m_allowNameEnforcement)
		    return;
	    
	    if (TerrainMain.name != m_myLevelName + " Terrain"){
		    TerrainMain.name = m_myLevelName + " Terrain";
		
		    if (m_debugChannel)
			    m_debugChannel.Raise(this, 
			    new string[] {
				    "Enforced terrain name.",
				    "Terrain name must match level prefab in order to export properly."});
	    }
	    
	    if (gameObject.name != m_myLevelName) 
	    { 
		    gameObject.name = m_myLevelName;
		
			if (m_debugChannel)
				m_debugChannel.Raise(this, 
					new string[] {
						"Enforced level name.",
						"Terrain name must match level prefab in order to export properly."});
		}
    }

	void Update () 
	{
		// CHECK FOR ERRORS //
		#region

		// Cancel all correction routines if we have errors
		if (CheckBasics())
		{
			if (m_debugChannel)
				m_debugChannel.Raise(this, 
				"Setup error",
				DebugChannelSO.Severity.Error);
			return;
		}

		#endregion

		if (EditorApplication.isPlayingOrWillChangePlaymode) 
		{
			if (levelError)
			{
				EditorUtility.DisplayDialog("Level Setup Error", "The level cannot be tested because there is something wrong with the level format. Check the error log in edit mode to see what's wrong.", "Ok");
				Debug.DebugBreak();
				Debug.Break();
            }

			if (EditorApplication.isPlaying)
				return;
		} 
		else
        {
			//UpdateEditorTime();

            // ADJUST EDITED COMPONENTS //
		#region

            // Snap the terrain to the edges of the level
            SnapTerrainEdges();

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

		#endregion


            // 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);

			//HandleAutosave();

            if (savePrefab)
            {
                Transform selectedTransform = Selection.activeTransform;
                SavePrefab(true);
                Selection.activeTransform = selectedTransform;
            }
            else if (updateWalkableTerrain){
            	if (EditorUtility.DisplayDialog(
	            	"Calculate Terrain Slopes",
            		"Showing terrain normals can cause intense slowdown on low-spec devices. Are you sure you want to proceed?",
            		"Let's go, I can take it!",
            		"No thanks! I like my frames!"
            	)){
            		UpdateTerrainNormals();
            	}
            }
            else if (hideTerrainDebug){
            	HideTerrainLines();
            }
        }
    }
    
	void OnDrawGizmos(){
		if (m_hitResults.Length > 0)
			foreach (TerrainNormal _hit in m_hitResults){
				float _angle = Mathf.Abs(Vector3.Angle(Vector3.up, _hit.Normal));
				
				if (_angle < 30){
					if(!showLevelGround)
						continue;
				}
				else if (_angle < 60){
					if(!showSteepSlopes)
						continue;
				}
				else if (!showUnwalkable)
					continue;
					
				Gradient _gradient = new Gradient();
				
				_gradient.colorKeys = new GradientColorKey[]{
					new GradientColorKey(Color.green, 0),
					new GradientColorKey(Color.red, 1)
				};
				
				Color _color = _gradient.Evaluate((_angle - 10) / 80);
				
				if (m_debugChannel)
					switch(displayShape){
						case(DebugShape.Ray):
							m_debugChannel.DrawGizmoRay(_hit.Position, _hit.Normal, _color, 1.5f);
							break;
						case(DebugShape.Sphere):
							m_debugChannel.DrawGizmoSphere(_hit.Position, _color, 1);
							break;
					}
			}
	}

	public void SetLevelName(string _name){
		m_myLevelName = _name;
		
		if (m_debugChannel)
			m_debugChannel.Raise(this, 
			"Name set to " + m_myLevelName);
	}
	
	public void NameEnforcementEnabled(bool _enabled){
		m_allowNameEnforcement = _enabled;
	}

	void UpdateTerrainNormals(){
		RaycastHit[] _hits;
		
		List<TerrainNormal> _normals = new List<TerrainNormal>();
		
		for (float x = -200; x < 200; x += m_checkDistance)
			for (float y = -200; y < 200; y += m_checkDistance){
				Ray _ray = new Ray(new Vector3(x, 200, y), Vector3.down);
				
				_hits = Physics.RaycastAll(_ray, 500, LayerMask.GetMask("Default","Environment"), QueryTriggerInteraction.Ignore);
				
				if (_hits.Length > 0)
					foreach (RaycastHit _hit in _hits)
						_normals.Add(new TerrainNormal(_hit));
			}
			
		m_hitResults = _normals.ToArray();
			
		updateWalkableTerrain = false;
		
		if (m_debugChannel)
			m_debugChannel.Raise(
			this, 
			"Calculated " + m_hitResults.Length + " contact points");
	}
	
	void HideTerrainLines(){
		m_hitResults = new TerrainNormal[0];
			
		hideTerrainDebug = false;
		
		if (m_debugChannel)
			m_debugChannel.Raise(this, 
			"Terrain slope gizmos reset.");
	}

    /// <summary>
    /// Handles autosave behaviors
    /// </summary>
    private void HandleAutosave()
	{
		if (AutosaveInterval != 0 && AutosaveInterval < 60)
			AutosaveInterval = 60;
	    
		if (!m_awaitingAutosaveTick && m_tickersActive != 0)
			m_tickersActive = 0;
    	
        // If our autosave interval is greater than zero
	    if (AutosaveInterval > 0)
        {
            // Clamp the autosave time to the interval
            if (m_autosaveTime > AutosaveInterval)
                m_autosaveTime = AutosaveInterval;

		    if (startTicker){
		    	Tick();
		    	startTicker = false;
		    }
        }
    }
    
	async void Tick(){
		// If there are any tick tasks already running, continue.
		if (m_tickersActive > 0){
			return;
		}
		
		// Increment the number of active tickers
		m_tickersActive++;
		
		// Wait 1 second
		await System.Threading.Tasks.Task.Delay(1000);
		
		// If AutosaveInterval is set to zero or we are forcing a stop
		if (AutosaveInterval == 0 || m_forceStop){
			
			// Update the display
			TimeRemaining = "Autosave Disabled";
			
			// Reset the timer
			m_autosaveTime = AutosaveInterval;
			
			// Make sure that we are not at zero or negative number of tickers
			if (m_tickersActive < 1)
				m_tickersActive = 1;
				
			// Reduce the active ticker number
			m_tickersActive--;
			
			// Return
			return;
		}
		
		// Count down and move to display
		m_autosaveTime--;
		TimeRemaining = Mathf.FloorToInt(m_autosaveTime).ToString() + " seconds";
		
		// Send debug message if we have 10 seconds remaining
		if (m_autosaveTime == 10){
			if (m_debugChannel)
				m_debugChannel.Raise(this,
				"Autosave in 10 seconds");
		}
		
		// If our autosave time has hit 0
		if (m_autosaveTime <= 0)
		{
			// Display the autosave ask dialog and save if the user wants to
			if (m_initialSave || EditorUtility.DisplayDialog("Autosave Level", "Would you like to save? Saving will take a few seconds.", "Yes", "No")){
				Transform selectedTransform = Selection.activeTransform;
				SavePrefab(true);
				Selection.activeTransform = selectedTransform;
			}

			// Reset the autosave timer to the interval
			m_autosaveTime = AutosaveInterval;
		}
		
		// Make sure that we are not at zero or negative number of tickers
		if (m_tickersActive < 1)
			m_tickersActive = 1;
				
		// Reduce the active ticker number
		m_tickersActive--;
		
		// If we have no other tickers active, we can tick again
		if (m_tickersActive == 0)
			Tick();
	}

    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);
				
				if (!obj.name.Contains(m_myLevelName))
					if (m_debugChannel)
						m_debugChannel.Raise(this, 
						new string[]{
							"Added " + obj.name + " as a child of level prefab.",
							"<color=grey>Use the Save Scene & Level utility to finalize your changes.</color>"});
			}
	}

    /// <summary>
    /// Returns true if basic level information is not set up correctly.
    /// </summary>
    /// <returns></returns>
	bool CheckBasics () 
	{
        levelError = false;

        // Checks if terrain is set up correctly
        if (!AreTerrainsCorrect())
        {
            levelError = true;

			if (m_debugChannel)
				m_debugChannel.Raise(this, 
				new string[] {
					"Terrain is not set up correctly!",
					"Please do not edit or delete the terrain object."}, 
				DebugChannelSO.Severity.Error);
        }

        // Checks if the dummy player is set up correctly
        if (!CheckDummies())
        {
            levelError = true;

	        if (m_debugChannel)
		        m_debugChannel.Raise(this, 
		        new string[] {
			        "Dummy Player is not set up correctly!",
			        "Please do not edit or delete the Dummy Player object."}, 
		        DebugChannelSO.Severity.Error);
        }
        
		return levelError;
	}

	bool AreTerrainsCorrect()
    {
		if (UnityEngine.SceneManagement.SceneManager.GetActiveScene().name == "GameIsland")
			return true;

		// Set start value for number of terrains
		int terrainCount = 0;

		// Get all of the terrains in the scene
		Terrain[] terrains = Terrain.FindObjectsOfType<Terrain>();

		// Cycle through the objects to find terrain
		foreach (Terrain t in terrains)
		{

			// Increase the terrain count
			terrainCount++;

			// Make the first terrain the main one
			if (!TerrainMain)
			{
				TerrainMain = t.GetComponent<Terrain>();
			}

			// Remove any additional terrains
			if (TerrainMain && TerrainMain != t)
			{
				EditorApplication.delayCall += () => { DestroyImmediate(t.gameObject); };
				terrainCount--;
			}

			// Set the terrain count to 0 if there's no terrain
			if (!TerrainMain)
			{
				terrainCount = 0;
			}

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

		// Display terrain error message
		if (terrainCount == 0)
		{
			if (m_debugChannel)
				m_debugChannel.Raise(this, 
				new string[]{
					"There is no Terrain attached to the Level Prefab!",
					"Please add Terrain of the same name from the Do Not Edit OR Delete/Terrains folder to the Level Prefab."},
				DebugChannelSO.Severity.Error);
			return false;
		}
        else if (terrainCount > 1)
        {
	        if (m_debugChannel)
		        m_debugChannel.Raise(this, 
		        new string[]{
		        	"Too many terrains in scene!",
		        	"Please delete any Terrains other than the default terrain tile. <MapName_####### Terrain>"},
		        DebugChannelSO.Severity.Error);
            return false;
        }

        return true;
	}

	bool CheckDummies()
	{
		// Set start value for number of terrains
		int dummyCount = 0;

		// Get all of the dummies in the scene
		GameObject[] dummies = GameObject.FindGameObjectsWithTag("Dummy");

		// Cycle through the dummies
		foreach (GameObject dummy in dummies)
		{
			// Increase the dummy count
			dummyCount++;

			// Make the first dummy the main one
			if (!dummyPlayer)
			{
				dummyPlayer = dummy;
			}

			// Remove any additional dummies
			if (dummyPlayer && dummyPlayer != dummy)
			{
				UnityEditor.EditorApplication.delayCall += () => { DestroyImmediate(dummy); };
				dummyCount--;
			}

			// Set the dummy count to 0 if there's no dummy
			if (!dummyPlayer)
			{
				dummyCount = 0;
			}
		}

        // If we are in edit mode and NOT playtesting...
        if (!EditorApplication.isPlaying)
        {
		    // Display dummy error message
		    if (dummyCount == 0)
		    {
			    if (m_debugChannel)
				    m_debugChannel.Raise(this, 
				    new string[]{
				    	"Missing Dummy Player!",
					    "Please add a unique Dummy from the Level Prefabs folder to the gameobject."},
				    DebugChannelSO.Severity.Error);
			    
				return false;
		    }
		    else if (dummyCount > 1)
            {
			    if (m_debugChannel)
				    m_debugChannel.Raise(this, 
				    new string[]{
				    	"There are multiple Dummy Players in the scene!",
				    	"Please remove any extra dummies from the scene."},
				    DebugChannelSO.Severity.Error);
			    
				return false;
		    }
        }

		return true;
	}

	void KeepTheKidsInside (Transform parent) 
	{
		if (dontForceHierarchy)
			return;

		foreach (Transform kid in parent) 
		{
            // Skip the spell catalog
            if (kid.GetComponent<BattleRoyale.Spells.SpellCatalog>())
                continue;

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

		if (ShowLevelBounds) { 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 (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<NewWandScript> ())
			return "Wand";
        else if (check.GetComponent<BattleRoyale.Spells.SpellBase> ())
			return "Spell";
        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";

	}

	bool CanUpdate (int objIndex, string objType) {

		if (objType == "Empty")
			return false;

		// Tinkercad objects have Colliders on all their child game objects
		if (objType == "Tinker") {
			foreach (Transform tinker in objs[objIndex]) {

			}
			return true;

			// Disco Balls only have Colliders on two of their children, the rest can be ignored
		} else if (objType == "Disco") {
			foreach (Transform disco in objs[objIndex]) {
				if (disco.GetComponent<Collider> ()) {

				}
			}
			return true;

			// treat Potions and Loot Chests the same as other objects, they are only labled so they don't loop through their children
		} else if (objType == "Potion" || objType == "Loot" || objType == "") {
			return true;
		}

		return true;
	}

	void SavePrefab (bool save) {
		if (m_initialSave) {
			m_initialSave = false;
			
			if (m_debugChannel)
				m_debugChannel.Raise(this, 
				"Executing mandatory save...");
		}
		else if (m_debugChannel)
				m_debugChannel.Raise(this, 
			"Saving Scene & Level...");

		if (gameObject.activeInHierarchy && save) {

			// Search for an existing level prefab
			GameObject currentPrefab = (GameObject)AssetDatabase.LoadAssetAtPath("Assets/Levels/" + gameObject.name + ".prefab", typeof(GameObject));
			
			// If one is not found, create a new one
			if (!currentPrefab)
			{
                PrefabUtility.SaveAsPrefabAsset(gameObject, "Assets/Levels/" + gameObject.name + ".prefab");
			
				if (m_debugChannel)
					m_debugChannel.Raise(this, 
					new string[] {
						"Could not find existing level prefab file!",
						"A new prefab has been created at Assets/Levels/" + gameObject.name + ".prefab"});
			}
            else
			{
				PrefabUtility.SaveAsPrefabAssetAndConnect(gameObject, "Assets/Levels/" + gameObject.name + ".prefab", InteractionMode.UserAction);
			
	            if (m_debugChannel)
		            m_debugChannel.Raise(this, 
		            new string[] {
			            "Overwriting existing level prefab.",
			            "Assets/Levels/" + gameObject.name + ".prefab was overwritten."});
			}
			
			if (PrefabUtility.IsPartOfPrefabInstance(gameObject)){
				PrefabUtility.UnpackPrefabInstance(gameObject, PrefabUnpackMode.Completely, InteractionMode.AutomatedAction);
				
				if (m_debugChannel)
					m_debugChannel.Raise(this, 
					"Unpacking prefab instance.");
			}

			EditorSceneManager.SaveScene(EditorSceneManager.GetActiveScene ());
			
			if (m_debugChannel)
				m_debugChannel.Raise(this, 
				"Saved scene as " + SceneManager.GetActiveScene().name);
		}

		
		if (m_debugChannel)
			m_debugChannel.Raise(this, 
			new string[] {
				"Saving complete!",
				"Thank you for your patience."});
		
		savePrefab = false;
		startTicker = true;
		
		if (!UnityEditor.Lightmapping.isRunning)
			UnityEditor.Lightmapping.BakeAsync();
		
		HideHierarchyElements ();
	}

	void HideHierarchyElements () {
		GameObject canvases = GameObject.FindGameObjectWithTag ("Safe");
		if(canvases){
			canvases.hideFlags = HideFlags.NotEditable;
		}
	}

	void SnapTerrainEdges () {

		// Get the terrain heightmap width and height
		int xRes = TerrainMain.terrainData.heightmapWidth;
		int yRes = TerrainMain.terrainData.heightmapHeight;

		// GetHeights = gets the heightmap points of the terrain. Store those values in a float array
		float [,] heights = TerrainMain.terrainData.GetHeights(0, 0, xRes, yRes);

		// Manipulate the height data
		for (int i = 0; i < 257; i++) {
			for (int j = 0; j < 11; j++) {
				heights [0 + j, i] = 0.5f;
				heights [256 - j, i] = 0.5f;
				heights [i, 0 + j] = 0.5f;
				heights [i, 256 - j] = 0.5f;
			}
		}

		// SetHeights to change the terrain heightdata
		TerrainMain.terrainData.SetHeights(0, 0, heights);
	}

	void LockAndHide () {
		Tools.lockedLayers = (1 << LayerMask.NameToLayer ("UI")) |
			(1 << LayerMask.NameToLayer ("Camera")) |
			(1 << LayerMask.NameToLayer ("Level Editor")) |
			(1 << LayerMask.NameToLayer ("MiniMap")) |
			(1 << LayerMask.NameToLayer ("UI Cameras")) |
			(1 << LayerMask.NameToLayer ("BigMap")) |
			(1 << LayerMask.NameToLayer ("HideOnCamera")) |
			(1 << LayerMask.NameToLayer ("OuterBounds"));
		Tools.visibleLayers = ~((1 << LayerMask.NameToLayer ("UI")) |
			(1 << LayerMask.NameToLayer ("Camera")) |
			(1 << LayerMask.NameToLayer ("Level Editor")) |
			(1 << LayerMask.NameToLayer ("MiniMap")) |
			(1 << LayerMask.NameToLayer ("UI Cameras")) |
			(1 << LayerMask.NameToLayer ("BigMap")) |
			(1 << LayerMask.NameToLayer ("HideOnCamera")) |
			(1 << LayerMask.NameToLayer ("OuterBounds")));
	}

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

		// Terrain Defaults
		TerrainMain.transform.localPosition = Vector3.one * -200;
		TerrainMain.transform.eulerAngles = Vector3.zero;
		TerrainMain.transform.localScale = Vector3.one;

		// Dummy Defaults
		dummyPlayer.transform.localPosition = Vector3.zero;
		dummyPlayer.transform.eulerAngles = Vector3.zero;
		dummyPlayer.transform.localScale = Vector3.one;

		TerrainMain.gameObject.layer = 17;
	}

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

	/*
	public static void ClearConsole () {
		var assembly = Assembly.GetAssembly (typeof(SceneView));
		var type = assembly.GetType ("UnityEditor.LogEntries");
		var method = type.GetMethod ("Clear");
		method.Invoke (new object (), null);
	}

	void ErrorStop (bool error) {
		Undo.RecordObject (this, "Set Value");
		this.levelError = error;
		EditorUtility.SetDirty (this);
	}
	*/

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

	void SearchAndDestroyParticles(Transform parent)
	{
		foreach (Transform child in parent)
		{
			if (child.GetComponent<ParticleSystem>())
				Destroy(child.GetComponent<ParticleSystem>());

			SearchAndDestroyParticles(child);
		}
	}
	
	void ToggleAllGPUInstancing(Transform parent, bool _state)
	{
		foreach (Transform child in parent)
		{
			if (child.GetComponent<Renderer>())
				if (child.GetComponent<Renderer>().sharedMaterials.Length > 0)
					foreach (Material _mat in child.GetComponent<Renderer>().sharedMaterials)
						if (_mat != null)
							_mat.enableInstancing = _state;

			ToggleAllGPUInstancing(child, _state);
		}
	}
	void SearchAndDestroyOldWands(Transform parent)
	{
		foreach (Transform child in parent)
		{
			if (child.GetComponent<WandScript>())
            {
				Destroy(child);
				continue;
            }

			SearchAndDestroyOldWands(child);
		}
	}
}
