#pragma once

#include <SDL2/SDL_gamecontroller.h>  // for SDL_GameControllerButton, SDL_G...
#include <SDL2/SDL_joystick.h>        // for SDL_Joystick
#include <SDL2/SDL_scancode.h>        // for SDL_Scancode
#include <SDL2/SDL_stdinc.h>          // for Uint8
#include <string>                     // for string, basic_string
#include <vector>                     // for vector

/*
connectedControllers es un vector donde estan todos los mandos encontrados [0 .. n]
checkInput requiere de un indice para comprobar las pulsaciónes de un controlador en concreto [0 .. n]
device contiene el tipo de dispositivo a comprobar:
	INPUT_USE_KEYBOARD solo mirará el teclado
	INPUT_USE_GAMECONTROLLER solo mirará el controlador especificado (Si no se especifica, el primero)
	INPUT_USE_ANY mirará tanto el teclado como el PRIMER controlador
*/

enum inputs_e
{
	// Inputs de movimiento
	input_up,
	input_down,
	input_left,
	input_right,

	// Inputs personalizados
	input_fire_left,
	input_fire_center,
	input_fire_right,
	input_start,

	// Inputs de control
	input_exit,
	input_pause,
	input_service,
	input_window_fullscreen,
	input_window_inc_size,
	input_window_dec_size,
	input_video_shaders,
	input_reset,
	input_mute,
	input_showinfo,
	input_config,
	input_swap_controllers,

	// Input obligatorio
	input_null,
	input_number_of_inputs,
};

#define INPUT_ALLOW_REPEAT true
#define INPUT_DO_NOT_ALLOW_REPEAT false

#define INPUT_USE_KEYBOARD 0
#define INPUT_USE_GAMECONTROLLER 1
#define INPUT_USE_ANY 2

enum i_disable_e
{
	d_notDisabled,
	d_forever,
	d_keyPressed
};

class Input
{
private:
	// [SINGLETON] Objeto screen privado para Don Melitón
	static Input *input;

	struct keyBindings_t
	{
		Uint8 scancode; // Scancode asociado
		bool active;	// Indica si está activo
	};

	struct GameControllerBindings_t
	{
		SDL_GameControllerButton button; // GameControllerButton asociado
		bool active;					 // Indica si está activo
	};

	// Variables
	std::vector<SDL_GameController *> connectedControllers;					   // Vector con todos los mandos conectados
	std::vector<SDL_Joystick *> joysticks;									   // Vector con todos los joysticks conectados
	std::vector<keyBindings_t> keyBindings;									   // Vector con las teclas asociadas a los inputs predefinidos
	std::vector<std::vector<GameControllerBindings_t>> gameControllerBindings; // Vector con los botones asociadas a los inputs predefinidos para cada mando
	std::vector<std::string> controllerNames;								   // Vector con los nombres de los mandos
	std::vector<inputs_e> gameInputs;										   // Inputs usados para jugar, normalmente direcciones y botones
	std::vector<inputs_e> buttonInputs;										   // Inputs asignados al jugador y a botones, excluyendo direcciones
	int numJoysticks;														   // Número de joysticks conectados
	int numGamepads;														   // Número de mandos conectados
	std::string dbPath;														   // Ruta al archivo gamecontrollerdb.txt
	bool verbose;															   // Indica si ha de mostrar mensajes
	i_disable_e disabledUntil;												   // Tiempo que esta deshabilitado
	bool enabled;															   // Indica si está habilitado

	// Comprueba el eje del mando
	bool checkAxisInput(inputs_e input, int index = 0) const;

	// Constructor
	Input(std::string dbPath);

	// Destructor
	~Input();

public:
	// [SINGLETON] Crearemos el objeto screen con esta función estática
	static void init(std::string dbPath);

	// [SINGLETON] Destruiremos el objeto screen con esta función estática
	static void destroy();

	// [SINGLETON] Con este método obtenemos el objeto screen y podemos trabajar con él
	static Input *get();

	// Actualiza el estado del objeto
	void update();

	// Asigna inputs a teclas
	void bindKey(inputs_e input, SDL_Scancode code);

	// Asigna inputs a botones del mando
	void bindGameControllerButton(int index, inputs_e input, SDL_GameControllerButton button);
	void bindGameControllerButton(int index, inputs_e inputTarget, inputs_e inputSource);

	// Comprueba si un input esta activo
	bool checkInput(inputs_e input, bool repeat = true, int device = INPUT_USE_ANY, int index = 0);

	// Comprueba si un input con modificador esta activo
	bool checkModInput(inputs_e inputMod, inputs_e input, bool repeat = true, int device = INPUT_USE_ANY, int index = 0);

	// Comprueba si hay almenos un input activo
	bool checkAnyInput(int device = INPUT_USE_ANY, int index = 0);

	// Comprueba si hay algún botón pulsado
	int checkAnyButtonPressed(bool repeat = INPUT_DO_NOT_ALLOW_REPEAT);

	// Busca si hay mandos conectados
	bool discoverGameControllers();

	// Comprueba si hay algun mando conectado
	bool gameControllerFound();

	// Obten el número de mandos conectados
	int getNumControllers() const;

	// Obten el nombre de un mando de juego
	std::string getControllerName(int index) const;

	// Establece si ha de mostrar mensajes
	void setVerbose(bool value);

	// Deshabilita las entradas durante un periodo de tiempo
	void disableUntil(i_disable_e value);

	// Hablita las entradas
	void enable();

	// Obtiene el indice del controlador a partir de un event.id
	int getJoyIndex(int id) const;

	// Muestra por consola los controles asignados
	void printBindings(int device = INPUT_USE_KEYBOARD, int index = 0) const;

	// Obtiene el SDL_GameControllerButton asignado a un input
	SDL_GameControllerButton getControllerBinding(int index, inputs_e input) const;

	// Convierte un inputs_e a std::string
	std::string to_string(inputs_e input) const;

	// Convierte un std::string a inputs_e
	inputs_e to_inputs_e(std::string name) const;

	// Obtiene el indice a partir del nombre del mando
	int getIndexByName(std::string name) const;

	// Activa todos los inputs. Sirve para evitar inputs sin repeticiones pero que ya vienen pulsados cuando checkInput no estaba monitorizando
	void allActive(int index);
};