using System;
using System.Collections.Generic;
using Mox.Shared.Serializer;

namespace Mox.Shared.UI
{
	public struct SMouseEventData
	{
		public SMouseEventData(int _iX, int _iY, int _iIndex = -1)
		{
			X = _iX;
			Y = _iY;
			Index = _iIndex;
		}

		public int X;
		public int Y;
		public int Index;
	}

	public class Root
	{
		public Root(int _iClientWidth, int _iClientHeight)
		{
			ClientWidth = _iClientWidth;
			ClientHeight = _iClientHeight;

			MainControl = new Control();
			MainControl.Children = new List<Control>();

			Instance = this;
		}

		public void Layout()
		{
			if (m_bLayoutDirty)
			{
				m_bLayoutDirty = false;
				MainControl.CalculateLayout();
			}
		}

		public bool OnMouseDown(int _iX, int _iY)
		{
			return FireEvent(EEventType.MouseButtonDown, new SMouseEventData(_iX, _iY));
		}

		public bool OnMouseMoved(int _iX, int _iY)
		{
			return FireEvent(EEventType.MouseMove, new SMouseEventData(_iX, _iY));
		}

		public bool OnMouseUp(int _iX, int _iY)
		{
			return FireEvent(EEventType.MouseButtonUp, new SMouseEventData(_iX, _iY));
		}

		public bool FireEvent(EEventType _eEventType, object _data)
		{
			var handled = false;

			if (EventTypeHelper.IsMouseEvent(_eEventType))
			{
				m_LastMouseEvent = (SMouseEventData) _data;

				var control = MainControl.HitTest(m_LastMouseEvent.X, m_LastMouseEvent.Y);

				if (EventTypeHelper.IsFocusStateEvent(_eEventType))
				{
					FocusedControl = control;
				}

				if (MouseCaptureControl == null)
				{
					if (EventTypeHelper.IsActiveStateEvent(_eEventType) && control != null)
					{
						ActiveControl = control;
					}
					else
					{
						ActiveControl = null;
					}
				}

				// Send the event to the active mouse capture control, 
				// control will handle if the mouse was released above the last pressed control
				if (MouseCaptureControl != null)
				{
					handled |= FireEvent(MouseCaptureControl, _eEventType, EEventPropagation.Bubble, _data, control);
				}
				else if (control != null)
				{
					handled |= FireEvent(control, _eEventType, EEventPropagation.Bubble, _data, control);
				}

				if (m_bIsDragging && m_DraggedNode != null)
				{
					if (_eEventType == EEventType.MouseButtonUp)
					{
						FireEvent(m_DraggedNode, EEventType.EndDrag, EEventPropagation.Bubble, _data, control);
						m_DraggedNode = null;
						m_bIsDragging = false;
					}
					else if (_eEventType == EEventType.MouseMove)
					{
						FireEvent(m_DraggedNode, EEventType.Dragged, EEventPropagation.Bubble, _data, control);
					}
				}
				else
				{
					if (m_DraggedNode != null)
					{
						if (_eEventType == EEventType.MouseButtonUp)
						{
							m_DraggedNode = null;
						}
						else if (_eEventType == EEventType.MouseMove)
						{
							int dx = (m_LastMouseEvent.X - m_DragStartPointEvent.X);
							int dy = (m_LastMouseEvent.Y - m_DragStartPointEvent.Y);

							if (dx * dx + dy * dy > 30)
							{
								m_bIsDragging = true;

								if (MouseCaptureControl != null && IsNodeParentOf(MouseCaptureControl, m_DraggedNode))
								{
									m_DraggedNode = MouseCaptureControl;
								}

								FireEvent(m_DraggedNode, EEventType.BeginDrag, EEventPropagation.Bubble, _data,
								          control);

								FireEvent(m_DraggedNode, EEventType.Dragged, EEventPropagation.Bubble, _data, control);
							}
						}
					}
					else
					{
						if (_eEventType == EEventType.MouseButtonDown)
						{
							m_DraggedNode = control;
							m_DragStartPointEvent = m_LastMouseEvent;
						}
					}
				}
			}

			return handled;
		}

		internal bool IsNodeParentOf(Control potentialParent, Control potentialChild)
		{
			if (potentialParent == null || potentialChild == null)
			{
				return false;
			}

			potentialChild = potentialChild.Parent;

			while (potentialChild != null)
			{
				if (potentialParent == potentialChild)
				{
					return true;
				}

				potentialChild = potentialChild.Parent;
			}

			return false;
		}

		public Control ActiveControl
		{
			get { return m_ActiveControl; }
			set
			{
				if (m_ActiveControl != value)
				{
					m_ActiveControl?.Deactivate();

					m_ActiveControl = value;

					m_ActiveControl?.Activate();
				}
			}
		}

		public Control FocusedControl
		{
			get { return m_FocusedControl; }
			set
			{
				if (m_FocusedControl != value)
				{
					m_FocusedControl?.Blur();

					m_FocusedControl = value;

					m_FocusedControl?.Focus();
				}
			}
		}

		internal bool FireEvent(Control _source, EEventType _eEventType, EEventPropagation _ePropagation, object _data,
		                        Control _target = null)
		{
			var eventProperties = new SEventProperties()
			{
				EventType = _eEventType,
				Source = _source,
				Data = _data
			};

			if (_target != null)
			{
				eventProperties.Target = _target;
			}

			return FireEvent(_source, _ePropagation, eventProperties, true);
		}

