#pragma once

#include <SDL2/SDL_rect.h>	   // for SDL_Rect, SDL_Point
#include <SDL2/SDL_render.h>   // for SDL_RendererFlip, SDL_FLIP_NONE
#include <memory>			   // for shared_ptr, __shared_ptr_access
#include <string>			   // for string
#include <vector>			   // for vector
#include "s_animated_sprite.h" // for SAnimatedSprite
#include "defines.h"		   // for BORDER_TOP, BLOCK
#include "surface.h"		   // for Texture
#include "utils.h"			   // for Color
class Room;					   // lines 16-16
struct JA_Sound_t;			   // lines 17-17

enum class PlayerState
{
	STANDING,
	JUMPING,
	FALLING,
};

struct PlayerSpawn
{
	float x;
	float y;
	float vx;
	float vy;
	int jump_init_pos;
	PlayerState state;
	SDL_RendererFlip flip;

	// Constructor por defecto
	PlayerSpawn() : x(0), y(0), vx(0), vy(0), jump_init_pos(0), state(PlayerState::STANDING), flip(SDL_FLIP_NONE) {}

	// Constructor
	PlayerSpawn(float x, float y, float vx, float vy, int jump_init_pos, PlayerState state, SDL_RendererFlip flip)
		: x(x), y(y), vx(vx), vy(vy), jump_init_pos(jump_init_pos), state(state), flip(flip) {}
};

struct PlayerData
{
	PlayerSpawn spawn;
	std::string texture_path;
	std::string animations_path;
	std::shared_ptr<Room> room;

	// Constructor
	PlayerData(PlayerSpawn spawn, std::string texture_path, std::string animations_path, std::shared_ptr<Room> room)
		: spawn(spawn), texture_path(texture_path), animations_path(animations_path), room(room) {}
};

class Player
{
public:
	// Constantes
	static constexpr int WIDTH_ = 8;					  // Ancho del jugador
	static constexpr int HEIGHT_ = 16;					  // ALto del jugador
	static constexpr int MAX_FALLING_HEIGHT_ = BLOCK * 4; // Altura maxima permitida de caída.
	static constexpr float MAX_VY_ = 1.2f;				  // Velocidad máxima que puede alcanzar al desplazarse en vertical

	// Objetos y punteros
	std::shared_ptr<Room> room_;			 // Objeto encargado de gestionar cada habitación del juego
	std::shared_ptr<SAnimatedSprite> sprite_; // Sprite del jugador

	// Variables
	float x_;								  // Posición del jugador en el eje X
	float y_;								  // Posición del jugador en el eje Y
	float vx_;								  // Velocidad/desplazamiento del jugador en el eje X
	float vy_;								  // Velocidad/desplazamiento del jugador en el eje Y
	Uint8 color_;							  // Color del jugador
	SDL_Rect collider_box_;					  // Caja de colisión con los enemigos u objetos
	std::vector<SDL_Point> collider_points_;  // Puntos de colisión con el mapa
	std::vector<SDL_Point> under_feet_;		  // Contiene los puntos que hay bajo cada pie del jugador
	std::vector<SDL_Point> feet_;			  // Contiene los puntos que hay en el pie del jugador
	PlayerState state_;						  // Estado en el que se encuentra el jugador. Util apara saber si está saltando o cayendo
	PlayerState previous_state_;			  // Estado previo en el que se encontraba el jugador
	bool is_on_border_ = false;				  // Indica si el jugador esta en uno de los cuatro bordes de la pantalla
	bool is_alive_ = true;					  // Indica si el jugador esta vivo o no
	bool is_paused_ = false;				  // Indica si el jugador esta en modo pausa
	bool auto_movement_ = false;			  // Indica si esta siendo arrastrado por una superficie automatica
	int border_ = BORDER_TOP;				  // Indica en cual de los cuatro bordes se encuentra
	SDL_Rect last_position_;				  // Contiene la ultima posición del jugador, por si hay que deshacer algun movimiento
	int jump_init_pos_;						  // Valor del eje Y en el que se inicia el salto
	std::vector<JA_Sound_t *> jumping_sound_; // Vecor con todos los sonidos del salto
	std::vector<JA_Sound_t *> falling_sound_; // Vecor con todos los sonidos de la caída
	int jumping_counter_ = 0;				  // Cuenta el tiempo de salto
	int falling_counter_ = 0;				  // Cuenta el tiempo de caida

#ifdef DEBUG
	SDL_Rect debug_rect_x_; // Rectangulo de desplazamiento para el modo debug
	SDL_Rect debug_rect_y_; // Rectangulo de desplazamiento para el modo debug
	Color debug_color_;		// Color del recuadro de debug del jugador
	SDL_Point debug_point_; // Punto para debug
#endif

