#if UNITY_INPUT_SYSTEM_ENABLE_UI
using UnityEngine.EventSystems;

namespace UnityEngine.InputSystem.UI
{
    // A pointer is identified by a single unique integer ID. It has an associated position and the ability to press
    // on up to three buttons. It can also scroll.
    //
    // There's a single ExtendedPointerEventData instance allocated for the pointer which is used to retain the pointer's
    // event state. As part of that state is specific to button presses, each button retains a partial copy of press-specific
    // event information.
    //
    // A pointer can operate in 2D (mouse, pen, touch) or 3D (tracked). For 3D, screen-space 2D positions are derived
    // via raycasts based on world-space position and orientation.
    internal struct PointerModel
    {
        public bool changedThisFrame;
        public UIPointerType pointerType => eventData.pointerType;

        public Vector2 screenPosition
        {
            get => m_ScreenPosition;
            set
            {
                if (m_ScreenPosition != value)
                {
                    m_ScreenPosition = value;
                    changedThisFrame = true;
                }
            }
        }

        public Vector3 worldPosition
        {
            get => m_WorldPosition;
            set
            {
                if (m_WorldPosition != value)
                {
                    m_WorldPosition = value;
                    changedThisFrame = true;
                }
            }
        }

        public Quaternion worldOrientation
        {
            get => m_WorldOrientation;
            set
            {
                if (m_WorldOrientation != value)
                {
                    m_WorldOrientation = value;
                    changedThisFrame = true;
                }
            }
        }

        public Vector2 scrollDelta
        {
            get => m_ScrollDelta;
            set
            {
                if (m_ScrollDelta != value)
                {
                    changedThisFrame = true;
                    m_ScrollDelta = value;
                }
            }
        }

        public ButtonState leftButton;
        public ButtonState rightButton;
        public ButtonState middleButton;
        public ExtendedPointerEventData eventData;

        public PointerModel(int pointerId, int touchId, UIPointerType pointerType, InputDevice device, ExtendedPointerEventData eventData)
        {
            this.eventData = eventData;

            eventData.pointerId = pointerId;
            eventData.touchId = touchId;
            eventData.pointerType = pointerType;
            eventData.device = device;

            changedThisFrame = false;

            leftButton = default; leftButton.OnEndFrame();
            rightButton = default; rightButton.OnEndFrame();
            middleButton = default; middleButton.OnEndFrame();

            m_ScreenPosition = default;
            m_ScrollDelta = default;
            m_WorldOrientation = default;
            m_WorldPosition = default;
        }

        public void OnFrameFinished()
        {
            changedThisFrame = false;
            scrollDelta = default;
            leftButton.OnEndFrame();
            rightButton.OnEndFrame();
            middleButton.OnEndFrame();
        }

        private Vector2 m_ScreenPosition;
        private Vector2 m_ScrollDelta;
        private Vector3 m_WorldPosition;
        private Quaternion m_WorldOrientation;

        // State related to pressing and releasing individual bodies. Retains those parts of
        // PointerInputEvent that are specific to presses and releases.
        public struct ButtonState
        {
            private bool m_IsPressed;
            private PointerEventData.FramePressState m_FramePressState;

            public bool isPressed
            {
                get => m_IsPressed;
                set
                {
                    if (m_IsPressed != value)
                    {
                        m_IsPressed = value;

                        if (m_FramePressState == PointerEventData.FramePressState.NotChanged && value)
                            m_FramePressState = PointerEventData.FramePressState.Pressed;
                        else if (m_FramePressState == PointerEventData.FramePressState.NotChanged && !value)
                            m_FramePressState = PointerEventData.FramePressState.Released;
                        else if (m_FramePressState == PointerEventData.FramePressState.Pressed && !value)
                            m_FramePressState = PointerEventData.FramePressState.PressedAndReleased;
                    }
                }
            }

            public bool wasPressedThisFrame => m_FramePressState == PointerEventData.FramePressState.Pressed ||
            m_FramePressState == PointerEventData.FramePressState.PressedAndReleased;
            public bool wasReleasedThisFrame => m_FramePressState == PointerEventData.FramePressState.Released ||
            m_FramePressState == PointerEventData.FramePressState.PressedAndReleased;

            private RaycastResult pressRaycast;
            private GameObject pressObject;
            private GameObject rawPressObject;
            private GameObject lastPressObject;
            private GameObject dragObject;
            private Vector2 pressPosition;
            private float clickTime; // On Time.unscaledTime timeline, NOT input event time.
            private int clickCount;
            private bool dragging;

            public void CopyPressStateTo(PointerEventData eventData)
            {
                eventData.pointerPressRaycast = pressRaycast;
                eventData.pressPosition = pressPosition;
                eventData.clickCount = clickCount;
                eventData.clickTime = clickTime;
                // We can't set lastPress directly. Old input module uses three different event instances, one for each
                // button. We share one instance and just switch press states. Set pointerPress twice to get the lastPress
                // we need.
                //
                // NOTE: This does *NOT* quite work as stated in the docs. pointerPress is nulled out on button release which
                //       will set lastPress as a side-effect. This means that lastPress will only be non-null while no press is
                //       going on and will *NOT* refer to the last pressed object when a new object has been pressed on.
                eventData.pointerPress = lastPressObject;
                eventData.pointerPress = pressObject;
                eventData.rawPointerPress = rawPressObject;
                eventData.pointerDrag = dragObject;
                eventData.dragging = dragging;
            }

            public void CopyPressStateFrom(PointerEventData eventData)
            {
                pressRaycast = eventData.pointerPressRaycast;
                pressObject = eventData.pointerPress;
                rawPressObject = eventData.rawPointerPress;
                lastPressObject = eventData.lastPress;
                pressPosition = eventData.pressPosition;
                clickTime = eventData.clickTime;
                clickCount = eventData.clickCount;
                dragObject = eventData.pointerDrag;
                dragging = eventData.dragging;
            }

            public void OnEndFrame()
            {
                m_FramePressState = PointerEventData.FramePressState.NotChanged;
            }
        }
    }
}
#endif
