using Cryville.Common.Buffers;
using UnityEngine;
using UnityEngine.EventSystems;

namespace Cryville.Unity.EventSystems {
	[AddComponentMenu("Event/GC-less Standalone Input Module")]
	public class StandaloneInputModule : UnityEngine.EventSystems.StandaloneInputModule {
		private bool ShouldIgnoreEventsOnNoFocus() {
#if UNITY_EDITOR
			return !UnityEditor.EditorApplication.isRemoteConnected;
#else
			return true;
#endif
		}
		public override void Process() {
			if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus()) {
				return;
			}
			bool usedEvent = SendUpdateEventToSelectedObject();
			if (!ProcessTouchEvents() && input.mousePresent) {
				ProcessMouseEvent();
			}
			if (eventSystem.sendNavigationEvents) {
				if (!usedEvent) {
					usedEvent |= SendMoveEventToSelectedObject();
				}
				if (!usedEvent) {
					SendSubmitEventToSelectedObject();
				}
			}
		}
		private bool ProcessTouchEvents() {
			for (int i = 0; i < input.touchCount; i++) {
				Touch touch = input.GetTouch(i);
				if (touch.type != TouchType.Indirect) {
					bool pressed, released;
					PointerEventData pointer = GetTouchPointerEventData(touch, out pressed, out released);
					ProcessTouchPress(pointer, pressed, released);
					if (!released) {
						ProcessMove(pointer);
						ProcessDrag(pointer);
					}
					else {
						RemovePointerData(pointer);
					}
				}
			}
			return input.touchCount > 0;
		}
		protected new PointerEventData GetTouchPointerEventData(Touch input, out bool pressed, out bool released) {
			PointerEventData pointerData;
			bool created = GetPointerData(input.fingerId, out pointerData, create: true);
			pointerData.Reset();
			pressed = created || input.phase == TouchPhase.Began;
			released = input.phase == TouchPhase.Canceled || input.phase == TouchPhase.Ended;
			if (created) {
				pointerData.position = input.position;
			}
			if (pressed) {
				pointerData.delta = Vector2.zero;
			}
			else {
				pointerData.delta = input.position - pointerData.position;
			}
			pointerData.position = input.position;
			pointerData.button = PointerEventData.InputButton.Left;
			if (input.phase == TouchPhase.Canceled) {
				pointerData.pointerCurrentRaycast = default(RaycastResult);
			}
			else {
				eventSystem.RaycastAll(pointerData, m_RaycastResultCache);
				pointerData.pointerCurrentRaycast = FindFirstRaycast(m_RaycastResultCache);
				m_RaycastResultCache.Clear();
			}
			pointerData.pressure = input.pressure;
			pointerData.altitudeAngle = input.altitudeAngle;
			pointerData.azimuthAngle = input.azimuthAngle;
			pointerData.radius = Vector2.one * input.radius;
			pointerData.radiusVariance = Vector2.one * input.radiusVariance;
			return pointerData;
		}
		class PointerEventDataPool : ObjectPool<PointerEventData> {
			readonly EventSystem _eventSystem;
			public PointerEventDataPool(EventSystem eventSystem, int capacity) : base(capacity) {
				_eventSystem = eventSystem;
			}
			protected override PointerEventData Construct() {
				return new PointerEventData(_eventSystem);
			}
		}
		PointerEventDataPool _pool;
		protected override void OnEnable() {
			base.OnEnable();
			_pool = new PointerEventDataPool(eventSystem, 64);
		}
		protected new bool GetPointerData(int id, out PointerEventData data, bool create) {
			if (!m_PointerData.TryGetValue(id, out data) && create) {
				data = _pool.Rent();
				data.pointerId = id;
				m_PointerData.Add(id, data);
				return true;
			}
			return false;
		}
		protected new void RemovePointerData(PointerEventData data) {
			m_PointerData.Remove(data.pointerId);
			_pool.Return(data);
		}
	}
}
