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

namespace Mox.Shared.UI
{
	public enum ELayout
	{
		Horizontal,
		Vertical,
		Stack
	};

	public enum EVisibility
	{
		Visible,
		Hidden,
		Collapsed
	};

	public enum EHorizontalAlignment
	{
		Left,
		Center,
		Right,
		Stretch
	};

	public enum EVerticalAlignment
	{
		Top,
		Center,
		Bottom,
		Stretch
	};

	[Flags]
	public enum ControlStates
	{
		Disabled = 0x01,
		Highlighted = 0x02,
		Focused = 0x04,
		Pressed = 0x08
	}

	public class Control : Serializable
	{
		public virtual void Render()
		{
			if (BackgroundColor != null)
			{
				RendererBase.Instance.FillRect(LayoutedRect, BackgroundColor);
			}
		}

		public Rect Measure()
		{
			if (Layout == ELayout.Horizontal)
			{
				int iWidth = 0;
				int iHeight = System.Math.Max(MinHeight.GetPixels(0, 0), Height?.GetPixels(0, 0) ?? 0);
				foreach (var c in Children)
				{
					if (c.Visibility == EVisibility.Collapsed)
					{
						continue;
					}

					iWidth += System.Math.Max(c.MinWidth.GetPixels(0, 0), c.Width?.GetPixels(0, 0) ?? 0);
					iHeight = System.Math.Max(iHeight, System.Math.Max(c.MinHeight.GetPixels(0, 0), c.Height?.GetPixels(0, 0) ?? 0));
				}

				iWidth = System.Math.Max(iWidth, System.Math.Max(MinWidth.GetPixels(0, 0), Width?.GetPixels(0, 0) ?? 0));
				return new Rect(0, 0, iWidth, iHeight);
			}
			else if (Layout == ELayout.Vertical)
			{
				int iWidth = System.Math.Max(MinWidth.GetPixels(0, 0), Width?.GetPixels(0, 0) ?? 0);
				int iHeight = 0;
				foreach (var c in Children)
				{
					if (c.Visibility == EVisibility.Collapsed)
					{
						continue;
					}

					iWidth = System.Math.Max(iWidth, System.Math.Max(c.MinWidth.GetPixels(0, 0), c.Width?.GetPixels(0, 0) ?? 0));
					iHeight += System.Math.Max(c.MinHeight.GetPixels(0, 0), c.Height?.GetPixels(0, 0) ?? 0);
				}
				
				iHeight = System.Math.Max(iHeight, System.Math.Max(MinHeight.GetPixels(0, 0), Height?.GetPixels(0, 0) ?? 0));
				return new Rect(0, 0, iWidth, iHeight);
			}
			else if (Layout == ELayout.Stack)
			{
				int iWidth = System.Math.Max(MinWidth.GetPixels(0, 0), Width?.GetPixels(0, 0) ?? 0);
				int iHeight = System.Math.Max(MinHeight.GetPixels(0, 0), Height?.GetPixels(0, 0) ?? 0);
				foreach (var c in Children)
				{
					if (c.Visibility == EVisibility.Collapsed)
					{
						continue;
					}

					iWidth = System.Math.Max(iWidth, System.Math.Max(c.MinWidth.GetPixels(0, 0), c.Width?.GetPixels(0, 0) ?? 0));
					iHeight = System.Math.Max(iHeight, System.Math.Max(c.MinHeight.GetPixels(0, 0), c.Height?.GetPixels(0, 0) ?? 0));
				}
				return new Rect(0, 0, iWidth, iHeight);
			}

			return null;
		}

		public void CalculateLayout()
		{
			if (Parent == null)
			{
				var w = Align(Width ?? new Measure(1, EUnit.Percent), Root.Instance.ClientWidth, (int)HorizontalAlignment);
				var h = Align(Height ?? new Measure(1, EUnit.Percent), Root.Instance.ClientHeight, (int)VerticalAlignment);
				LayoutedRect = new Rect(w.Item1, h.Item1, w.Item2, h.Item2);
			}

			if (Layout == ELayout.Horizontal)
			{
				LayoutFrame(c => c.Width ?? new Measure(c.Measure().Width, EUnit.Pixel), LayoutedRect.Width, (_control, _iLeft, _iWidth) =>
				{
					var alignment = Align(_control.Height ?? new Measure(_control.Measure().Height, EUnit.Pixel), LayoutedRect.Height, (int)_control.VerticalAlignment);

					_control.LayoutedRect = new Rect(LayoutedRect.Left + _iLeft, LayoutedRect.Top + alignment.Item1, _iWidth, alignment.Item2);
					_control.CalculateLayout();
				});
			}
			else if (Layout == ELayout.Vertical)
			{
				LayoutFrame(c => c.Height ?? new Measure(c.Measure().Height, EUnit.Pixel), LayoutedRect.Height, (_control, _iTop, _iHeight) =>
				{
					var alignment = Align(_control.Width ?? new Measure(_control.Measure().Width, EUnit.Pixel), LayoutedRect.Width, (int)_control.HorizontalAlignment);

					_control.LayoutedRect = new Rect(LayoutedRect.Left + alignment.Item1, LayoutedRect.Top + _iTop, alignment.Item2, _iHeight);
					_control.CalculateLayout();
				});
			}
			else if (Layout == ELayout.Stack)
			{
				foreach (var child in Children)
				{
					if (child.Visibility == EVisibility.Collapsed)
					{
						continue;
					}

					var m = child.Measure();
					var w = Align(child.Width ?? new Measure(m.Width, EUnit.Pixel), LayoutedRect.Width, (int)child.HorizontalAlignment);
					var h = Align(child.Height ?? new Measure(m.Height, EUnit.Pixel), LayoutedRect.Height, (int)child.VerticalAlignment);
					child.LayoutedRect = new Rect(LayoutedRect.Left + w.Item1, LayoutedRect.Top + h.Item1, w.Item2, h.Item2);
					child.CalculateLayout();
				}
			}
		}

