#include "room.h"
#include <SDL2/SDL_blendmode.h> // Para SDL_BLENDMODE_BLEND
#include <SDL2/SDL_error.h>     // Para SDL_GetError
#include <SDL2/SDL_pixels.h>    // Para SDL_PIXELFORMAT_RGBA8888
#include <stdlib.h>             // Para rand
#include <fstream>              // Para basic_ostream, operator<<, basic_ist...
#include <iostream>             // Para cout
#include <sstream>              // Para basic_stringstream
#include "asset.h"              // Para Asset
#include "defines.h"            // Para BLOCK, PLAY_AREA_HEIGHT, PLAY_AREA_W...
#include "debug.h"              // Para Debug
#include "item_tracker.h"       // Para ItemTracker
#include "jail_audio.h"         // Para JA_DeleteSound, JA_LoadSound, JA_Pla...
#include "screen.h"             // Para Screen
#include "sprite.h"             // Para Sprite
#include "texture.h"            // Para Texture
#include "options.h"
#include "utils.h" // Para stringToBool, stringToColor
#include "resource.h"

// Carga las variables y texturas desde un fichero de mapa de tiles
std::vector<int> loadRoomTileFile(const std::string &file_path, bool verbose)
{
    std::vector<int> tileMapFile;
    const std::string filename = file_path.substr(file_path.find_last_of("\\/") + 1);
    std::ifstream file(file_path);

    // El fichero se puede abrir
    if (file.good())
    {
        std::string line;
        // Procesa el fichero linea a linea
        while (std::getline(file, line))
        { // Lee el fichero linea a linea
            if (line.find("data encoding") != std::string::npos)
            {
                // Lee la primera linea
                std::getline(file, line);
                while (line != "</data>")
                { // Procesa lineas mientras haya
                    std::stringstream ss(line);
                    std::string tmp;
                    while (getline(ss, tmp, ','))
                    {
                        tileMapFile.push_back(std::stoi(tmp) - 1);
                    }

                    // Lee la siguiente linea
                    std::getline(file, line);
                }
            }
        }

        // Cierra el fichero
        if (verbose)
        {
            std::cout << "TileMap loaded: " << filename.c_str() << std::endl;
        }
        file.close();
    }

    else
    { // El fichero no se puede abrir
        if (verbose)
        {
            std::cout << "Warning: Unable to open " << filename.c_str() << " file" << std::endl;
        }
    }

    return tileMapFile;
}

// Carga las variables desde un fichero de mapa
RoomData loadRoomFile(const std::string &file_path, bool verbose)
{
    RoomData room;
    room.item_color1 = "yellow";
    room.item_color2 = "magenta";
    room.auto_surface_direction = 1;

    const std::string fileName = file_path.substr(file_path.find_last_of("\\/") + 1);
    room.number = fileName.substr(0, fileName.find_last_of("."));

    std::ifstream file(file_path);

    // El fichero se puede abrir
    if (file.good())
    {
        std::string line;
        // Procesa el fichero linea a linea
        while (std::getline(file, line))
        {
            // Si la linea contiene el texto [enemy] se realiza el proceso de carga de un enemigo
            if (line == "[enemy]")
            {
                EnemyData enemy;
                enemy.flip = false;
                enemy.mirror = false;
                enemy.frame = -1;

                do
                {
                    std::getline(file, line);

                    // Encuentra la posición del caracter '='
                    int pos = line.find("=");

                    // Procesa las dos subcadenas
                    std::string key = line.substr(0, pos);
                    std::string value = line.substr(pos + 1, line.length());
                    if (!setEnemy(&enemy, key, value))
                        if (verbose)
                        {
                            std::cout << "Warning: file " << fileName.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl;
                        }
                } while (line != "[/enemy]");

                // Añade el enemigo al vector de enemigos
                room.enemies.push_back(enemy);
            }

            // Si la linea contiene el texto [item] se realiza el proceso de carga de un item
            else if (line == "[item]")
            {
                ItemData item;
                item.counter = 0;
                item.color1 = stringToColor(Palette::ZXSPECTRUM, "yellow");
                item.color2 = stringToColor(Palette::ZXSPECTRUM, "magenta");

                do
                {
                    std::getline(file, line);

                    // Encuentra la posición del caracter '='
                    int pos = line.find("=");

                    // Procesa las dos subcadenas
                    std::string key = line.substr(0, pos);
                    std::string value = line.substr(pos + 1, line.length());
                    if (!setItem(&item, key, value))
                    {
                        if (verbose)
                        {
                            std::cout << "Warning: file " << fileName.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl;
                        }
                    }

                } while (line != "[/item]");

                room.items.push_back(item);
            }

            // En caso contrario se parsea el fichero para buscar las variables y los valores
            else
            {
                // Encuentra la posición del caracter '='
                int pos = line.find("=");

                // Procesa las dos subcadenas
                std::string key = line.substr(0, pos);
                std::string value = line.substr(pos + 1, line.length());
                if (!setRoom(&room, key, value))
                {
                    if (verbose)
                    {
                        std::cout << "Warning: file " << fileName.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl;
                    }
                }
            }
        }

        // Cierra el fichero
        if (verbose)
        {
            std::cout << "Room loaded: " << fileName.c_str() << std::endl;
        }
        file.close();
    }
    // El fichero no se puede abrir
    else
    {
        {
            std::cout << "Warning: Unable to open " << fileName.c_str() << " file" << std::endl;
        }
    }

    return room;
}

