#include "input_state.h"

#include <SDL2/SDL.h>
#include <algorithm>

#ifdef __SWITCH__
#include <switch.h>
#endif

namespace ns_game {

namespace {

constexpr Sint16 kControllerAxisDeadZone = 10000;

bool isDown(const Uint8* keys, SDL_Scancode key) {
  return keys && keys[key] != 0;
}

bool controllerButtonDown(SDL_GameController* controller,
                          SDL_GameControllerButton button) {
  return controller &&
         SDL_GameControllerGetButton(controller, button) == SDL_PRESSED;
}

Sint16 controllerAxis(SDL_GameController* controller,
                      SDL_GameControllerAxis axis) {
  if (!controller) {
    return 0;
  }
  return SDL_GameControllerGetAxis(controller, axis);
}

#ifdef __SWITCH__
PadState& switchPad() {
  static PadState pad;
  static bool initialized = false;
  if (!initialized) {
    padInitializeDefault(&pad);
    initialized = true;
  }
  return pad;
}
#endif

} // namespace

InputStateProvider::InputStateProvider() {
  ensureController();
}

InputStateProvider::~InputStateProvider() {
  if (controller_) {
    SDL_GameControllerClose(controller_);
    controller_ = nullptr;
  }
}

void InputStateProvider::ensureController() {
  if (controller_) {
    return;
  }

  const int joystickCount = SDL_NumJoysticks();
  for (int i = 0; i < joystickCount; ++i) {
    if (!SDL_IsGameController(i)) {
      continue;
    }

    controller_ = SDL_GameControllerOpen(i);
    if (controller_) {
      SDL_Log("InputStateProvider opened controller: %s",
              SDL_GameControllerName(controller_));
      return;
    }
  }
}

InputState InputStateProvider::Sample() {
  ensureController();

  const Uint8* keys = SDL_GetKeyboardState(nullptr);
  InputState state;
  bool moveLeftHeld = false;
  bool moveRightHeld = false;

  if (isDown(keys, SDL_SCANCODE_A) || isDown(keys, SDL_SCANCODE_LEFT)) {
    moveLeftHeld = true;
  }
  if (isDown(keys, SDL_SCANCODE_D) || isDown(keys, SDL_SCANCODE_RIGHT)) {
    moveRightHeld = true;
  }

  bool jumpHeld =
      isDown(keys, SDL_SCANCODE_SPACE) || isDown(keys, SDL_SCANCODE_W) ||
      isDown(keys, SDL_SCANCODE_UP);
  bool attackHeld =
      isDown(keys, SDL_SCANCODE_J) || isDown(keys, SDL_SCANCODE_K);

  if (controller_) {
    const Sint16 leftX =
        controllerAxis(controller_, SDL_CONTROLLER_AXIS_LEFTX);
    moveLeftHeld = moveLeftHeld ||
                   leftX < -kControllerAxisDeadZone ||
                   controllerButtonDown(controller_,
                                        SDL_CONTROLLER_BUTTON_DPAD_LEFT);
    moveRightHeld = moveRightHeld ||
                    leftX > kControllerAxisDeadZone ||
                    controllerButtonDown(controller_,
                                         SDL_CONTROLLER_BUTTON_DPAD_RIGHT);
    jumpHeld = jumpHeld ||
               controllerButtonDown(controller_, SDL_CONTROLLER_BUTTON_A) ||
               controllerButtonDown(controller_, SDL_CONTROLLER_BUTTON_B);
    attackHeld = attackHeld ||
                 controllerButtonDown(controller_, SDL_CONTROLLER_BUTTON_X) ||
                 controllerButtonDown(controller_, SDL_CONTROLLER_BUTTON_Y);
  }

#ifdef __SWITCH__
  PadState& pad = switchPad();
  padUpdate(&pad);
  const u64 buttons = padGetButtons(&pad);
  moveLeftHeld = moveLeftHeld || (buttons & HidNpadButton_AnyLeft) != 0;
  moveRightHeld = moveRightHeld || (buttons & HidNpadButton_AnyRight) != 0;
  jumpHeld = jumpHeld ||
             (buttons & (HidNpadButton_A | HidNpadButton_B)) != 0;
  attackHeld = attackHeld ||
               (buttons & (HidNpadButton_X | HidNpadButton_Y)) != 0;
#endif

  if (moveLeftHeld) {
    state.moveX -= 1.0f;
  }
  if (moveRightHeld) {
    state.moveX += 1.0f;
  }
  state.moveX = std::clamp(state.moveX, -1.0f, 1.0f);

  state.jumpPressed = jumpHeld && !jumpHeldLastFrame_;
  state.attackPressed = attackHeld && !attackHeldLastFrame_;

  jumpHeldLastFrame_ = jumpHeld;
  attackHeldLastFrame_ = attackHeld;
  return state;
}

} // namespace ns_game