		private Tuple<int, int> Align(Measure _size, int _iAvailableSize, int _iAlignment)
		{
			var iSize = _size.GetPixels(_iAvailableSize);

			if (iSize >= _iAvailableSize || _iAlignment == (int)EHorizontalAlignment.Stretch)
			{
				return Tuple.Create(0, _iAvailableSize);
			}

			var iFreeSpace = _iAvailableSize - iSize;

			switch (_iAlignment)
			{
				case (int)EHorizontalAlignment.Left: return Tuple.Create(0, iSize);
				case (int)EHorizontalAlignment.Right: return Tuple.Create(iFreeSpace, iSize);
				default: return Tuple.Create(iFreeSpace / 2, iSize);
			}
		}

		private void LayoutFrame(Func<Control, Measure> _size, int _iAvailableSize, Action<Control, int, int> _resize)
		{
			var dWeight = 0.0;
			var iMaxSize = _iAvailableSize;

			foreach (var child in Children)
			{
				if (child.Visibility == EVisibility.Collapsed)
				{
					continue;
				}

				var size = _size(child);

				if (size.Unit == EUnit.Star)
				{
					dWeight += size.Value;
				}
				else if (size.Unit == EUnit.Percent)
				{
					_iAvailableSize -= size.GetPixels(iMaxSize);
				}
				else
				{
					_iAvailableSize -= (int) size.Value;
				}
			}

			var iPosition = 0;

			foreach (var child in Children)
			{
				if (child.Visibility == EVisibility.Collapsed)
				{
					continue;
				}

				var iSize = _size(child).GetPixels(_iAvailableSize, dWeight);
				_resize(child, iPosition, iSize);
				iPosition += iSize;
			}
		}

		public Control HitTest(int _iX, int _iY)
		{
			for (int i = Children.Count - 1; i >= 0; --i)
			{
				if (Children[i].Visibility == EVisibility.Visible)
				{
					var control = Children[i].HitTest(_iX, _iY);

					if (control != null)
					{
						return control;
					}
				}
			}

			if (IsHitTestVisible && Visibility == EVisibility.Visible && LayoutedRect != null)
			{
				if (_iX >= LayoutedRect.Left &&
				    _iY >= LayoutedRect.Top &&
				    _iX <= LayoutedRect.Left + LayoutedRect.Width &&
				    _iY <= LayoutedRect.Top + LayoutedRect.Height)
				{
					return this;
				}
			}

			return null;
		}

		internal void Activate()
		{
			if ((States & ControlStates.Highlighted) != 0)
			{
				return;
			}

			States |= ControlStates.Highlighted;

			if ((States & ControlStates.Pressed) == 0)
			{
				Root.Instance.FireEvent(this, (States & ControlStates.Disabled) == 0 ? EEventType.ControlActivated : EEventType.ControlDisabledActivated, EEventPropagation.Bubble, null);
			}
		}

		internal void Deactivate()
		{
			if ((States & ControlStates.Highlighted) == 0)
			{
				return;
			}

			States &= ~(ControlStates.Highlighted);

			if ((States & ControlStates.Pressed) != 0)
			{
				States &= ~(ControlStates.Pressed);
			}

			Root.Instance.FireEvent(this, (States & ControlStates.Disabled) == 0 ? EEventType.ControlDeactivated : EEventType.ControlDisabledDeactivated, EEventPropagation.Bubble, null);
		}

		internal void Focus()
		{
			if ((States & ControlStates.Focused) != 0)
			{
				return;
			}

			States |= ControlStates.Focused;

			Root.Instance.FireEvent(this, (States & ControlStates.Disabled) == 0 ? EEventType.ControlFocus : EEventType.ControlDisabledFocus, EEventPropagation.Both, null);
		}

		internal virtual void Blur()
		{
			if ((States & ControlStates.Focused) == 0)
			{
				return;
			}

			States &= ~ControlStates.Focused;

			Root.Instance.FireEvent(this, (States & ControlStates.Disabled) == 0 ? EEventType.ControlBlur : EEventType.ControlDisabledBlur, EEventPropagation.Both, null);
		}