// Asigna variables a una estructura RoomData
bool setRoom(RoomData *room, const std::string &key, const std::string &value)
{
    // Indicador de éxito en la asignación
    bool success = true;

    try
    {
        if (key == "tileMapFile")
        {
            room->tile_map_file = value;
        }
        else if (key == "name")
        {
            room->name = value;
        }
        else if (key == "bgColor")
        {
            room->bg_color = value;
        }
        else if (key == "border")
        {
            room->border_color = value;
        }
        else if (key == "itemColor1")
        {
            room->item_color1 = value;
        }
        else if (key == "itemColor2")
        {
            room->item_color2 = value;
        }
        else if (key == "tileSetFile")
        {
            room->tile_set_file = value;
        }
        else if (key == "roomUp")
        {
            room->room_top = value;
        }
        else if (key == "roomDown")
        {
            room->room_bottom = value;
        }
        else if (key == "roomLeft")
        {
            room->room_left = value;
        }
        else if (key == "roomRight")
        {
            room->room_right = value;
        }
        else if (key == "autoSurface")
        {
            room->auto_surface_direction = (value == "right") ? 1 : -1;
        }
        else if (key == "" || key.substr(0, 1) == "#")
        {
            // No se realiza ninguna acción para estas claves
        }
        else
        {
            success = false;
        }
    }
    catch (const std::exception &e)
    {
        std::cerr << "Error al asignar la clave " << key << " con valor " << value << ": " << e.what() << std::endl;
        success = false;
    }

    return success;
}

// Asigna variables a una estructura EnemyData
bool setEnemy(EnemyData *enemy, const std::string &key, const std::string &value)
{
    // Indicador de éxito en la asignación
    bool success = true;

    try
    {
        if (key == "tileSetFile")
        {
            enemy->texture_path = value;
        }
        else if (key == "animation")
        {
            enemy->animation_path = value;
        }
        else if (key == "width")
        {
            enemy->w = std::stoi(value);
        }
        else if (key == "height")
        {
            enemy->h = std::stoi(value);
        }
        else if (key == "x")
        {
            enemy->x = std::stof(value) * BLOCK;
        }
        else if (key == "y")
        {
            enemy->y = std::stof(value) * BLOCK;
        }
        else if (key == "vx")
        {
            enemy->vx = std::stof(value);
        }
        else if (key == "vy")
        {
            enemy->vy = std::stof(value);
        }
        else if (key == "x1")
        {
            enemy->x1 = std::stoi(value) * BLOCK;
        }
        else if (key == "x2")
        {
            enemy->x2 = std::stoi(value) * BLOCK;
        }
        else if (key == "y1")
        {
            enemy->y1 = std::stoi(value) * BLOCK;
        }
        else if (key == "y2")
        {
            enemy->y2 = std::stoi(value) * BLOCK;
        }
        else if (key == "flip")
        {
            enemy->flip = stringToBool(value);
        }
        else if (key == "mirror")
        {
            enemy->mirror = stringToBool(value);
        }
        else if (key == "color")
        {
            enemy->color = value;
        }
        else if (key == "frame")
        {
            enemy->frame = std::stoi(value);
        }
        else if (key == "[/enemy]" || key == "tileSetFile" || key.substr(0, 1) == "#")
        {
            // No se realiza ninguna acción para estas claves
        }
        else
        {
            success = false;
        }
    }
    catch (const std::exception &e)
    {
        std::cerr << "Error al asignar la clave " << key << " con valor " << value << ": " << e.what() << std::endl;
        success = false;
    }

    return success;
}

// Asigna variables a una estructura ItemData
bool setItem(ItemData *item, const std::string &key, const std::string &value)
{
    // Indicador de éxito en la asignación
    bool success = true;

    try
    {
        if (key == "tileSetFile")
        {
            item->tile_set_file = value;
        }
        else if (key == "counter")
        {
            item->counter = std::stoi(value);
        }
        else if (key == "x")
        {
            item->x = std::stof(value) * BLOCK;
        }
        else if (key == "y")
        {
            item->y = std::stof(value) * BLOCK;
        }
        else if (key == "tile")
        {
            item->tile = std::stof(value);
        }
        else if (key == "[/item]")
        {
            // No se realiza ninguna acción para esta clave
        }
        else
        {
            success = false;
        }
    }
    catch (const std::exception &e)
    {
        std::cerr << "Error al asignar la clave " << key << " con valor " << value << ": " << e.what() << std::endl;
        success = false;
    }

    return success;
}

