/*
Crafter®.Graphics
Copyright (C) 2026 Catcrafts®
Catcrafts.net

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3.0 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
*/
export module Crafter.Graphics:Gamepad;
import std;
import Crafter.Event;

// Raw gamepad device API. Platform-split implementation:
//   - Linux: libudev (enumerate + hot-plug) + libevdev (read events,
//     calibrate axes). Open every /dev/input/event* node the kernel
//     classifies as a gamepad; ignore the rest.
//   - Windows: Windows.Gaming.Input via the C ABI (COBJMACROS pattern,
//     same as SDL). Added/Removed events fire on a thread pool and are
//     marshaled into a queue drained from Tick() on the main thread.
//
// Button names follow the standardized kernel BTN_SOUTH / BTN_EAST /
// BTN_WEST / BTN_NORTH convention — physical position, not label. An
// Xbox "A" and a PlayStation "Cross" both arrive as `South`.
//
// Stick axes are -1..1 (post-deadzone calibration, raw float). Trigger
// axes are 0..1. The `LeftTrigger` / `RightTrigger` entries in `Button`
// and `Axis` are both populated — pick whichever fits your use case
// (digital threshold vs. analog reading).

export namespace Crafter::Gamepad {
    enum class Button : std::uint8_t {
        South, East, West, North,           // BTN_SOUTH/EAST/WEST/NORTH
        Select, Start, Home,
        LeftStickClick, RightStickClick,
        LeftBumper, RightBumper,
        DPadUp, DPadDown, DPadLeft, DPadRight,
        LeftTrigger, RightTrigger,          // digital threshold; analog also on Axis
        Max
    };

    enum class Axis : std::uint8_t {
        LeftStickX, LeftStickY,
        RightStickX, RightStickY,
        LeftTrigger, RightTrigger,
        Max
    };

    enum class Stick : std::uint8_t { Left, Right };

    struct Device {
        std::uint32_t id;                                       // stable across the device's lifetime
        std::string   name;
        bool          buttons[(std::size_t)Button::Max] = {};
        float         axes[(std::size_t)Axis::Max]      = {};   // sticks: -1..1, triggers: 0..1
        Event<Button> onButtonDown;
        Event<Button> onButtonUp;
        Event<Axis>   onAxisChanged;
    };

    // Process-level state — one input subsystem per app. Indices into
    // `connected` are NOT stable across hot-plug; use `Device::id` when
    // serializing bindings. The list is unsorted; iterate to find a
    // specific id.
    inline std::vector<std::unique_ptr<Device>> connected;
    inline Event<Device*> onConnected;
    inline Event<Device*> onDisconnected;

    // Drains pending events from the OS, fires the relevant onButton* /
    // onAxisChanged / onConnected / onDisconnected events synchronously,
    // and updates the polled `buttons[]` / `axes[]` state on each Device.
    // Must be called from the main thread once per frame — Window's
    // event loop does this automatically. Safe to call when no gamepads
    // are connected (early-out path).
    void Tick();

    // Optional rumble. `low` drives the heavy/low-frequency motor,
    // `high` drives the light/high-frequency motor (Xbox naming). Both
    // are clamped to 0..1. `duration` clamps to the backend's max; pass
    // 0ms to stop any active rumble. No-op if the device doesn't
    // advertise force-feedback.
    void Rumble(Device& dev, float low, float high,
                std::chrono::milliseconds duration);

    // Find a connected device by its stable id. Returns nullptr if the
    // device isn't currently connected — typical use after deserializing
    // a saved binding.
    Device* FindById(std::uint32_t id);
}