		internal virtual bool HandleEvent(SEventProperties _eventProperties)
		{
			switch (_eventProperties.EventType)
			{
			case EEventType.MouseButtonDown:

				if ((States & ControlStates.Pressed) == 0)
				{
					States |= ControlStates.Pressed;
					States |= ControlStates.Highlighted;
				}

				break;
			case EEventType.MouseButtonUp:

				if ((States & ControlStates.Pressed) != 0)
				{
					States &= ~(ControlStates.Pressed);
				}

				break;
			}

			return false;
		}

		public virtual bool HandleSound(SEventProperties _eventProperties, out string _sSound)
		{
			if (_eventProperties.EventType == EEventType.ControlActivated && !string.IsNullOrEmpty(m_sSoundHover))
			{
				_sSound = m_sSoundHover;
				return true;
			}

			_sSound = null;
			return false;
		}

		internal virtual void OnLostCapture()
		{
			if ((States & ControlStates.Pressed) != 0)
			{
				States &= ~(ControlStates.Pressed);
			}
		}

		public override void Serialize(Serializer.Serializer _serializer)
		{
			_serializer.SerializeEnum(ref m_eHorizontalAlignment, nameof(HorizontalAlignment), EHorizontalAlignment.Center);
			_serializer.SerializeEnum(ref m_eVerticalAlignment, nameof(VerticalAlignment), EVerticalAlignment.Center);
			_serializer.Serialize(ref m_Width, nameof(Width), null);
			_serializer.Serialize(ref m_Height, nameof(Height), null);
			_serializer.Serialize(ref m_bIsHitTestVisible, nameof(IsHitTestVisible), true);
			_serializer.SerializeEnum(ref m_eVisibility, nameof(Visibility), EVisibility.Visible);
			_serializer.SerializeEnum(ref m_eLayout, nameof(Layout), ELayout.Stack);
			_serializer.Serialize(ref m_sSoundHover, nameof(SoundHover), string.Empty);
			_serializer.Serialize(ref m_BackgroundColor, nameof(BackgroundColor), null);

			if (_serializer.Serialize(ref m_sTag, nameof(Tag), null) && _serializer.IsLoading)
			{
				Root.Instance.TaggedControls.Add(m_sTag, this);
			}

			if (_serializer.IsLoading)
			{
				m_lChildren = new List<Control>();
				while (Root.Instance.SerializeControl(_serializer, out var child))
				{
					m_lChildren.Add(child);
					child.Parent = this;
				}
			}
		}

		public EHorizontalAlignment HorizontalAlignment
		{
			get => m_eHorizontalAlignment;
			set
			{
				m_eHorizontalAlignment = value;
				Root.Instance.InvalidateLayout();
			}
		}

		public EVerticalAlignment VerticalAlignment
		{
			get => m_eVerticalAlignment;
			set
			{
				m_eVerticalAlignment = value;
				Root.Instance.InvalidateLayout();
			}
		}

		public ControlStates States { get => m_eStates; set => m_eStates = value; }

		public Measure Width
		{
			get => m_Width;
			set
			{
				m_Width = value;
				Root.Instance.InvalidateLayout();
			}
		}

		public Measure Height
		{
			get => m_Height;
			set
			{
				m_Height = value;
				Root.Instance.InvalidateLayout();
			}
		}

		public virtual Measure MinWidth => new Measure(0, EUnit.Pixel);

		public virtual Measure MinHeight => new Measure(0, EUnit.Pixel);

		public EVisibility Visibility
		{
			get => m_eVisibility;
			set
			{
				m_eVisibility = value;
				Root.Instance.InvalidateLayout();
			}
		}

		public Rect LayoutedRect { get; set; }

		public bool IsHitTestVisible { get => m_bIsHitTestVisible; set => m_bIsHitTestVisible = value; }

		public Color BackgroundColor { get => m_BackgroundColor; set => m_BackgroundColor = value; }

		public ELayout Layout
		{
			get => m_eLayout;
			set
			{
				m_eLayout = value;
				Root.Instance.InvalidateLayout();
			}
		}

		public List<Control> Children { get => m_lChildren; set => m_lChildren = value; }

		public Control Parent { get; set; }

		public bool ConsumeBubbleEvents { get; set; }

		public string Tag
		{
			get => m_sTag;
			set
			{
				if (!string.IsNullOrEmpty(m_sTag))
				{
					Root.Instance.TaggedControls.Remove(m_sTag);
				}

				m_sTag = value;
				Root.Instance.TaggedControls.Add(m_sTag, this);
			}
		}
		
		public string SoundHover { get => m_sSoundHover; set => m_sSoundHover = value; }
		
		private string m_sSoundHover = string.Empty;
		private EHorizontalAlignment m_eHorizontalAlignment = EHorizontalAlignment.Center;
		private EVerticalAlignment m_eVerticalAlignment = EVerticalAlignment.Center;
		private ControlStates m_eStates;
		private Measure m_Width;
		private Measure m_Height;
		private bool m_bIsHitTestVisible = true;
		private EVisibility m_eVisibility = EVisibility.Visible;
		private ELayout m_eLayout = ELayout.Stack;
		private List<Control> m_lChildren;
		private string m_sTag;
		private Color m_BackgroundColor;
	}
}