// Constructor
Room::Room(const std::string &room_path, std::shared_ptr<ScoreboardData> data)
    : data_(data)
{
    auto room = Resource::get()->getRoom(room_path);
    number_ = room->number;
    name_ = room->name;
    bg_color_ = room->bg_color;
    border_color_ = room->border_color;
    item_color1_ = room->item_color1 == "" ? "yellow" : room->item_color1;
    item_color2_ = room->item_color2 == "" ? "magenta" : room->item_color2;
    room_top_ = room->room_top;
    room_bottom_ = room->room_bottom;
    room_left_ = room->room_left;
    room_right_ = room->room_right;
    tile_set_file_ = room->tile_set_file;
    tile_map_file_ = room->tile_map_file;
    auto_surface_direction_ = room->auto_surface_direction;
    tile_map_ = Resource::get()->getTileMap(room->tile_map_file);
    texture_ = Resource::get()->getTexture(room->tile_set_file);

    // Inicializa variables
    tile_set_width_ = texture_->getWidth() / TILE_SIZE_;
    is_paused_ = false;
    counter_ = 0;

    // Crea los enemigos
    for (auto &enemy_data : room->enemies)
    {
        enemies_.emplace_back(std::make_shared<Enemy>(enemy_data));
    }

    // Crea los items
    for (auto &item : room->items)
    {
        const SDL_Point itemPos = {item.x, item.y};

        if (!ItemTracker::get()->hasBeenPicked(room->name, itemPos))
        {
            item.color1 = stringToColor(options.video.palette, item_color1_);
            item.color2 = stringToColor(options.video.palette, item_color2_);
            items_.emplace_back(std::make_shared<Item>(item));
        }
    }

    // Abre la jail para poder entrar
    if (data_->jail_is_open)
    {
        openTheJail();
    }

    // Calcula las superficies
    setBottomSurfaces();
    setTopSurfaces();
    setLeftSurfaces();
    setRightSurfaces();
    setLeftSlopes();
    setRightSlopes();
    setAutoSurfaces();

    // Busca los tiles animados
    setAnimatedTiles();

    // Crea la textura para el mapa de tiles de la habitación
    map_texture_ = SDL_CreateTexture(Screen::get()->getRenderer(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, PLAY_AREA_WIDTH, PLAY_AREA_HEIGHT);
    if (map_texture_ == nullptr)
    {
        if (options.console)
        {
            std::cout << "Error: mapTexture could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
        }
    }
    SDL_SetTextureBlendMode(map_texture_, SDL_BLENDMODE_BLEND);

    // Pinta el mapa de la habitación en la textura
    fillMapTexture();

    // Establece el color del borde
    Screen::get()->setBorderColor(stringToColor(options.video.palette, room->border_color));
}

// Destructor
Room::~Room()
{
    SDL_DestroyTexture(map_texture_);
}

void Room::initializeRoom(const RoomData& room) {
    // Asignar valores a las variables miembro
    number_ = room.number;
    name_ = room.name;
    bg_color_ = room.bg_color;
    border_color_ = room.border_color;
    item_color1_ = room.item_color1.empty() ? "yellow" : room.item_color1;
    item_color2_ = room.item_color2.empty() ? "magenta" : room.item_color2;
    room_top_ = room.room_top;
    room_bottom_ = room.room_bottom;
    room_left_ = room.room_left;
    room_right_ = room.room_right;
    tile_set_file_ = room.tile_set_file;
    tile_map_file_ = room.tile_map_file;
    auto_surface_direction_ = room.auto_surface_direction;
    tile_map_ = Resource::get()->getTileMap(room.tile_map_file);
    texture_ = Resource::get()->getTexture(room.tile_set_file);
    tile_set_width_ = texture_->getWidth() / TILE_SIZE_;
    is_paused_ = false;
    counter_ = 0;

    // Crear los enemigos
    for (auto &enemy_data : room.enemies) {
        enemies_.emplace_back(std::make_shared<Enemy>(enemy_data));
    }

    // Crear los items
    for (auto &item : room.items) {
        const SDL_Point itemPos = {item.x, item.y};

        if (!ItemTracker::get()->hasBeenPicked(room.name, itemPos)) {
            item.color1 = stringToColor(options.video.palette, item_color1_);
            item.color2 = stringToColor(options.video.palette, item_color2_);
            items_.emplace_back(std::make_shared<Item>(item));
        }
    }
}

// Devuelve el nombre de la habitación
std::string Room::getName()
{
    return name_;
}

// Devuelve el color de la habitación
Color Room::getBGColor()
{
    return stringToColor(options.video.palette, bg_color_);
}

// Devuelve el color del borde
Color Room::getBorderColor()
{
    return stringToColor(options.video.palette, border_color_);
}

// Crea la textura con el mapeado de la habitación
void Room::fillMapTexture()
{
    const Color color = stringToColor(options.video.palette, bg_color_);
    SDL_SetRenderTarget(Screen::get()->getRenderer(), map_texture_);
    SDL_SetRenderDrawColor(Screen::get()->getRenderer(), color.r, color.g, color.b, 0xFF);
    SDL_RenderClear(Screen::get()->getRenderer());

    // Los tileSetFiles son de 20x20 tiles. El primer tile es el 0. Cuentan hacia la derecha y hacia abajo

    SDL_Rect clip = {0, 0, TILE_SIZE_, TILE_SIZE_};
    for (int y = 0; y < MAP_HEIGHT_; ++y)
        for (int x = 0; x < MAP_WIDTH_; ++x)
        {
            // Tiled pone los tiles vacios del mapa como cero y empieza a contar de 1 a n.
            // Al cargar el mapa en memoria, se resta uno, por tanto los tiles vacios son -1
            // Tampoco hay que dibujar los tiles animados que estan en la fila 19 (indices)
            const int index = (y * MAP_WIDTH_) + x;
            const bool a = (tile_map_[index] >= 18 * tile_set_width_) && (tile_map_[index] < 19 * tile_set_width_);
            const bool b = tile_map_[index] > -1;

            if (b && !a)
            {
                clip.x = (tile_map_[index] % tile_set_width_) * TILE_SIZE_;
                clip.y = (tile_map_[index] / tile_set_width_) * TILE_SIZE_;
                texture_->render(x * TILE_SIZE_, y * TILE_SIZE_, &clip);

#ifdef DEBUG
                if (Debug::get()->getEnabled())
                {
                    if (clip.x != -TILE_SIZE_)
                    {
                        clip.x = x * TILE_SIZE_;
                        clip.y = y * TILE_SIZE_;
                        SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 64, 64, 64, 224);
                        SDL_RenderFillRect(Screen::get()->getRenderer(), &clip);
                    }
                }
#endif
            }
        }

#ifdef DEBUG
    if (Debug::get()->getEnabled())
    {
        // BottomSurfaces
        if (true)
        {
            for (auto l : bottom_surfaces_)
            {
                SDL_SetRenderDrawColor(Screen::get()->getRenderer(), (rand() % 128) + 96, (rand() % 128) + 96, (rand() % 128) + 96, 0xFF);
                SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 255, 0, 0, 0xFF);
                SDL_RenderDrawLine(Screen::get()->getRenderer(), l.x1, l.y, l.x2, l.y);
            }
        }

        // TopSurfaces
        if (true)
        {
            for (auto l : top_surfaces_)
            {
                SDL_SetRenderDrawColor(Screen::get()->getRenderer(), (rand() % 128) + 96, (rand() % 128) + 96, (rand() % 128) + 96, 0xFF);
                SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 255, 0, 0xFF);
                SDL_RenderDrawLine(Screen::get()->getRenderer(), l.x1, l.y, l.x2, l.y);
            }
        }

        // LeftSurfaces
        if (true)
        {
            for (auto l : left_surfaces_)
            {
                SDL_SetRenderDrawColor(Screen::get()->getRenderer(), (rand() % 128) + 96, (rand() % 128) + 96, (rand() % 128) + 96, 0xFF);
                SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 128, 128, 255, 0xFF);
                SDL_RenderDrawLine(Screen::get()->getRenderer(), l.x, l.y1, l.x, l.y2);
            }
        }

        // RightSurfaces
        if (true)
        {
            for (auto l : right_surfaces_)
            {
                SDL_SetRenderDrawColor(Screen::get()->getRenderer(), (rand() % 128) + 96, (rand() % 128) + 96, (rand() % 128) + 96, 0xFF);
                SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 255, 255, 0, 0xFF);
                SDL_RenderDrawLine(Screen::get()->getRenderer(), l.x, l.y1, l.x, l.y2);
            }
        }

        // LeftSlopes
        if (true)
        {
            for (auto l : left_slopes_)
            {
                SDL_SetRenderDrawColor(Screen::get()->getRenderer(), (rand() % 128) + 96, (rand() % 128) + 96, (rand() % 128) + 96, 0xFF);
                SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 255, 255, 0xFF);
                SDL_RenderDrawLine(Screen::get()->getRenderer(), l.x1, l.y1, l.x2, l.y2);
            }
        }

        // RightSlopes
        if (true)
        {
            for (auto l : right_slopes_)
            {
                SDL_SetRenderDrawColor(Screen::get()->getRenderer(), (rand() % 128) + 96, (rand() % 128) + 96, (rand() % 128) + 96, 0xFF);
                SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 255, 0, 255, 0xFF);
                SDL_RenderDrawLine(Screen::get()->getRenderer(), l.x1, l.y1, l.x2, l.y2);
            }
        }

        // AutoSurfaces
        if (true)
        {
            for (auto l : auto_surfaces_)
            {
                SDL_SetRenderDrawColor(Screen::get()->getRenderer(), (rand() % 128) + 96, (rand() % 128) + 96, (rand() % 128) + 96, 0xFF);
                SDL_RenderDrawLine(Screen::get()->getRenderer(), l.x1, l.y, l.x2, l.y);
            }
        }
    }
