#include "room.h"
#include <iostream>
#include <fstream>
#include <sstream>

// Constructor
Room::Room(std::string file_path, SDL_Renderer *renderer, Screen *screen, Asset *asset, ItemTracker *itemTracker, int *items, Debug *debug)
{
    // Copia los punteros a objetos
    this->renderer = renderer;
    this->asset = asset;
    this->screen = screen;
    this->itemTracker = itemTracker;
    this->itemsPicked = items;
    this->debug = debug;

    // Crea los objetos
    load(file_path);
    texture = new LTexture(renderer, asset->get(tileset));
    itemSound = JA_LoadSound(asset->get("item.wav").c_str());

    // Crea la textura para el mapa de tiles de la habitación
    mapTexture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT);
    if (mapTexture == NULL)
        printf("Error: mapTexture could not be created!\nSDL Error: %s\n", SDL_GetError());

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

    // Establece el color del borde
    screen->setBorderColor(borderColor);

    // Inicializa variables
    tileSize = 8;
    mapWidth = 32;
    mapHeight = 16;
    tilesetWidth = 20;
}

// Destructor
Room::~Room()
{
    // Reclama la memoria utilizada por los objetos
    delete texture;
    JA_DeleteSound(itemSound);
    SDL_DestroyTexture(mapTexture);

    for (auto enemy : enemies)
    {
        delete enemy;
    }

    for (auto item : items)
    {
        delete item;
    }
}

