#include <Core/Common/EnumUtils.h>
#include <Core/HAL/Display/Window.h>
#include <Core/HAL/Event/Mouse_Private.h>
#include <Core/HAL/Event/SystemEvent.h>
#include <Core/HAL/Mouse/Mouse.h>

namespace Juliet
{
    namespace
    {
        Mouse MouseState;

        void ConstraintMousePositionToWindow(Mouse& mouseState, Window* window, float& x, float& y)
        {
            float x_min = 0.f, x_max = (float)(window->Width - 1);
            float y_min = 0.f, y_max = (float)(window->Height - 1);

            if (x >= (x_max + 1))
            {
                x = std::max(x_max, mouseState.X_Previous);
            }
            x = std::max(x, x_min);

            if (y >= (y_max + 1))
            {
                y = std::max(y_max, mouseState.Y_Previous);
            }
            y = std::max(y, y_min);
        }

        void SendMouseMotion_Internal(uint64 timestamp, Window* window, MouseID mouseID, float x, float y)
        {
            auto& mouseState    = GetMouseState();
            float xDisplacement = 0.0f;
            float yDisplacement = 0.0f;

            ConstraintMousePositionToWindow(mouseState, window, x, y);

            if (mouseState.HasPosition)
            {
                xDisplacement = x - mouseState.X_Previous;
                yDisplacement = y - mouseState.Y_Previous;
            }

            if (mouseState.HasPosition && xDisplacement == 0.0f && yDisplacement == 0.0f)
            {
                // Skip it because state didnt change
                return;
            }

            mouseState.X           = x;
            mouseState.Y           = y;
            mouseState.HasPosition = true;

            mouseState.X_Previous = x;
            mouseState.Y_Previous = y;

            SystemEvent evt;
            evt.Type                                 = EventType::Mouse_Move;
            evt.Timestamp                            = timestamp;
            evt.Data.MouseMovement.WindowID          = window->ID;
            evt.Data.MouseMovement.AssociatedMouseID = mouseID;
            evt.Data.MouseMovement.X                 = x;
            evt.Data.MouseMovement.Y                 = y;
            evt.Data.MouseMovement.X_Displacement    = xDisplacement;
            evt.Data.MouseMovement.Y_Displacement    = yDisplacement;
            evt.Data.MouseMovement.ButtonState       = mouseState.ButtonState;
            AddEvent(evt);
        }

    } // namespace

    constexpr MouseID kGlobalMouseID = 0;

    Mouse& GetMouseState()
    {
        return MouseState;
    }

    void SendMouseMotion(uint64 timestamp, NonNullPtr<Window> window, MouseID ID, float x, float y)
    {
        // TODO : Update Mouse focus and send Mouse Enter / Mouse Leave event

        SendMouseMotion_Internal(timestamp, window, ID, x, y);
    }

    void SendMouseButton(uint64 timestamp, NonNullPtr<Window> window, MouseID mouseID, MouseButton button, bool pressed)
    {
        Mouse&      mouseState = GetMouseState();
        MouseButton flags      = mouseState.ButtonState;

        auto type = EventType::None;
        if (pressed)
        {
            type = EventType::Mouse_ButtonPressed;
            flags |= button;
        }
        else
        {
            type  = EventType::Mouse_ButtonReleased;
            flags = flags & ~button;
        }

        if (flags == mouseState.ButtonState)
        {
            return;
        }

        mouseState.ButtonState = flags;

        // TODO : Send Event!
        SystemEvent evt;
        evt.Timestamp                          = timestamp;
        evt.Type                               = type;
        evt.Data.MouseButton.AssociatedMouseID = mouseID;
        evt.Data.MouseButton.WindowID          = window->ID;
        evt.Data.MouseButton.ButtonState       = button;
        evt.Data.MouseButton.X                 = mouseState.X;
        evt.Data.MouseButton.Y                 = mouseState.Y;
        evt.Data.MouseButton.IsPressed         = pressed;
        AddEvent(evt);
    }

    bool IsMouseButtonDown(MouseButton button)
    {
        auto& mouseState = GetMouseState();
        return (mouseState.ButtonState & button) != MouseButton::None;
    }

    MousePosition GetMousePosition()
    {
        auto& mouseState = GetMouseState();
        return { .X = mouseState.X, .Y = mouseState.Y };
    }

    MouseButton GetMouseButtonState()
    {
        const auto& mouseState = GetMouseState();
        return mouseState.ButtonState;
    }

} // namespace Juliet