		private bool FireEvent(Control _control, EEventPropagation _ePropagation, SEventProperties _eventProperties,
		                       bool _bHandleSound)
		{
			var bHandled = false;

			if (_control != null)
			{
				if (_bHandleSound)
				{
					_bHandleSound = !HandleSound(_control, _eventProperties);
				}

				bHandled |= HandleEvent(_control, _eventProperties, _ePropagation);

				if (!bHandled)
				{
					switch (_ePropagation)
					{
					case EEventPropagation.Bubble:
						bHandled |= FireEvent(_control.Parent, _ePropagation, _eventProperties, _bHandleSound);

						break;
					case EEventPropagation.Tunnel:

						foreach (var child in _control.Children)
						{
							bHandled |= FireEvent(child, _ePropagation, _eventProperties, _bHandleSound);
						}

						break;
					case EEventPropagation.Both:

						bHandled |= FireEvent(_control.Parent, EEventPropagation.Bubble, _eventProperties,
						                      _bHandleSound);

						foreach (var child in _control.Children)
						{
							bHandled |= FireEvent(child, EEventPropagation.Tunnel, _eventProperties, _bHandleSound);
						}

						break;
					}
				}
			}

			return bHandled;
		}

		private bool HandleEvent(Control _control, SEventProperties _eventProperties, EEventPropagation _ePropagation)
		{
			bool stopPropagation = false;

			if (EventHandlers.TryGetValue(_eventProperties.EventType, out var eventHandler))
			{
				if (eventHandler != null)
				{
					eventHandler(_eventProperties);
					stopPropagation = true;
				}
				else
				{
					EventHandlers.Remove(_eventProperties.EventType);
				}
			}

			if (_control.ConsumeBubbleEvents && _ePropagation == EEventPropagation.Bubble)
			{
				stopPropagation = true;
			}

			stopPropagation |= _control.HandleEvent(_eventProperties);

			return stopPropagation;
		}

		private bool HandleSound(Control _control, SEventProperties _eventProperties)
		{
			if (_control.HandleSound(_eventProperties, out string sSound))
			{
				if (sSound != null)
				{
					OnPlaySound?.Invoke(sSound);
				}

				return true;
			}

			return false;
		}

		public bool SerializeControl(Serializer.Serializer _serializer, out Control _control)
		{
			if (_serializer.StartChild(new [] { "Control", "Image", "Button", "Label", "CheckBox" }, out var sName))
			{
				_control = CreateControl(sName);

				string sTemplate = null;
				if (_serializer.Serialize(ref sTemplate, "Template", null) &&
				    m_dTemplates.TryGetValue(sTemplate, out var memoryNode))
				{
					var load = new MemorySerializerLoad(memoryNode);
					_control.Serialize(load);
				}

				_control.Serialize(_serializer);
				_serializer.EndChild();
				return true;
			}

			_control = null;
			return false;
		}

		public void SerializeTemplates(Serializer.Serializer _serializer)
		{
			if (_serializer.StartChild("Templates"))
			{
				while (_serializer.StartChild(new [] { "Control", "Image", "Button", "Label", "CheckBox", "Sound" }, out var sName))
				{
					var control = CreateControl(sName);
					
					string sTemplate = null;
					if (_serializer.Serialize(ref sTemplate, "Template", null))
					{
						control.Serialize(_serializer);
						var save = new MemorySerializerSave();
						control.Serialize(save);
						m_dTemplates.Add(sTemplate, save.Root);
					}

					_serializer.EndChild();
				}

				_serializer.EndChild();
			}
		}

		private static Control CreateControl(string sName)
		{
			Control _control;

			if (sName == "Control")
			{
				_control = new Control();
			}
			else if (sName == "Image")
			{
				_control = new Image();
			}
			else if (sName == "Button")
			{
				_control = new Button();
			}
			else if (sName == "Label")
			{
				_control = new Label();
			}
			else if (sName == "CheckBox")
			{
				_control = new CheckBox();
			}
			else
			{
				throw new Exception();
			}

			return _control;
		}

		public int ClientWidth { get; set; }

		public int ClientHeight { get; set; }

		public Control MainControl { get; }

		public Control MouseCaptureControl
		{
			get { return m_MouseCaptureControl; }
			set
			{
				if (m_MouseCaptureControl != value)
				{
					m_MouseCaptureControl?.OnLostCapture();
					m_MouseCaptureControl = value;
					ActiveControl = value;
				}
			}
		}

		public void InvalidateLayout()
		{
			m_bLayoutDirty = true;
		}

		public static Root Instance { get; private set; }

		public delegate void SoundEventDelegate(string _sSoundId);

		public readonly Dictionary<string, Control> TaggedControls = new Dictionary<string, Control>();
		public readonly Dictionary<EEventType, NuiEventHandler> EventHandlers = new Dictionary<EEventType, NuiEventHandler>();
		private readonly Dictionary<string, MemoryNode> m_dTemplates = new Dictionary<string, MemoryNode>();

		public event SoundEventDelegate OnPlaySound;
		private SMouseEventData m_LastMouseEvent;
		private Control m_MouseCaptureControl;
		private bool m_bIsDragging;
		private Control m_DraggedNode;
		private SMouseEventData m_DragStartPointEvent;
		private Control m_ActiveControl;
		private Control m_FocusedControl;
		private bool m_bLayoutDirty;
	}
}