#endif

    SDL_SetRenderTarget(Screen::get()->getRenderer(), nullptr);
}

// Dibuja el mapa en pantalla
void Room::renderMap()
{
    // Dibuja la textura con el mapa en pantalla
    SDL_Rect dest = {0, 0, PLAY_AREA_WIDTH, PLAY_AREA_HEIGHT};
    SDL_RenderCopy(Screen::get()->getRenderer(), map_texture_, nullptr, &dest);

// Dibuja los tiles animados
#ifdef DEBUG
    if (!Debug::get()->getEnabled())
    {
        renderAnimatedTiles();
    }
#else
    renderAnimatedTiles();
#endif
}

// Dibuja los enemigos en pantalla
void Room::renderEnemies()
{
    for (auto enemy : enemies_)
    {
        enemy->render();
    }
}

// Dibuja los objetos en pantalla
void Room::renderItems()
{
    for (auto item : items_)
    {
        item->render();
    }
}

// Actualiza las variables y objetos de la habitación
void Room::update()
{
    if (is_paused_)
    { // Si está en modo pausa no se actualiza nada
        return;
    }

    // Actualiza el contador
    counter_++;

    // Actualiza los tiles animados
    updateAnimatedTiles();

    for (auto enemy : enemies_)
    { // Actualiza los enemigos
        enemy->update();
    }

    for (auto item : items_)
    { // Actualiza los items
        item->update();
    }
}

