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

namespace SimpleInputNamespace
{
	public class Joystick : MonoBehaviour, ISimpleInputDraggable
	{
		public enum MovementAxes { XandY, X, Y };

		public SimpleInput.AxisInput xAxis = new SimpleInput.AxisInput( "Horizontal" );
		public SimpleInput.AxisInput yAxis = new SimpleInput.AxisInput( "Vertical" );

		private RectTransform joystickTR;
		private Graphic background;

		public MovementAxes movementAxes = MovementAxes.XandY;
		public float valueMultiplier = 1f;

		[SerializeField]
		private Image thumb;
		private RectTransform thumbTR;

		[SerializeField]
		private float movementAreaRadius = 75f;

		[Tooltip( "Radius of the deadzone at the center of the joystick that will yield no input" )]
		[SerializeField]
		private float deadzoneRadius;

		[SerializeField]
		private bool isDynamicJoystick = false;

		[SerializeField]
		private RectTransform dynamicJoystickMovementArea;

		[SerializeField]
		private bool canFollowPointer = false;

		private bool joystickHeld = false;
		private Vector2 pointerInitialPos;

		private float _1OverMovementAreaRadius;
		private float movementAreaRadiusSqr;
		private float deadzoneRadiusSqr;

		private Vector2 joystickInitialPos;

		private float opacity = 1f;

		private Vector2 m_value = Vector2.zero;
		public Vector2 Value { get { return m_value; } }

		private void Awake()
		{
			joystickTR = (RectTransform) transform;
			thumbTR = thumb.rectTransform;
			background = GetComponent<Graphic>();

			if( isDynamicJoystick )
			{
				opacity = 0f;
				thumb.raycastTarget = false;
				if( background )
					background.raycastTarget = false;

				OnUpdate();
			}
			else
			{
				thumb.raycastTarget = true;
				if( background )
					background.raycastTarget = true;
			}

			_1OverMovementAreaRadius = 1f / movementAreaRadius;
			movementAreaRadiusSqr = movementAreaRadius * movementAreaRadius;
			deadzoneRadiusSqr = deadzoneRadius * deadzoneRadius;

			joystickInitialPos = joystickTR.anchoredPosition;
			thumbTR.localPosition = Vector3.zero;
		}

		private void Start()
		{
			SimpleInputDragListener eventReceiver;
			if( !isDynamicJoystick )
			{
				if( background )
					eventReceiver = background.gameObject.AddComponent<SimpleInputDragListener>();
				else
					eventReceiver = thumbTR.gameObject.AddComponent<SimpleInputDragListener>();
			}
			else
			{
				if( !dynamicJoystickMovementArea )
				{
					dynamicJoystickMovementArea = new GameObject( "Dynamic Joystick Movement Area", typeof( RectTransform ) ).GetComponent<RectTransform>();
					dynamicJoystickMovementArea.SetParent( thumb.canvas.transform, false );
					dynamicJoystickMovementArea.SetAsFirstSibling();
					dynamicJoystickMovementArea.anchorMin = Vector2.zero;
					dynamicJoystickMovementArea.anchorMax = Vector2.one;
					dynamicJoystickMovementArea.sizeDelta = Vector2.zero;
					dynamicJoystickMovementArea.anchoredPosition = Vector2.zero;
				}

				eventReceiver = dynamicJoystickMovementArea.gameObject.AddComponent<SimpleInputDragListener>();
			}

			eventReceiver.Listener = this;
		}

		private void OnEnable()
		{
			xAxis.StartTracking();
			yAxis.StartTracking();

			SimpleInput.OnUpdate += OnUpdate;
		}

		private void OnDisable()
		{
			OnPointerUp( null );

			xAxis.StopTracking();
			yAxis.StopTracking();

			SimpleInput.OnUpdate -= OnUpdate;
		}

#if UNITY_EDITOR
		private void OnValidate()
		{
			_1OverMovementAreaRadius = 1f / movementAreaRadius;
			movementAreaRadiusSqr = movementAreaRadius * movementAreaRadius;
			deadzoneRadiusSqr = deadzoneRadius * deadzoneRadius;
		}
#endif

		public void OnPointerDown( PointerEventData eventData )
		{
			joystickHeld = true;

			if( isDynamicJoystick )
			{
				pointerInitialPos = Vector2.zero;

				Vector3 joystickPos;
				RectTransformUtility.ScreenPointToWorldPointInRectangle( dynamicJoystickMovementArea, eventData.position, eventData.pressEventCamera, out joystickPos );
				joystickTR.position = joystickPos;
			}
			else
				RectTransformUtility.ScreenPointToLocalPointInRectangle( joystickTR, eventData.position, eventData.pressEventCamera, out pointerInitialPos );
		}

		public void OnDrag( PointerEventData eventData )
		{
			Vector2 pointerPos;
			RectTransformUtility.ScreenPointToLocalPointInRectangle( joystickTR, eventData.position, eventData.pressEventCamera, out pointerPos );

			Vector2 direction = pointerPos - pointerInitialPos;
			if( movementAxes == MovementAxes.X )
				direction.y = 0f;
			else if( movementAxes == MovementAxes.Y )
				direction.x = 0f;

			if( direction.sqrMagnitude <= deadzoneRadiusSqr )
				m_value.Set( 0f, 0f );
			else
			{
				if( direction.sqrMagnitude > movementAreaRadiusSqr )
				{
					Vector2 directionNormalized = direction.normalized * movementAreaRadius;
					if( canFollowPointer )
						joystickTR.localPosition += (Vector3) ( direction - directionNormalized );

					direction = directionNormalized;
				}

				m_value = direction * _1OverMovementAreaRadius * valueMultiplier;
			}

			thumbTR.localPosition = direction;

			xAxis.value = m_value.x;
			yAxis.value = m_value.y;
		}

		public void OnPointerUp( PointerEventData eventData )
		{
			joystickHeld = false;
			m_value = Vector2.zero;

			thumbTR.localPosition = Vector3.zero;
			if( !isDynamicJoystick && canFollowPointer )
				joystickTR.anchoredPosition = joystickInitialPos;

			xAxis.value = 0f;
			yAxis.value = 0f;
		}

		private void OnUpdate()
		{
			if( !isDynamicJoystick )
				return;

			if( joystickHeld )
				opacity = Mathf.Min( 1f, opacity + Time.unscaledDeltaTime * 4f );
			else
				opacity = Mathf.Max( 0f, opacity - Time.unscaledDeltaTime * 4f );

			Color c = thumb.color;
			c.a = opacity;
			thumb.color = c;

			if( background )
			{
				c = background.color;
				c.a = opacity;
				background.color = c;
			}
		}
	}
}