using UnityEngine;
using System.Collections;
using UnityEditor;

namespace UnityEditor
{
	[System.Serializable]
	[InitializeOnLoad]
	public class GISEditorSplatLayer : GISEditorLayer
	{
		[SerializeField]
		GISSplatData m_SplatData;
		SerializedObject m_SplatDataSO;
		SerializedProperty m_RowCount;
		SerializedProperty m_ColumnCount;
		SerializedProperty m_EastLongitude;
		SerializedProperty m_WestLongitude;
		SerializedProperty m_NorthLatitude;
		SerializedProperty m_SouthLatitude;

		static GISEditorSplatLayer ()
		{
			GISLayerRegistration.RegisterLayer (typeof (GISEditorSplatLayer), "Splat Data Layer", GISLayerRegistration.LayerFlag.None);
		}

		public GISEditorSplatLayer ()
		{
			name = "Splat Data Layer";
		}

		protected override void AcceptDragAndDropObjects (Object[] objects)
		{
			GISSplatData splatData = objects[0] as GISSplatData;
			if (splatData != null)
			{
				m_SplatData = splatData;
				PrepareSerialize ();
				ReconstructLayer ();
			}
			else if (m_SplatData)
			{
				Texture2D tex = objects[0] as Texture2D;
				if (tex != null)
				{
					// Check where we should place the texture
					Vector2 mousePos = Event.current.mousePosition;
					Vector3 pos = Handles.inverseMatrix.MultiplyPoint (mousePos);
					int rowCount = m_RowCount.intValue;
					int columnCount = m_ColumnCount.intValue;
					for (int i = 0; i < rowCount; ++i)
					{
						for (int j = 0; j < columnCount; ++j)
						{
							// Skip the first element since it's our 'root layer'
							GISEditorLayer.LayerObject lyrObj = m_LayerObjects[1 + j + (i * columnCount)] as GISEditorLayer.LayerObject;
							
							Rect r = lyrObj.geoPosition;
							// I really really need to fix this alternate coordinate system
							r.y -= r.height;
							if (r.Contains ( pos))
							{
								m_SplatData.SetGISImage (i, j, tex);
								i = m_SplatData.rowCount;		
								break;
							}
						}
					}
				}
			}
		}

		protected override bool CanAcceptDragAndDropObjects (Object[] objects)
		{
			if (objects.Length == 1)
			{
				// we only take one texture at one time
				if (m_SplatData)
				{
					// Check if drag and drop is  Texture2D
					Texture2D tex = objects[0] as Texture2D;
					if (tex != null)
					{
						// yay we manage to cast!
						return true;
					}
				}
				// Check if drag and drop is GISSplatData
				GISSplatData splatData = objects[0] as GISSplatData;
				if (splatData != null)
				{
					return true;
				}
			}
			return false;
		}

		public override void LayerSelected (bool selected)
		{
			if (selected)
			{
				// Set m_SplatData as the active object to trigger inspector view for it.
				// NOTE: doesn't matter if it's null, inspector will just be empty.
				Selection.activeObject = m_SplatData;

				if (m_LayerObjects.Count > 1) // index 0 is our root object don't consider it.
					selectedObject = m_LayerObjects[0];
				else
					selectedObject = null;
			}
		}

		protected override void RenderInternal ()
		{
			base.RenderInternal ();
			// Update all layer's position based on the root LayerObject
			if (m_SplatData != null && m_SplatDataSO != null)
			{
				GISEditorLayer.LayerObject rootLyrObject = m_LayerObjects[0] as GISEditorLayer.LayerObject;
				int rowCount = m_RowCount.intValue;
				int columnCount = m_ColumnCount.intValue;
				float rowSpacing = rootLyrObject.geoPosition.height/ (float)rowCount;
				float columnSpacing = rootLyrObject.geoPosition.width/ (float)columnCount;
				Rect texRect = new Rect (rootLyrObject.geoPosition.x, rootLyrObject.geoPosition.y, columnSpacing, rowSpacing);

				GL.Begin (GL.LINES);
				GL.Color (Color.white);

				for (int i = 0; i < rowCount; ++i)
				{
					for (int j = 0; j < columnCount; ++j)
					{
						GISEditorLayer.LayerObject lyrObj = m_LayerObjects[1 + j + (i * m_SplatData.columnCount)] as GISEditorLayer.LayerObject;
						lyrObj.geoPosition = texRect;
						texRect.x += columnSpacing;

						lyrObj.texture = m_SplatData.GetGISImage (i, j);
						// if texture is null, we draw a square in it's place
						if (lyrObj.texture == null)
						{	
							Rect r = lyrObj.geoPosition;
							r.height *= -1;

							Vector3[] points = new Vector3[5];
							int p = 0;
							points[p++] = new Vector3 (r.xMin, r.yMin, 0f);
							points[p++] = new Vector3 (r.xMax, r.yMin, 0f);
							points[p++] = new Vector3 (r.xMax, r.yMax, 0f);
							points[p++] = new Vector3 (r.xMin, r.yMax, 0f);

							DrawLine (points[0], points[1]);
							DrawLine (points[1], points[2]);
							DrawLine (points[2], points[3]);
							DrawLine (points[3], points[0]);
						}
					}
					texRect.x = rootLyrObject.geoPosition.x;
					texRect.y -= rowSpacing;
				}

				GL.End ();
			}
		}

		private static void DrawLine (Vector3 p1, Vector3 p2)
		{
			GL.Vertex (p1);
			GL.Vertex (p2);
		}

		public override void OnEnable ()
		{
			if (m_SplatData != null)
			{
				PrepareSerialize ();
			}
			base.OnEnable ();
		}