// Devuelve la cadena del fichero de la habitación contigua segun el borde
std::string Room::getRoom(int border)
{
    switch (border)
    {
    case BORDER_TOP:
        return room_top_;
        break;

    case BORDER_BOTTOM:
        return room_bottom_;
        break;

    case BORDER_RIGHT:
        return room_right_;
        break;

    case BORDER_LEFT:
        return room_left_;
        break;

    default:
        break;
    }
    return "";
}

// Devuelve el tipo de tile que hay en ese pixel
tile_e Room::getTile(SDL_Point point)
{
    const int pos = ((point.y / TILE_SIZE_) * MAP_WIDTH_) + (point.x / TILE_SIZE_);
    return getTile(pos);
}

// Devuelve el tipo de tile que hay en ese indice
tile_e Room::getTile(int index)
{
    // const bool onRange = (index > -1) && (index < mapWidth * mapHeight);
    const bool onRange = (index > -1) && (index < (int)tile_map_.size());

    if (onRange)
    {
        // Las filas 0-8 son de tiles t_wall
        if ((tile_map_[index] >= 0) && (tile_map_[index] < 9 * tile_set_width_))
        {
            return t_wall;
        }

        // Las filas 9-17 son de tiles t_passable
        else if ((tile_map_[index] >= 9 * tile_set_width_) && (tile_map_[index] < 18 * tile_set_width_))
        {
            return t_passable;
        }

        // Las filas 18-20 es de tiles t_animated
        else if ((tile_map_[index] >= 18 * tile_set_width_) && (tile_map_[index] < 21 * tile_set_width_))
        {
            return t_animated;
        }

        // La fila 21 es de tiles t_slope_r
        else if ((tile_map_[index] >= 21 * tile_set_width_) && (tile_map_[index] < 22 * tile_set_width_))
        {
            return t_slope_r;
        }

        // La fila 22 es de tiles t_slope_l
        else if ((tile_map_[index] >= 22 * tile_set_width_) && (tile_map_[index] < 23 * tile_set_width_))
        {
            return t_slope_l;
        }

        // La fila 23 es de tiles t_kill
        else if ((tile_map_[index] >= 23 * tile_set_width_) && (tile_map_[index] < 24 * tile_set_width_))
        {
            return t_kill;
        }
    }

    return t_empty;
}

// Indica si hay colision con un enemigo a partir de un rectangulo
bool Room::enemyCollision(SDL_Rect &rect)
{
    bool collision = false;

    for (auto enemy : enemies_)
    {
        collision |= checkCollision(rect, enemy->getCollider());
    }

    return collision;
}

// Indica si hay colision con un objeto a partir de un rectangulo
bool Room::itemCollision(SDL_Rect &rect)
{
    for (int i = 0; i < (int)items_.size(); ++i)
    {
        if (checkCollision(rect, items_[i]->getCollider()))
        {
            ItemTracker::get()->addItem(name_, items_[i]->getPos());
            items_.erase(items_.begin() + i);
            JA_PlaySound(Resource::get()->getSound("item.wav"));
            data_->items++;
            options.stats.items = data_->items;
            return true;
        }
    }

    return false;
}

// Obten la coordenada de la cuesta a partir de un punto perteneciente a ese tile
int Room::getSlopeHeight(SDL_Point p, tile_e slope)
{
    // Calcula la base del tile
    int base = ((p.y / TILE_SIZE_) * TILE_SIZE_) + TILE_SIZE_;
#ifdef DEBUG
    Debug::get()->add("BASE = " + std::to_string(base));
#endif

    // Calcula cuanto se ha entrado en el tile horizontalmente
    const int pos = (p.x % TILE_SIZE_); // Esto da un valor entre 0 y 7
#ifdef DEBUG
    Debug::get()->add("POS = " + std::to_string(pos));
#endif

    // Se resta a la base la cantidad de pixeles pos en funcion de la rampa
    if (slope == t_slope_r)
    {
        base -= pos + 1;
#ifdef DEBUG
        Debug::get()->add("BASE_R = " + std::to_string(base));
#endif
    }
    else
    {
        base -= (TILE_SIZE_ - pos);
#ifdef DEBUG
        Debug::get()->add("BASE_L = " + std::to_string(base));
#endif
    }

    return base;
}

// Calcula las superficies inferiores
void Room::setBottomSurfaces()
{
    std::vector<int> tile;

    // Busca todos los tiles de tipo muro que no tengan debajo otro muro
    // Hay que recorrer la habitación por filas (excepto los de la última fila)
    for (int i = 0; i < (int)tile_map_.size() - MAP_WIDTH_; ++i)
    {
        if (getTile(i) == t_wall && getTile(i + MAP_WIDTH_) != t_wall)
        {
            tile.push_back(i);

            // Si llega al final de la fila, introduce un separador
            if (i % MAP_WIDTH_ == MAP_WIDTH_ - 1)
            {
                tile.push_back(-1);
            }
        }
    }

    // Añade un terminador
    tile.push_back(-1);

    // Recorre el vector de tiles buscando tiles consecutivos para localizar las superficies
    if ((int)tile.size() > 1)
    {
        int i = 0;
        do
        {
            h_line_t line;
            line.x1 = (tile[i] % MAP_WIDTH_) * TILE_SIZE_;
            line.y = ((tile[i] / MAP_WIDTH_) * TILE_SIZE_) + TILE_SIZE_ - 1;
            int last_one = i;
            i++;

            if (i <= (int)tile.size() - 1)
            {
                while (tile[i] == tile[i - 1] + 1)
                {
                    last_one = i;
                    if (i == (int)tile.size() - 1)
                    {
                        break;
                    }
                    i++;
                }
            }

            line.x2 = ((tile[last_one] % MAP_WIDTH_) * TILE_SIZE_) + TILE_SIZE_ - 1;
            bottom_surfaces_.push_back(line);
            if (i <= (int)tile.size() - 1)
            {
                if (tile[i] == -1)
                { // Si el siguiente elemento es un separador, hay que saltarlo
                    i++;
                }
            }
        } while (i < (int)tile.size() - 1);
    }
}

