using System;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

namespace Cryville.Crtr.Browsing {
	public class PVPNumber : PropertyValuePanel {
		double m_value;
		public override object Value {
			get {
				float s_value = GetDisplayValue();
				return IntegerMode ? (object)(int)s_value : (object)s_value;
			}
			set {
				if (value is double) m_value = (double)value;
				else m_value = IntegerMode ? (double)(int)value : (double)(float)value;
				float s_value = GetDisplayValue();
				m_text.text = s_value.ToString();
				if (Range != null && MaxStep == 0) {
					SetRatio((float)(m_value - Range.Value.x) / (Range.Value.y - Range.Value.x));
				}
			}
		}

		float GetDisplayValue() {
			double s_value = m_value;
			if (Precision > 0)
				s_value = Math.Round(s_value / Precision) * Precision;
			if (IntegerMode)
				s_value = Math.Round(s_value);
			return (float)s_value;
		}

		public bool IntegerMode { get; set; }
		public bool LogarithmicMode { get; set; }
		public float MaxStep { get; set; }
		public double Precision { get; set; }
		public Vector2? Range { get; set; }

		[SerializeField]
		EventTrigger m_ctn;
		[SerializeField]
		RectTransform m_handleArea;
		[SerializeField]
		RectTransform m_handle;
		[SerializeField]
		Text m_text;
#pragma warning disable IDE0051
		void Start() {
			var ev = new EventTrigger.Entry { eventID = EventTriggerType.InitializePotentialDrag };
			ev.callback.AddListener(e => OnInitializePotentialDrag((PointerEventData)e));
			m_ctn.triggers.Add(ev);
			ev = new EventTrigger.Entry { eventID = EventTriggerType.Drag };
			ev.callback.AddListener(e => OnDrag((PointerEventData)e));
			m_ctn.triggers.Add(ev);
			ev = new EventTrigger.Entry { eventID = EventTriggerType.EndDrag };
			ev.callback.AddListener(e => OnEndDrag((PointerEventData)e));
			m_ctn.triggers.Add(ev);
			ev = new EventTrigger.Entry { eventID = EventTriggerType.PointerClick };
			ev.callback.AddListener(e => OnPointerClick((PointerEventData)e));
			m_ctn.triggers.Add(ev);

			if (MaxStep != 0) SetRatio(0.5f);
		}

		void Update() {
			if (use && MaxStep != 0) {
				SetRatio(GetRatioFromPos(pp));
				SetValueFromPos(pp);
			}
		}

		void OnRectTransformDimensionsChange() {
			m_handleArea.sizeDelta = new Vector2(m_handle.rect.height - m_handle.rect.width, 0);
		}
#pragma warning restore IDE0051

		Vector2 pp;
		bool use, nouse;
		public void OnInitializePotentialDrag(PointerEventData eventData) {
			eventData.useDragThreshold = false;
			pp = eventData.position;
		}

		public void OnDrag(PointerEventData eventData) {
			if (nouse) return;
			if (!use) {
				var delta = eventData.position - pp;
				float dx = Mathf.Abs(delta.x), dy = Mathf.Abs(delta.y);
				if (dx > dy) use = true;
				else if (dx < dy) nouse = true;
			}
			if (use) {
				pp = eventData.position;
				if (MaxStep == 0) SetValueFromPos(eventData.position);
				eventData.Use();
			}
		}

		public void OnEndDrag(PointerEventData eventData) {
			if (!nouse) {
				SetValueFromPos(eventData.position);
				Callback(Value);
				if (MaxStep != 0) SetRatio(0.5f);
				eventData.Use();
				use = false;
			}
			nouse = false;
		}

		public void OnPointerClick(PointerEventData eventData) {
			SetValueFromPos(eventData.position);
			Callback(Value);
			eventData.Use();
		}

		float GetRatioFromPos(Vector2 pos) {
			Vector2 lp;
			if (RectTransformUtility.ScreenPointToLocalPointInRectangle(m_handleArea, pos, null, out lp)) {
				lp -= m_handleArea.rect.position;
				return Mathf.Clamp01(lp.x / m_handleArea.rect.width);
			}
			return float.NegativeInfinity;
		}

		void SetValueFromPos(Vector2 pos) {
			double ratio = GetRatioFromPos(pos);
			double result;
			if (MaxStep == 0) {
				if (LogarithmicMode) throw new NotImplementedException();
				else result = (1 - ratio) * Range.Value.x + ratio * Range.Value.y;
			}
			else {
				double delta = (ratio - 0.5f) * 2 * MaxStep * Time.deltaTime;
				if (LogarithmicMode) result = Math.Pow(Math.E, Math.Log(m_value) + delta);
				else result = m_value + delta;
			}
			if (Range != null) {
				if (result < Range.Value.x) result = Range.Value.x;
				else if (result > Range.Value.y) result = Range.Value.y;
			}
			Value = result;
		}

		void SetRatio(float ratio) {
			m_handle.anchorMin = new Vector2(ratio, m_handle.anchorMin.y);
			m_handle.anchorMax = new Vector2(ratio, m_handle.anchorMax.y);
		}
	}
}