// Carga las variables desde un fichero
bool Room::load(std::string _file_path)
{
    // Indicador de éxito en la carga
    bool success = true;

    std::string filename = _file_path.substr(_file_path.find_last_of("\\/") + 1);
    std::string line;
    std::ifstream file(_file_path);

    // El fichero se puede abrir
    if (file.good())
    {
        // Procesa el fichero linea a linea
        printf("Reading file %s\n", filename.c_str());
        while (std::getline(file, line))
        {
            // Si la linea contiene el texto [enemy] se realiza el proceso de carga de un enemigo
            if (line == "[enemy]")
            {
                enemy_t enemy;
                enemy.asset = asset;
                enemy.renderer = renderer;

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

                    // Encuentra la posición del caracter '='
                    int pos = line.find("=");
                    // Procesa las dos subcadenas
                    if (!setEnemy(&enemy, line.substr(0, pos), line.substr(pos + 1, line.length())))
                    {
                        printf("Warning: file %s\n, unknown parameter \"%s\"\n", filename.c_str(), line.substr(0, pos).c_str());
                        success = false;
                    }
                } while (line != "[/enemy]");

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

            // Si la linea contiene el texto [tilemap] se realiza el proceso de carga del fichero tmx
            else if (line == "[tilemap]")
            {
                // printf("Loading tilemap...\n");
                do
                {
                    std::getline(file, line);
                    if (line.find(".tmx") != std::string::npos)
                    {
                        // printf("Reading file %s\n", asset->get(line).c_str());
                        std::ifstream file2(asset->get(line)); // Abre el fichero tmx
                        if (file2.good())
                        {
                            bool data_read = false;
                            while (std::getline(file2, line)) // Lee el fichero linea a linea
                            {
                                if (!data_read)
                                { // Lee lineas hasta que encuentre donde empiezan los datos del mapa
                                    int pos = 0;
                                    do
                                    {
                                        std::getline(file2, line);
                                        // printf("parsing: %s\n", line.c_str());

                                        pos = line.find("data encoding");
                                        // printf("pos: %i\n", pos);

                                    } while (pos == std::string::npos);

                                    do
                                    { // Se introducen los valores separados por comas en un vector
                                        data_read = true;
                                        std::getline(file2, line);
                                        if (line != "</data>")
                                        {
                                            // printf("data: %s\n", line.c_str());
                                            std::stringstream ss(line);
                                            std::string tmp;
                                            while (getline(ss, tmp, ','))
                                            {
                                                // printf("tile: %s\n", tmp.c_str());
                                                tilemap.push_back(std::stoi(tmp));
                                            }
                                        }
                                    } while (line != "</data>");
                                }
                            }
                        }
                    }
                } while (line != "[/tilemap]");
            }

            // Si la linea contiene el texto [item] se realiza el proceso de carga de un item
            else if (line == "[item]")
            {
                item_t item;
                item.asset = asset;
                item.renderer = renderer;
                item.counter = 0;

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

                    // Encuentra la posición del caracter '='
                    int pos = line.find("=");
                    // Procesa las dos subcadenas
                    if (!setItem(&item, line.substr(0, pos), line.substr(pos + 1, line.length())))
                    {
                        printf("Warning: file %s\n, unknown parameter \"%s\"\n", filename.c_str(), line.substr(0, pos).c_str());
                        success = false;
                    }
                } while (line != "[/item]");

                // Añade el item al vector de items
                const SDL_Point itemPos = {item.x, item.y};
                if (!itemTracker->hasBeenPicked(name, itemPos))
                {
                    items.push_back(new Item(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
                if (!setVars(line.substr(0, pos), line.substr(pos + 1, line.length())))
                {
                    printf("Warning: file %s, unknown parameter \"%s\"\n", filename.c_str(), line.substr(0, pos).c_str());
                    success = false;
                }
            }
        }

        // Cierra el fichero
        printf("Closing file %s\n", filename.c_str());
        file.close();
    }
    // El fichero no se puede abrir
    else
    {
        printf("Warning: Unable to open %s file\n", filename.c_str());
        success = false;
    }

    return success;
}

// Asigna variables a partir de dos cadenas
bool Room::setVars(std::string var, std::string value)
{
    // Indicador de éxito en la asignación
    bool success = true;

    if (var == "name")
    {
        name = value;
    }

    else if (var == "bgColor")
    {
        bgColor = stringToColor(value);
    }

    else if (var == "border")
    {
        borderColor = stringToColor(value);
    }

    else if (var == "tileset")
    {
        tileset = value;
    }

    else if (var == "roomUp")
    {
        roomUp = value;
    }

    else if (var == "roomDown")
    {
        roomDown = value;
    }

    else if (var == "roomLeft")
    {
        roomLeft = value;
    }

    else if (var == "roomRight")
    {
        roomRight = value;
    }

    else if (var == "tilemap")
    {
        // Se introducen los valores separados por comas en un vector
        std::stringstream ss(value);
        std::string tmp;
        while (getline(ss, tmp, ','))
        {
            tilemap.push_back(std::stoi(tmp));
        }
    }

    else if (var == "")
    {
    }

    else
    {
        success = false;
    }

    return success;
}

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

    if (var == "tileset")
    {
        enemy->tileset = value;
    }

    else if (var == "animation")
    {
        enemy->animation = value;
    }

    else if (var == "width")
    {
        enemy->w = std::stof(value);
    }

    else if (var == "height")
    {
        enemy->h = std::stof(value);
    }

    else if (var == "x")
    {
        enemy->x = std::stof(value) * BLOCK;
    }

    else if (var == "y")
    {
        enemy->y = std::stof(value) * BLOCK;
    }

    else if (var == "vx")
    {
        enemy->vx = std::stof(value);
    }

    else if (var == "vy")
    {
        enemy->vy = std::stof(value);
    }

    else if (var == "x1")
    {
        enemy->x1 = std::stoi(value) * BLOCK;
    }

    else if (var == "x2")
    {
        enemy->x2 = std::stoi(value) * BLOCK;
    }

    else if (var == "y1")
    {
        enemy->y1 = std::stoi(value) * BLOCK;
    }

    else if (var == "y2")
    {
        enemy->y2 = std::stoi(value) * BLOCK;
    }

    else if (var == "color")
    {
        enemy->color = stringToColor(value);
    }

    else if (var == "[/enemy]")
    {
    }

    else
    {
        success = false;
    }

    return success;
}

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

    if (var == "tileset")
    {
        item->tileset = value;
    }

    else if (var == "counter")
    {
        item->counter = std::stoi(value);
    }

    else if (var == "x")
    {
        item->x = std::stof(value) * BLOCK;
    }

    else if (var == "y")
    {
        item->y = std::stof(value) * BLOCK;
    }

    else if (var == "tile")
    {
        item->tile = std::stof(value);
    }

    else if (var == "[/item]")
    {
    }

    else
    {
        success = false;
    }

    return success;
}

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

// Devuelve el color de la habitación
color_t Room::getBGColor()
{
    return bgColor;
}