		void PrepareSerialize ()
		{
			m_SplatDataSO = new SerializedObject (m_SplatData);
			m_RowCount = m_SplatDataSO.FindProperty ("rowCount");
			m_ColumnCount = m_SplatDataSO.FindProperty ("columnCount");
			m_EastLongitude = m_SplatDataSO.FindProperty ("eastLongitude");
			m_WestLongitude = m_SplatDataSO.FindProperty ("westLongitude");
			m_NorthLatitude = m_SplatDataSO.FindProperty ("northLatitude");
			m_SouthLatitude = m_SplatDataSO.FindProperty ("southLatitude");
		}

		void ReconstructLayer ()
		{
			// Clear and add our root object
			m_LayerObjects.Clear ();
			
			if (m_SplatData)
			{
				AddRootLayerObject (m_WestLongitude.floatValue,
									m_NorthLatitude.floatValue,
									m_EastLongitude.floatValue,
									m_SouthLatitude.floatValue);

				GISEditorLayer.LayerObject lyrObj = m_LayerObjects[0] as GISEditorLayer.LayerObject;
				int rowCount = m_RowCount.intValue;
				int columnCount = m_ColumnCount.intValue;
				float rowSpacing = lyrObj.geoPosition.height/ (float)rowCount;
				float columnSpacing = lyrObj.geoPosition.width/ (float)columnCount;
				Rect texRect = new Rect (lyrObj.geoPosition.x, lyrObj.geoPosition.y, columnSpacing, rowSpacing);

				for (int i = 0; i < rowCount; ++i)
				{
					for (int j = 0; j < columnCount; ++j)
					{
						LayerObject obj = new LayerObject ();
						obj.texture = m_SplatData.GetGISImage (i, j);
						obj.geoPosition = texRect;
						obj.locked = true;
						m_LayerObjects.Add (obj);
						texRect.x += columnSpacing;
					}
					texRect.x = lyrObj.geoPosition.x;
					texRect.y -= rowSpacing;
				}
			}
		}

		private void Reset ()
		{
			m_SplatData = null;
			m_SplatDataSO = null;
			m_RowCount = null;
			m_ColumnCount = null;
			m_EastLongitude = null;
			m_WestLongitude = null;
			m_NorthLatitude = null;
			m_SouthLatitude = null;

			m_LayerObjects.Clear ();
		}

		protected override void OnLayerObjectManipulated (LayerObject manipulatedLayerObject)
		{
			if (m_LayerObjects.Count == 0)
				return;
			GISEditorLayer.LayerObject rootLyrObject = m_LayerObjects[0] as GISEditorLayer.LayerObject;
			Rect splatGeoPosition = rootLyrObject.geoPosition;

			m_EastLongitude.floatValue = splatGeoPosition.x + splatGeoPosition.width;
			m_WestLongitude.floatValue = splatGeoPosition.x;
			m_NorthLatitude.floatValue = splatGeoPosition.y;
			m_SouthLatitude.floatValue = splatGeoPosition.y - splatGeoPosition.height;

			m_SplatDataSO.ApplyModifiedProperties ();
		}

		public override void Update ()
		{
			base.Update ();

			if (m_Visible)
			{
				if (m_SplatDataSO != null)
				{
					m_SplatDataSO.Update ();

					if (IsDirty ())
					{
						ReconstructLayer ();
					}
				}			
			}
		}

		private bool IsDirty ()
		{
			bool isDirty = false;
			isDirty = m_LayerObjects.Count != m_RowCount.intValue * m_ColumnCount.intValue + 1; /*Root layer object*/

			if (!isDirty)
			{
				GISEditorLayer.LayerObject rootLyrObject = m_LayerObjects[0] as GISEditorLayer.LayerObject;
				Rect splatGeoPosition = rootLyrObject.geoPosition;

				isDirty = (
					!Mathf.Approximately (m_EastLongitude.floatValue, splatGeoPosition.x + splatGeoPosition.width)
					|| !Mathf.Approximately (m_WestLongitude.floatValue, splatGeoPosition.x)
					|| !Mathf.Approximately (m_NorthLatitude.floatValue, splatGeoPosition.y)
					|| !Mathf.Approximately (m_SouthLatitude.floatValue, splatGeoPosition.y - splatGeoPosition.height));
			}

			return isDirty;
		}

		// We add a dummy layer object to represent all the splats and for group scaling
		void AddRootLayerObject (float westLongitude, float northLatitude, float eastLongitude, float southLatitude)
		{
			if (m_LayerObjects.Count == 0)
			{
				GISEditorLayer.LayerObject lyrObj = new GISEditorLayer.LayerObject ();
				lyrObj.geoPosition = new Rect (westLongitude, northLatitude, 
											(eastLongitude - westLongitude), 
											(northLatitude - southLatitude));
				m_LayerObjects.Add (lyrObj);
			}
		}

		public override Rect GeoArea ()
		{
			return m_LayerObjects.Count == 0 ? new Rect(-180, 90, 360, 180) : m_LayerObjects[0].geoPosition;
		}

		public override void ApplyChanges (Rect geoPosition, GISTerrain gisTerrain)
		{
			// root object contains our geo position
			if (m_LayerObjects.Count > 0)
			{
				Rect offset = m_SplatData.CalculateTextureOffset (geoPosition.x + geoPosition.width, geoPosition.x, geoPosition.y, geoPosition.y - geoPosition.height);
				m_SplatData.ApplyData (gisTerrain, offset, (int)gisTerrain.terrainBasemapResolution);
			}
		}

		protected override void DeleteLayer (LayerObject lyrObj)
		{
			if (EditorUtility.DisplayDialog("Delete SplatData Layer?",
				"Are you sure you want to remove the SplatData on this layer?" , 
				"Yes", "No"))
			{
				Reset ();
				selectedObject = null;
			}
		}
	}
}