	// Comprueba las entradas y modifica variables
	void checkInput();

	// Comprueba si se halla en alguno de los cuatro bordes
	void checkBorders();

	// Comprueba el estado del jugador
	void checkState();

	// Aplica gravedad al jugador
	void applyGravity();

	// Recalcula la posición del jugador y su animación
	void move();

	// Establece la animación del jugador
	void animate();

	// Comprueba si ha finalizado el salto al alcanzar la altura de inicio
	void checkJumpEnd();

	// Calcula y reproduce el sonido de salto
	void playJumpSound();

	// Calcula y reproduce el sonido de caer
	void playFallSound();

	// Comprueba si el jugador tiene suelo debajo de los pies
	bool isOnFloor();

	// Comprueba si el jugador esta sobre una superficie automática
	bool isOnAutoSurface();

	// Comprueba si el jugador está sobre una rampa hacia abajo
	bool isOnDownSlope();

	// Comprueba que el jugador no toque ningun tile de los que matan
	bool checkKillingTiles();

	// Actualiza los puntos de colisión
	void updateColliderPoints();

	// Actualiza los puntos de los pies
	void updateFeet();

	// Cambia el estado del jugador
	void setState(PlayerState value);

	// Inicializa los sonidos de salto y caida
	void initSounds();

	// Coloca el sprite en la posición del jugador
	void placeSprite() { sprite_->setPos(x_, y_); }

	// Aplica los valores de spawn al jugador
	void applySpawnValues(const PlayerSpawn &spawn);

	// Inicializa el sprite del jugador
	void initSprite(const std::string &texture_path, const std::string &animations_path);

#ifdef DEBUG
	// Pinta la información de debug del jugador
	void renderDebugInfo();
#endif

public:
	// Constructor
	explicit Player(const PlayerData &player);

	// Destructor
	~Player() = default;

	// Pinta el enemigo en pantalla
	void render();

	// Actualiza las variables del objeto
	void update();

	// Indica si el jugador esta en uno de los cuatro bordes de la pantalla
	bool getOnBorder() { return is_on_border_; }

	// Indica en cual de los cuatro bordes se encuentra
	int getBorder() { return border_; }

	// Cambia al jugador de un borde al opuesto. Util para el cambio de pantalla
	void switchBorders();

	// Obtiene el rectangulo que delimita al jugador
	SDL_Rect getRect() { return {static_cast<int>(x_), static_cast<int>(y_), WIDTH_, HEIGHT_}; }

	// Obtiene el rectangulo de colision del jugador
	SDL_Rect &getCollider() { return collider_box_; }

	// Obtiene el estado de reaparición del jugador
	PlayerSpawn getSpawnParams() { return {x_, y_, vx_, vy_, jump_init_pos_, state_, sprite_->getFlip()}; }

	// Establece el color del jugador
	void setColor();

	// Establece la habitación en la que se encuentra el jugador
	void setRoom(std::shared_ptr<Room> room) { room_ = room; }

	// Comprueba si el jugador esta vivo
	bool isAlive() { return is_alive_; }

	// Pone el jugador en modo pausa
	void setPaused(bool value) { is_paused_ = value; }
};