// Calcula las superficies superiores
void Room::setTopSurfaces()
{
    std::vector<int> tile;

    // Busca todos los tiles de tipo muro o pasable que no tengan encima un muro
    // Hay que recorrer la habitación por filas (excepto los de la primera fila)
    for (int i = MAP_WIDTH_; i < (int)tile_map_.size(); ++i)
    {
        if ((getTile(i) == t_wall || getTile(i) == t_passable) && getTile(i - MAP_WIDTH_) != t_wall)
        {
            tile.push_back(i);

            // Si llega al final de la fila, introduce un separador
            if (i % MAP_WIDTH_ == MAP_WIDTH_ - 1)
            {
                tile.push_back(-1);
            }
        }
    }

    // Añade un terminador
    tile.push_back(-1);

    // Recorre el vector de tiles buscando tiles consecutivos para localizar las superficies
    if ((int)tile.size() > 1)
    {
        int i = 0;
        do
        {
            h_line_t line;
            line.x1 = (tile[i] % MAP_WIDTH_) * TILE_SIZE_;
            line.y = (tile[i] / MAP_WIDTH_) * TILE_SIZE_;
            int last_one = i;
            i++;

            if (i <= (int)tile.size() - 1)
            {
                while (tile[i] == tile[i - 1] + 1)
                {
                    last_one = i;
                    if (i == (int)tile.size() - 1)
                    {
                        break;
                    }
                    i++;
                }
            }

            line.x2 = ((tile[last_one] % MAP_WIDTH_) * TILE_SIZE_) + TILE_SIZE_ - 1;
            top_surfaces_.push_back(line);
            if (i <= (int)tile.size() - 1)
            {
                if (tile[i] == -1)
                { // Si el siguiente elemento es un separador, hay que saltarlo
                    i++;
                }
            }
        } while (i < (int)tile.size() - 1);
    }
}

// Calcula las superficies laterales izquierdas
void Room::setLeftSurfaces()
{
    std::vector<int> tile;

    // Busca todos los tiles de tipo muro que no tienen a su izquierda un tile de tipo muro
    // Hay que recorrer la habitación por columnas (excepto los de la primera columna)
    for (int i = 1; i < MAP_WIDTH_; ++i)
    {
        for (int j = 0; j < MAP_HEIGHT_; ++j)
        {
            const int pos = (j * MAP_WIDTH_ + i);
            if (getTile(pos) == t_wall && getTile(pos - 1) != t_wall)
            {
                tile.push_back(pos);
            }
        }
    }

    // Añade un terminador
    tile.push_back(-1);

    // Recorre el vector de tiles buscando tiles consecutivos
    // (Los tiles de la misma columna, la diferencia entre ellos es de mapWidth)
    // para localizar las superficies
    if ((int)tile.size() > 1)
    {
        int i = 0;
        do
        {
            v_line_t line;
            line.x = (tile[i] % MAP_WIDTH_) * TILE_SIZE_;
            line.y1 = ((tile[i] / MAP_WIDTH_) * TILE_SIZE_);
            while (tile[i] + MAP_WIDTH_ == tile[i + 1])
            {
                if (i == (int)tile.size() - 1)
                {
                    break;
                }
                i++;
            }
            line.y2 = ((tile[i] / MAP_WIDTH_) * TILE_SIZE_) + TILE_SIZE_ - 1;
            left_surfaces_.push_back(line);
            i++;
        } while (i < (int)tile.size() - 1);
    }
}

// Calcula las superficies laterales derechas
void Room::setRightSurfaces()
{
    std::vector<int> tile;

    // Busca todos los tiles de tipo muro que no tienen a su derecha un tile de tipo muro
    // Hay que recorrer la habitación por columnas (excepto los de la última columna)
    for (int i = 0; i < MAP_WIDTH_ - 1; ++i)
    {
        for (int j = 0; j < MAP_HEIGHT_; ++j)
        {
            const int pos = (j * MAP_WIDTH_ + i);
            if (getTile(pos) == t_wall && getTile(pos + 1) != t_wall)
            {
                tile.push_back(pos);
            }
        }
    }

    // Añade un terminador
    tile.push_back(-1);

    // Recorre el vector de tiles buscando tiles consecutivos
    // (Los tiles de la misma columna, la diferencia entre ellos es de mapWidth)
    // para localizar las superficies
    if ((int)tile.size() > 1)
    {
        int i = 0;
        do
        {
            v_line_t line;
            line.x = ((tile[i] % MAP_WIDTH_) * TILE_SIZE_) + TILE_SIZE_ - 1;
            line.y1 = ((tile[i] / MAP_WIDTH_) * TILE_SIZE_);
            while (tile[i] + MAP_WIDTH_ == tile[i + 1])
            {
                if (i == (int)tile.size() - 1)
                {
                    break;
                }
                i++;
            }
            line.y2 = ((tile[i] / MAP_WIDTH_) * TILE_SIZE_) + TILE_SIZE_ - 1;
            right_surfaces_.push_back(line);
            i++;
        } while (i < (int)tile.size() - 1);
    }
}

