#pragma once
#include "controller_interface.h"
#include "core/types.h"
#include <array>
#include <functional>
#include <mutex>
#include <vector>

class SDLControllerInterface final : public ControllerInterface
{
public:
  SDLControllerInterface();
  ~SDLControllerInterface();

  Backend GetBackend() const override;
  bool Initialize(CommonHostInterface* host_interface) override;
  void Shutdown() override;

  /// Returns the path of the optional game controller database file.
  std::string GetGameControllerDBFileName() const;

  // Removes all bindings. Call before setting new bindings.
  void ClearBindings() override;

  // Binding to events. If a binding for this axis/button already exists, returns false.
  bool BindControllerAxis(int controller_index, int axis_number, AxisSide axis_side, AxisCallback callback) override;
  bool BindControllerButton(int controller_index, int button_number, ButtonCallback callback) override;
  bool BindControllerAxisToButton(int controller_index, int axis_number, bool direction,
                                  ButtonCallback callback) override;
  bool BindControllerHatToButton(int controller_index, int hat_number, std::string_view hat_position,
                                 ButtonCallback callback) override;
  bool BindControllerButtonToAxis(int controller_index, int button_number, AxisCallback callback) override;

  // Changing rumble strength.
  u32 GetControllerRumbleMotorCount(int controller_index) override;
  void SetControllerRumbleStrength(int controller_index, const float* strengths, u32 num_motors) override;

  // Set deadzone that will be applied on axis-to-button mappings
  bool SetControllerDeadzone(int controller_index, float size = 0.25f) override;

  void PollEvents() override;

  bool ProcessSDLEvent(const union SDL_Event* event);

private:
  struct ControllerData
  {
    void* haptic;
    void* game_controller;
    int haptic_left_right_effect;
    int joystick_id;
    int player_id;
    bool use_game_controller_rumble;

    float deadzone = 0.25f;

    // TODO: Turn to vectors to support arbitrary amounts of buttons and axes (for Joysticks)
    // Preferably implement a simple "flat map", an ordered view over a vector
    std::array<std::array<AxisCallback, 3>, MAX_NUM_AXISES> axis_mapping;
    std::array<ButtonCallback, MAX_NUM_BUTTONS> button_mapping;
    std::array<std::array<ButtonCallback, 2>, MAX_NUM_AXISES> axis_button_mapping;
    std::array<AxisCallback, MAX_NUM_BUTTONS> button_axis_mapping;
    std::vector<std::array<ButtonCallback, 4>> hat_button_mapping;

    ALWAYS_INLINE bool IsGameController() const { return (game_controller != nullptr); }
  };

  using ControllerDataVector = std::vector<ControllerData>;

  ControllerDataVector::iterator GetControllerDataForJoystickId(int id);
  ControllerDataVector::iterator GetControllerDataForPlayerId(int id);
  int GetFreePlayerId() const;

  bool OpenGameController(int index);
  bool CloseGameController(int joystick_index, bool notify);
  bool HandleControllerAxisEvent(const struct SDL_ControllerAxisEvent* event);
  bool HandleControllerButtonEvent(const struct SDL_ControllerButtonEvent* event);

  bool OpenJoystick(int index);
  bool HandleJoystickAxisEvent(const struct SDL_JoyAxisEvent* event);
  bool HandleJoystickButtonEvent(const struct SDL_JoyButtonEvent* event);
  bool HandleJoystickHatEvent(const struct SDL_JoyHatEvent* event);

  ControllerDataVector m_controllers;

  std::mutex m_event_intercept_mutex;
  Hook::Callback m_event_intercept_callback;

  bool m_sdl_subsystem_initialized = false;
};