// Crea la textura con el mapeado de la habitación
void Room::fillMapTexture()
{
    SDL_SetRenderTarget(renderer, mapTexture);
    SDL_SetTextureBlendMode(mapTexture, SDL_BLENDMODE_BLEND);
    SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0x00);
    SDL_RenderClear(renderer);

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

    SDL_Rect clip = {0, 0, 8, 8};
    for (int y = 0; y < 16; y++)
        for (int x = 0; x < 32; x++)
        {
            clip.x = ((tilemap[(y * 32) + x] - 1) % 20) * 8;
            clip.y = ((tilemap[(y * 32) + x] - 1) / 20) * 8;
            texture->render(renderer, x * 8, y * 8, &clip);
        }

    SDL_SetRenderTarget(renderer, nullptr);
}

// Dibuja el mapa en pantalla
void Room::renderMap()
{
    SDL_Rect rect = {0, 0, GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT};

    // Dibuja la textura con el mapa en pantalla
    SDL_RenderCopy(renderer, mapTexture, &rect, NULL);
}

// 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()
{
    for (auto enemy : enemies)
    {
        enemy->update();
    }

    for (auto item : 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 roomUp;
        break;

    case BORDER_BOTTOM:
        return roomDown;
        break;

    case BORDER_RIGHT:
        return roomRight;
        break;

    case BORDER_LEFT:
        return roomLeft;
        break;

    default:
        break;
    }
    return "";
}

// Devuelve el tipo de tile que hay en ese pixel
tile_e Room::getTile(SDL_Point point)
{
    const int maxTile = mapWidth * mapHeight;
    const int pos = ((point.y / tileSize) * mapWidth) + (point.x / tileSize);
    tile_e tile = t_empty;

    if (pos < maxTile)
    {
        // Las filas 0-7 son de tiles t_wall
        if ((tilemap[pos] > 0) && (tilemap[pos] < 8 * tilesetWidth))
        {
            return t_wall;
        }

        // La fila 8 es de tiles t_slope_r
        else if ((tilemap[pos] >= 8 * tilesetWidth) && (tilemap[pos] < 9 * tilesetWidth))
        {
            return t_slope_r;
        }

        // La fila 9 es de tiles t_slope_l
        else if ((tilemap[pos] >= 9 * tilesetWidth) && (tilemap[pos] < 10 * tilesetWidth))
        {
            return t_slope_l;
        }

        // Las filas 10-14 son de tiles t_passable
        if ((tilemap[pos] >= 10 * tilesetWidth) && (tilemap[pos] < 15 * tilesetWidth))
        {
            return t_passable;
        }
    }

    return tile;
}

// 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)
{
    bool collision = false;
    for (int i = 0; i < items.size(); i++)
    {
        if (checkCollision(rect, items[i]->getCollider()))
        {
            itemTracker->addItem(name, items[i]->getPos());
            delete items[i];
            items.erase(items.begin() + i);
            JA_PlaySound(itemSound);
            *itemsPicked = *itemsPicked + 1;
            collision = true;
        }
    }

    return collision;
}

// Recarga la textura
void Room::reLoadTexture()
{
    texture->reLoad();
    fillMapTexture();
    for (auto enemy : enemies)
    {
        enemy->reLoadTexture();
    }

    for (auto item : items)
    {
        item->reLoadTexture();
    }
}

// Obten el tamaño del tile
int Room::getTileSize()
{
    return 8;
}

// 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 / tileSize) * tileSize) + tileSize;
    printf("base %i\n", base);
    debug->add("BASE = " + std::to_string(base));

    // Calcula cuanto se ha entrado en el tile horizontalmente
    const int pos = (p.x % tileSize); // esto da un valor entre 0 y 7
    printf("pos %i\n", base);
    debug->add("POS = " + std::to_string(pos));

    // Se resta a la base la cantidad de pixeles pos en funcion de la rampa
    if (slope == t_slope_r)
    {
        base -= pos + 1;
        printf("base_R %i\n", base);
        debug->add("BASE_R = " + std::to_string(base));
    }
    else
    {
        base -= (tileSize - pos);
        printf("base_L %i\n", base);
        debug->add("BASE_L = " + std::to_string(base));
    }

    return base;
}