// Encuentra todas las rampas que suben hacia la izquierda
void Room::setLeftSlopes()
{
    // Recorre la habitación entera por filas buscando tiles de tipo t_slope_l
    std::vector<int> found;
    for (int i = 0; i < (int)tile_map_.size(); ++i)
    {
        if (getTile(i) == t_slope_l)
        {
            found.push_back(i);
        }
    }

    // El primer elemento es el inicio de una rampa. Se añade ese elemento y se buscan los siguientes,
    // que seran i + mapWidth + 1. Conforme se añaden se eliminan y se vuelve a escudriñar el vector de
    // tiles encontrados hasta que esté vacío

    while (found.size() > 0)
    {
        d_line_t line;
        line.x1 = (found[0] % MAP_WIDTH_) * TILE_SIZE_;
        line.y1 = (found[0] / MAP_WIDTH_) * TILE_SIZE_;
        int lookingFor = found[0] + MAP_WIDTH_ + 1;
        int lastOneFound = found[0];
        found.erase(found.begin());
        for (int i = 0; i < (int)found.size(); ++i)
        {
            if (found[i] == lookingFor)
            {
                lastOneFound = lookingFor;
                lookingFor += MAP_WIDTH_ + 1;
                found.erase(found.begin() + i);
                i--;
            }
        }
        line.x2 = ((lastOneFound % MAP_WIDTH_) * TILE_SIZE_) + TILE_SIZE_ - 1;
        line.y2 = ((lastOneFound / MAP_WIDTH_) * TILE_SIZE_) + TILE_SIZE_ - 1;
        left_slopes_.push_back(line);
    }
}

// Encuentra todas las rampas que suben hacia la derecha
void Room::setRightSlopes()
{
    // Recorre la habitación entera por filas buscando tiles de tipo t_slope_r
    std::vector<int> found;
    for (int i = 0; i < (int)tile_map_.size(); ++i)
    {
        if (getTile(i) == t_slope_r)
        {
            found.push_back(i);
        }
    }

    // El primer elemento es el inicio de una rampa. Se añade ese elemento y se buscan los siguientes,
    // que seran i + mapWidth - 1. Conforme se añaden se eliminan y se vuelve a escudriñar el vector de
    // tiles encontrados hasta que esté vacío

    while (found.size() > 0)
    {
        d_line_t line;
        line.x1 = ((found[0] % MAP_WIDTH_) * TILE_SIZE_) + TILE_SIZE_ - 1;
        line.y1 = (found[0] / MAP_WIDTH_) * TILE_SIZE_;
        int lookingFor = found[0] + MAP_WIDTH_ - 1;
        int lastOneFound = found[0];
        found.erase(found.begin());
        for (int i = 0; i < (int)found.size(); ++i)
        {
            if (found[i] == lookingFor)
            {
                lastOneFound = lookingFor;
                lookingFor += MAP_WIDTH_ - 1;
                found.erase(found.begin() + i);
                i--;
            }
        }
        line.x2 = (lastOneFound % MAP_WIDTH_) * TILE_SIZE_;
        line.y2 = ((lastOneFound / MAP_WIDTH_) * TILE_SIZE_) + TILE_SIZE_ - 1;
        right_slopes_.push_back(line);
    }
}

// Calcula las superficies automaticas
void Room::setAutoSurfaces()
{
    std::vector<int> tile;

    // Busca todos los tiles de tipo animado
    // Hay que recorrer la habitación por filas (excepto los de la primera fila)
    for (int i = MAP_WIDTH_; i < (int)tile_map_.size(); ++i)
    {
        if (getTile(i) == t_animated)
        {
            tile.push_back(i);

            // Si llega al final de la fila, introduce un separador
            if (i % MAP_WIDTH_ == MAP_WIDTH_ - 1)
            {
                tile.push_back(-1);
            }
        }
    }

    // Recorre el vector de tiles buscando tiles consecutivos para localizar las superficies
    if ((int)tile.size() > 0)
    {
        int i = 0;
        do
        {
            h_line_t line;
            line.x1 = (tile[i] % MAP_WIDTH_) * TILE_SIZE_;
            line.y = (tile[i] / MAP_WIDTH_) * TILE_SIZE_;
            int last_one = i;
            i++;

            if (i <= (int)tile.size() - 1)
            {
                while (tile[i] == tile[i - 1] + 1)
                {
                    last_one = i;
                    if (i == (int)tile.size() - 1)
                    {
                        break;
                    }
                    i++;
                }
            }

            line.x2 = ((tile[last_one] % MAP_WIDTH_) * TILE_SIZE_) + TILE_SIZE_ - 1;
            auto_surfaces_.push_back(line);
            if (i <= (int)tile.size() - 1)
            {
                if (tile[i] == -1)
                { // Si el siguiente elemento es un separador, hay que saltarlo
                    i++;
                }
            }
        } while (i < (int)tile.size() - 1);
    }
}

