// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.

#pragma once

#include <functional>
#include <mutex>
#include <unordered_map>

#include "common/input.h"
#include "common/param_package.h"
#include "common/point.h"
#include "common/quaternion.h"
#include "common/settings.h"
#include "common/vector_math.h"
#include "core/hid/hid_types.h"
#include "core/hid/motion_input.h"

namespace Core::HID {

struct ControllerMotionInfo {
    Input::MotionStatus raw_status;
    MotionInput emulated{};
};

using ButtonDevices =
    std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeButton::NumButtons>;
using StickDevices =
    std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeAnalog::NumAnalogs>;
using ControllerMotionDevices =
    std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeMotion::NumMotions>;
using TriggerDevices =
    std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeTrigger::NumTriggers>;
using BatteryDevices = std::array<std::unique_ptr<Input::InputDevice>, 2>;

using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>;
using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>;
using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>;
using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>;
using BatteryParams = std::array<Common::ParamPackage, 2>;

using ButtonValues = std::array<Input::ButtonStatus, Settings::NativeButton::NumButtons>;
using SticksValues = std::array<Input::StickStatus, Settings::NativeAnalog::NumAnalogs>;
using TriggerValues = std::array<Input::TriggerStatus, Settings::NativeTrigger::NumTriggers>;
using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::NativeMotion::NumMotions>;
using ColorValues = std::array<Input::BodyColorStatus, 3>;
using BatteryValues = std::array<Input::BatteryStatus, 3>;
using VibrationValues = std::array<Input::VibrationStatus, 2>;

struct AnalogSticks {
    AnalogStickState left;
    AnalogStickState right;
};

struct ControllerColors {
    NpadControllerColor fullkey;
    NpadControllerColor left;
    NpadControllerColor right;
};

struct BatteryLevelState {
    NpadPowerInfo dual;
    NpadPowerInfo left;
    NpadPowerInfo right;
};

struct ControllerMotion {
    bool is_at_rest;
    Common::Vec3f accel{};
    Common::Vec3f gyro{};
    Common::Vec3f rotation{};
    std::array<Common::Vec3f, 3> orientation{};
};

using MotionState = std::array<ControllerMotion, 2>;

struct ControllerStatus {
    // Data from input_common
    ButtonValues button_values{};
    SticksValues stick_values{};
    ControllerMotionValues motion_values{};
    TriggerValues trigger_values{};
    ColorValues color_values{};
    BatteryValues battery_values{};
    VibrationValues vibration_values{};

    // Data for Nintendo devices
    NpadButtonState npad_button_state{};
    DebugPadButton debug_pad_button_state{};
    AnalogSticks analog_stick_state{};
    MotionState motion_state{};
    NpadGcTriggerState gc_trigger_state{};
    ControllerColors colors_state{};
    BatteryLevelState battery_state{};
};
enum class ControllerTriggerType {
    Button,
    Stick,
    Trigger,
    Motion,
    Color,
    Battery,
    Vibration,
    Connected,
    Disconnected,
    Type,
    All,
};

struct ControllerUpdateCallback {
    std::function<void(ControllerTriggerType)> on_change;
};

class EmulatedController {
public:
    /**
     * TODO: Write description
     *
     * @param npad_id_type
     */
    explicit EmulatedController(NpadIdType npad_id_type_);
    ~EmulatedController();

    YUZU_NON_COPYABLE(EmulatedController);
    YUZU_NON_MOVEABLE(EmulatedController);

    static NpadType MapSettingsTypeToNPad(Settings::ControllerType type);
    static Settings::ControllerType MapNPadToSettingsType(NpadType type);

    /// Gets the NpadIdType for this controller.
    NpadIdType GetNpadIdType() const;

    /// Sets the NpadType for this controller.
    void SetNpadType(NpadType npad_type_);

    /// Gets the NpadType for this controller.
    NpadType GetNpadType() const;

    void Connect();
    void Disconnect();

    bool IsConnected() const;
    bool IsVibrationEnabled() const;

    void ReloadFromSettings();
    void ReloadInput();
    void UnloadInput();

    void EnableConfiguration();
    void DisableConfiguration();
    bool IsConfiguring() const;
    void SaveCurrentConfig();
    void RestoreConfig();

    std::vector<Common::ParamPackage> GetMappedDevices() const;

    Common::ParamPackage GetButtonParam(std::size_t index) const;
    Common::ParamPackage GetStickParam(std::size_t index) const;
    Common::ParamPackage GetMotionParam(std::size_t index) const;

    void SetButtonParam(std::size_t index, Common::ParamPackage param);
    void SetStickParam(std::size_t index, Common::ParamPackage param);
    void SetMotionParam(std::size_t index, Common::ParamPackage param);

    ButtonValues GetButtonsValues() const;
    SticksValues GetSticksValues() const;
    TriggerValues GetTriggersValues() const;
    ControllerMotionValues GetMotionValues() const;
    ColorValues GetColorsValues() const;
    BatteryValues GetBatteryValues() const;

    NpadButtonState GetNpadButtons() const;
    DebugPadButton GetDebugPadButtons() const;
    AnalogSticks GetSticks() const;
    NpadGcTriggerState GetTriggers() const;
    MotionState GetMotions() const;
    ControllerColors GetColors() const;
    BatteryLevelState GetBattery() const;

    bool SetVibration(std::size_t device_index, VibrationValue vibration);
    int TestVibration(std::size_t device_index);

    int SetCallback(ControllerUpdateCallback update_callback);
    void DeleteCallback(int key);

private:
    /**
     * Sets the status of a button. Applies toggle properties to the output.
     *
     * @param A CallbackStatus and a button index number
     */
    void SetButton(Input::CallbackStatus callback, std::size_t index);
    void SetStick(Input::CallbackStatus callback, std::size_t index);
    void SetTrigger(Input::CallbackStatus callback, std::size_t index);
    void SetMotion(Input::CallbackStatus callback, std::size_t index);
    void SetBattery(Input::CallbackStatus callback, std::size_t index);

    /**
     * Triggers a callback that something has changed
     *
     * @param Input type of the trigger
     */
    void TriggerOnChange(ControllerTriggerType type);

    NpadIdType npad_id_type;
    NpadType npad_type{NpadType::None};
    bool is_connected{false};
    bool is_configuring{false};
    bool is_vibration_enabled{true};
    f32 motion_sensitivity{0.01f};

    ButtonParams button_params;
    StickParams stick_params;
    ControllerMotionParams motion_params;
    TriggerParams trigger_params;
    BatteryParams battery_params;

    ButtonDevices button_devices;
    StickDevices stick_devices;
    ControllerMotionDevices motion_devices;
    TriggerDevices trigger_devices;
    BatteryDevices battery_devices;
    // VibrationDevices vibration_devices;

    mutable std::mutex mutex;
    std::unordered_map<int, ControllerUpdateCallback> callback_list;
    int last_callback_key = 0;
    ControllerStatus controller;
};

} // namespace Core::HID
