/*
© 2017 Altavant Technologies Inc.
Web: http://www.altavant.com
*/

using UIKit;

namespace Altavant.Fusion.Wrappers
{
	using System.Collections.Generic;
	using Fusion.Core.Data;
	using Fusion.Graphics;
	using Fusion.UI;
	using Fusion.UI.Listeners;
	using Fusion.Utils;

	internal class Interceptor : IReleasable
	{
		private List<IWindow> _windows;
		private Dictionary<UITouch, Touch> _touches;
		private List<IListener> _listeners;
		private ChangeList<IListener> _listenerChanges;
		private TouchCollection _touchesBegan;
		private TouchCollection _touchesMoved;
		private TouchCollection _touchesEnded;
		private TouchCollection _touchesCancelled;
		private TouchCollection _touchesDelivered;

		public Interceptor()
		{
			_windows = new List<IWindow>();
			_touches = new Dictionary<UITouch, Touch>();
			_listeners = new List<IListener>();
			_listenerChanges = new ChangeList<IListener>();
			_touchesBegan = new TouchCollection();
			_touchesMoved = new TouchCollection();
			_touchesEnded = new TouchCollection();
			_touchesCancelled = new TouchCollection();
			_touchesDelivered = new TouchCollection();
		}

		public void AddWindow(IWindow window)
		{
			_windows.Add(window);
		}

		public void RemoveWindow(IWindow window)
		{
			_windows.Remove(window);
		}

		public void RemoveAllWindows()
		{
			_windows.Clear();
		}

		public void Process(UIEvent evt)
		{
			if ((evt.Type == UIEventType.Touches) && (evt.AllTouches != null))
			{
				_listenerChanges.Sync(_listeners);

				if (_listeners.Count == 0)
					return;

				foreach (UITouch touch in evt.AllTouches)
				{
					switch (touch.Phase)
					{
						case UITouchPhase.Began:
							CreateTouch(touch);

							break;
							
						case UITouchPhase.Moved:
							UpdateTouch(touch, TouchPhase.Moved);

							break;
							
						case UITouchPhase.Ended:
							UpdateTouch(touch, TouchPhase.Ended);

							DisposeTouch(touch);

							break;
							
						case UITouchPhase.Cancelled:
							UpdateTouch(touch, TouchPhase.Cancelled);

							DisposeTouch(touch);

							break;
					}
				}

				int i = _listeners.Count - 1;

				while (i >= 0)
				{
					IListener listener = _listeners[i];

					if (listener.Active)
					{
						if (_touchesBegan.Count > 0)
							listener.TouchesBegan(_touchesBegan);

						if (_touchesMoved.Count > 0)
							listener.TouchesMoved(_touchesMoved);

						if (_touchesEnded.Count > 0)
							listener.TouchesEnded(_touchesEnded);

						if (_touchesCancelled.Count > 0)
							listener.TouchesCancelled(_touchesCancelled);

						if (listener.Exclusive)
							break;
					}

					i--;
				}

				if (_touchesBegan.Count > 0)
					_touchesBegan.ForEach(t => _touchesDelivered.Add(t));

				if (_touchesEnded.Count > 0)
					_touchesEnded.ForEach(t => _touchesDelivered.Remove(t));

				if (_touchesCancelled.Count > 0)
					_touchesCancelled.ForEach(t => _touchesDelivered.Remove(t));

				_touchesBegan.Clear();
				_touchesMoved.Clear();
				_touchesEnded.Clear();
				_touchesCancelled.Clear();
			}
		}

		public void ExpireAllTouches()
		{
			if (_touchesDelivered.Count > 0)
			{
				int i = _listeners.Count - 1;

				while (i >= 0)
				{
					IListener listener = _listeners[i];

					if (listener.Active)
					{
						listener.TouchesExpired(_touchesDelivered);

						if (listener.Exclusive)
							break;
					}

					i--;
				}

				_touchesDelivered.Clear();
			}
		}

		public void AddListener(IListener listener)
		{
			_listenerChanges.OnAdd(listener);
		}

		public void RemoveListener(IListener listener)
		{
			_listenerChanges.OnRemove(listener);
		}

		private void AssociateTouch(Touch touch, TouchPhase phase)
		{
			switch (phase)
			{
				case TouchPhase.Began:
					_touchesBegan.Add(touch);

					break;
					
				case TouchPhase.Moved:
					_touchesMoved.Add(touch);
					
					break;
					
				case TouchPhase.Ended:
					_touchesEnded.Add(touch);

					break;
					
				case TouchPhase.Cancelled:
					_touchesCancelled.Add(touch);

					break;
			}
		}

		private void CreateTouch(UITouch touch)
		{
			if (!_touches.ContainsKey(touch))
			{
				foreach (IWindow window in _windows)
				{
					Point prevPos = new Point(touch.PreviousLocationInView(window.Host));
					
					Point pos = new Point(touch.LocationInView(window.Host));

					Touch t = new Touch(prevPos, pos, (int)touch.TapCount, touch.Timestamp, TouchPhase.Began, null);

					_touches.Add(touch, t);

					AssociateTouch(t, TouchPhase.Began);
				}
			}
		}
		
		private void UpdateTouch(UITouch touch, TouchPhase phase)
		{
			if (_touches.ContainsKey(touch))
			{
				foreach (IWindow window in _windows)
				{
					Touch t = _touches[touch];

					t.PrevPos = new Point(touch.PreviousLocationInView(window.Host));
					t.Pos = new Point(touch.LocationInView(window.Host));
					t.Phase = phase;

					AssociateTouch(t, phase);
				}
			}
		}
		
		public void DisposeTouch(UITouch touch)
		{
			_touches.Remove(touch);
		}

		public void Release()
		{
			_windows = null;
			_touches = null;
			_listeners = null;
			_listenerChanges = null;
			_touchesBegan = null;
			_touchesMoved = null;
			_touchesEnded = null;
			_touchesCancelled = null;
			_touchesDelivered = null;
		}
	}
}