// UI Draggable Item|UI|80030
namespace VRTK
{
    using UnityEngine;
    using UnityEngine.EventSystems;

    /// <summary>
    /// Event Payload
    /// </summary>
    /// <param name="target">The target the item is dragged onto.</param>
    public struct UIDraggableItemEventArgs
    {
        public GameObject target;
    }

    /// <summary>
    /// Event Payload
    /// </summary>
    /// <param name="sender">this object</param>
    /// <param name="e"><see cref="UIDraggableItemEventArgs"/></param>
    public delegate void UIDraggableItemEventHandler(object sender, UIDraggableItemEventArgs e);

    /// <summary>
    /// Denotes a Unity UI Element as being draggable on the UI Canvas.
    /// </summary>
    /// <remarks>
    ///   > If a UI Draggable item is set to `Restrict To Drop Zone = true` then the UI Draggable item must be a child of an element that has the VRTK_UIDropZone script applied to it to ensure it starts in a valid drop zone.
    ///
    /// **Script Usage:**
    ///  * Place the `VRTK_UIDraggableItem` script on the Unity UI element that is to be dragged.
    /// </remarks>
    /// <example>
    /// `VRTK/Examples/034_Controls_InteractingWithUnityUI` demonstrates a collection of UI elements that are draggable
    /// </example>
    [RequireComponent(typeof(CanvasGroup))]
    [AddComponentMenu("VRTK/Scripts/UI/VRTK_UIDraggableItem")]
    public class VRTK_UIDraggableItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
    {
        [Tooltip("If checked then the UI element can only be dropped in valid a VRTK_UIDropZone object and must start as a child of a VRTK_UIDropZone object. If unchecked then the UI element can be dropped anywhere on the canvas.")]
        public bool restrictToDropZone = false;
        [Tooltip("If checked then the UI element can only be dropped on the original parent canvas. If unchecked the UI element can be dropped on any valid VRTK_UICanvas.")]
        public bool restrictToOriginalCanvas = false;
        [Tooltip("The offset to bring the UI element forward when it is being dragged.")]
        public float forwardOffset = 0.1f;

        /// <summary>
        /// The current valid drop zone the dragged element is hovering over.
        /// </summary>
        [HideInInspector]
        public GameObject validDropZone;

        /// <summary>
        /// Emitted when the draggable item is successfully dropped.
        /// </summary>
        public event UIDraggableItemEventHandler DraggableItemDropped;
        /// <summary>
        /// Emitted when the draggable item is reset.
        /// </summary>
        public event UIDraggableItemEventHandler DraggableItemReset;

        protected RectTransform dragTransform;
        protected Vector3 startPosition;
        protected Quaternion startRotation;
        protected GameObject startDropZone;
        protected Transform startParent;
        protected Canvas startCanvas;
        protected CanvasGroup canvasGroup;

        public virtual void OnDraggableItemDropped(UIDraggableItemEventArgs e)
        {
            if (DraggableItemDropped != null)
            {
                DraggableItemDropped(this, e);
            }
        }

        public virtual void OnDraggableItemReset(UIDraggableItemEventArgs e)
        {
            if (DraggableItemReset != null)
            {
                DraggableItemReset(this, e);
            }
        }

        public virtual void OnBeginDrag(PointerEventData eventData)
        {
            startPosition = transform.position;
            startRotation = transform.rotation;
            startParent = transform.parent;
            startCanvas = GetComponentInParent<Canvas>();
            canvasGroup.blocksRaycasts = false;

            if (restrictToDropZone)
            {
                startDropZone = GetComponentInParent<VRTK_UIDropZone>().gameObject;
                validDropZone = startDropZone;
            }

            SetDragPosition(eventData);
            VRTK_UIPointer pointer = GetPointer(eventData);
            if (pointer != null)
            {
                pointer.OnUIPointerElementDragStart(pointer.SetUIPointerEvent(pointer.pointerEventData.pointerPressRaycast, gameObject));
            }
        }

        public virtual void OnDrag(PointerEventData eventData)
        {
            SetDragPosition(eventData);
        }

        public virtual void OnEndDrag(PointerEventData eventData)
        {
            canvasGroup.blocksRaycasts = true;
            dragTransform = null;
            transform.position += (transform.forward * forwardOffset);
            bool validDragEnd = true;
            if (restrictToDropZone)
            {
                if (validDropZone != null && validDropZone != startDropZone)
                {
                    transform.SetParent(validDropZone.transform);
                }
                else
                {
                    ResetElement();
                    validDragEnd = false;
                }
            }

            Canvas destinationCanvas = (eventData.pointerEnter != null ? eventData.pointerEnter.GetComponentInParent<Canvas>() : null);
            if (restrictToOriginalCanvas)
            {
                if (destinationCanvas != null && destinationCanvas != startCanvas)
                {
                    ResetElement();
                    validDragEnd = false;
                }
            }

            if (destinationCanvas == null)
            {
                //We've been dropped off of a canvas
                ResetElement();
                validDragEnd = false;
            }

            if (validDragEnd)
            {
                VRTK_UIPointer pointer = GetPointer(eventData);
                if (pointer != null)
                {
                    pointer.OnUIPointerElementDragEnd(pointer.SetUIPointerEvent(pointer.pointerEventData.pointerPressRaycast, gameObject));
                }
                OnDraggableItemDropped(SetEventPayload(validDropZone));
            }

            validDropZone = null;
            startParent = null;
            startCanvas = null;
        }

        protected virtual void OnEnable()
        {
            canvasGroup = GetComponent<CanvasGroup>();
            if (restrictToDropZone && GetComponentInParent<VRTK_UIDropZone>() == null)
            {
                enabled = false;
                VRTK_Logger.Error(VRTK_Logger.GetCommonMessage(VRTK_Logger.CommonMessageKeys.REQUIRED_COMPONENT_MISSING_FROM_GAMEOBJECT, "VRTK_UIDraggableItem", "VRTK_UIDropZone", "the parent", " if `freeDrop = false`"));
            }
        }

        protected virtual VRTK_UIPointer GetPointer(PointerEventData eventData)
        {
            GameObject controller = VRTK_DeviceFinder.GetControllerByIndex((uint)eventData.pointerId, false);
            return (controller != null ? controller.GetComponent<VRTK_UIPointer>() : null);
        }

        protected virtual void SetDragPosition(PointerEventData eventData)
        {
            if (eventData.pointerEnter != null && eventData.pointerEnter.transform as RectTransform != null)
            {
                dragTransform = eventData.pointerEnter.transform as RectTransform;
            }

            Vector3 pointerPosition;
            if (dragTransform != null && RectTransformUtility.ScreenPointToWorldPointInRectangle(dragTransform, eventData.position, eventData.pressEventCamera, out pointerPosition))
            {
                transform.position = pointerPosition - (transform.forward * forwardOffset);
                transform.rotation = dragTransform.rotation;
            }
        }

        protected virtual void ResetElement()
        {
            transform.position = startPosition;
            transform.rotation = startRotation;
            transform.SetParent(startParent);
            OnDraggableItemReset(SetEventPayload(startParent.gameObject));
        }

        protected virtual UIDraggableItemEventArgs SetEventPayload(GameObject target)
        {
            UIDraggableItemEventArgs e;
            e.target = target;
            return e;
        }
    }
}