// Localiza todos los tiles animados de la habitación
void Room::setAnimatedTiles()
{
    // Recorre la habitación entera por filas buscando tiles de tipo t_animated
    for (int i = 0; i < (int)tile_map_.size(); ++i)
    {
        if (getTile(i) == t_animated)
        {
            // La i es la ubicación
            const int x = (i % MAP_WIDTH_) * TILE_SIZE_;
            const int y = (i / MAP_WIDTH_) * TILE_SIZE_;

            // TileMap[i] es el tile a poner
            const int xc = (tile_map_[i] % tile_set_width_) * TILE_SIZE_;
            const int yc = (tile_map_[i] / tile_set_width_) * TILE_SIZE_;

            AnimatedTile at;
            at.sprite = std::make_shared<Sprite>(texture_, x, y, 8, 8);
            at.sprite->setClip(xc, yc, 8, 8);
            at.x_orig = xc;
            animated_tiles_.push_back(at);
        }
    }
}

// Actualiza los tiles animados
void Room::updateAnimatedTiles()
{
    const int numFrames = 4;
    int offset = 0;
    if (auto_surface_direction_ == -1)
    {
        offset = ((counter_ / 3) % numFrames * TILE_SIZE_);
    }
    else
    {
        offset = ((numFrames - 1 - ((counter_ / 3) % numFrames)) * TILE_SIZE_);
    }

    for (auto &a : animated_tiles_)
    {
        SDL_Rect rect = a.sprite->getClip();
        rect.x = a.x_orig + offset;
        a.sprite->setClip(rect);
    }
}

// Pinta los tiles animados en pantalla
void Room::renderAnimatedTiles()
{
    for (auto a : animated_tiles_)
    {
        a.sprite->render();
    }
}

// Comprueba las colisiones
int Room::checkRightSurfaces(SDL_Rect *rect)
{
    for (auto s : right_surfaces_)
    {
        if (checkCollision(s, *rect))
        {
            return s.x;
        }
    }

    return -1;
}

// Comprueba las colisiones
int Room::checkLeftSurfaces(SDL_Rect *rect)
{
    for (auto s : left_surfaces_)
    {
        if (checkCollision(s, *rect))
        {
            return s.x;
        }
    }

    return -1;
}

// Comprueba las colisiones
int Room::checkTopSurfaces(SDL_Rect *rect)
{
    for (auto s : top_surfaces_)
    {
        if (checkCollision(s, *rect))
        {
            return s.y;
        }
    }

    return -1;
}

// Comprueba las colisiones
int Room::checkBottomSurfaces(SDL_Rect *rect)
{
    for (auto s : bottom_surfaces_)
    {
        if (checkCollision(s, *rect))
        {
            return s.y;
        }
    }

    return -1;
}

// Comprueba las colisiones
int Room::checkAutoSurfaces(SDL_Rect *rect)
{
    for (auto s : auto_surfaces_)
    {
        if (checkCollision(s, *rect))
        {
            return s.y;
        }
    }

    return -1;
}

// Comprueba las colisiones
bool Room::checkTopSurfaces(SDL_Point *p)
{
    for (auto s : top_surfaces_)
    {
        if (checkCollision(s, *p))
        {
            return true;
        }
    }

    return false;
}

// Comprueba las colisiones
bool Room::checkAutoSurfaces(SDL_Point *p)
{
    for (auto s : auto_surfaces_)
    {
        if (checkCollision(s, *p))
        {
            return true;
        }
    }

    return false;
}

// Comprueba las colisiones
int Room::checkLeftSlopes(const v_line_t *line)
{
    for (auto slope : left_slopes_)
    {
        const SDL_Point p = checkCollision(slope, *line);
        if (p.x != -1)
        {
            return p.y;
        }
    }

    return -1;
}

// Comprueba las colisiones
bool Room::checkLeftSlopes(SDL_Point *p)
{
    for (auto slope : left_slopes_)
    {
        if (checkCollision(*p, slope))
        {
            return true;
        }
    }

    return false;
}

// Comprueba las colisiones
int Room::checkRightSlopes(const v_line_t *line)
{
    for (auto slope : right_slopes_)
    {
        const SDL_Point p = checkCollision(slope, *line);
        if (p.x != -1)
        {
            return p.y;
        }
    }

    return -1;
}

// Comprueba las colisiones
bool Room::checkRightSlopes(SDL_Point *p)
{
    for (auto slope : right_slopes_)
    {
        if (checkCollision(*p, slope))
        {
            return true;
        }
    }

    return false;
}

// Obten la direccion de las superficies automaticas
int Room::getAutoSurfaceDirection()
{
    return auto_surface_direction_;
}

// Abre la jail para poder entrar
void Room::openTheJail()
{
    if (name_ == "THE JAIL")
    {
        // Elimina el último enemigo (Bry debe ser el ultimo enemigo definido en el fichero)
        enemies_.pop_back();

        // Abre las puertas
        const int tileA = 16 + (13 * 32);
        const int tileB = 16 + (14 * 32);
        tile_map_[tileA] = -1;
        tile_map_[tileB] = -1;
    }
}