#include "map.h"

// Constructor
Map::Map(std::string file, SDL_Renderer *renderer, Asset *asset)
{
    // Inicializa variables
    tile_size = 8;
    map_width = 40;
    map_height = 26;
    tileset_width = 32;

    // Copia los punteros a objetos
    this->asset = asset;
    this->renderer = renderer;

    // Crea los objetos
    texture_tile = new LTexture();
    load(file);
    loadTextureFromFile(texture_tile, asset->get(tileset_img), renderer);

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

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

// Destructor
Map::~Map()
{
    // Reclama la memoria utilizada por los objetos
    texture_tile->unload();
    delete texture_tile;

    SDL_DestroyTexture(map_texture);

    for (auto enemy : enemy_list)
    {
        delete enemy;
    }
    enemy_list.clear();
}

// Carga las variables desde un fichero
bool Map::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 [tilemap] se realiza el proceso de carga del fichero tmx
            if (line == "[tilemap]")
            {
                do
                {
                    std::getline(file, line);
                    if (line.find(".tmx") != std::string::npos)
                    {
                        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);
                                        pos = line.find("data encoding");
                                    } 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>")
                                        {
                                            std::stringstream ss(line);
                                            std::string tmp;
                                            while (getline(ss, tmp, ','))
                                            {
                                                tilemap.push_back(std::stoi(tmp));
                                            }
                                        }
                                    } while (line != "</data>");
                                }
                            }
                        }
                    }
                } while (line != "[/tilemap]");
            }

            // Si la linea contiene el texto [enemy] se realiza el proceso de carga de un enemigo
            else 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
                enemy_list.push_back(new Enemy(enemy));
            }

            // 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 Map::setVars(std::string var, std::string value)
{
    // Indicador de éxito en la asignación
    bool success = true;

    if (var == "tileset_img")
    {
        tileset_img = value;
    }
    else if (var == "room_up")
    {
        room_up = value;
    }
    else if (var == "room_down")
    {
        room_down = value;
    }
    else if (var == "room_left")
    {
        room_left = value;
    }
    else if (var == "room_right")
    {
        room_right = value;
    }
    else if (var == "")
    {
    }
    else
    {
        success = false;
    }

    return success;
}

// Asigna variables a una estructura enemy_t
bool Map::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) * tile_size;
    }
    else if (var == "y")
    {
        enemy->y = std::stof(value) * tile_size;
    }
    else if (var == "vx")
    {
        enemy->vx = std::stof(value);
    }
    else if (var == "vy")
    {
        enemy->vy = std::stof(value);
    }
    else if (var == "x1")
    {
        enemy->p1.x = std::stoi(value) * tile_size;
    }
    else if (var == "x2")
    {
        enemy->p2.x = std::stoi(value) * tile_size;
    }
    else if (var == "y1")
    {
        enemy->p1.y = std::stoi(value) * tile_size;
    }
    else if (var == "y2")
    {
        enemy->p2.y = std::stoi(value) * tile_size;
    }
    else if (var == "[/enemy]")
    {
    }
    else
    {
        success = false;
    }

    return success;
}

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

    // Dibuja el degradado de fondo
    const int num_lines = 208;

    const color_t color1 = {234, 171, 159};
    const color_t color2 = {144, 225, 231};

    for (int i = 0; i < num_lines; i++)
    {
        float step = ((float)i / (float)num_lines);
        int r = color1.r + ((color2.r - color1.r) * step);
        int g = color1.g + ((color2.g - color1.g) * step);
        int b = color1.b + ((color2.b - color1.b) * step);
        SDL_SetRenderDrawColor(renderer, r, g, b, 0xFF);
        SDL_RenderDrawLine(renderer, 0, i, 320, i);
    }

    // Dibuja el mapeado de tiles
    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++)
        {
            clip.x = ((tilemap[(y * map_width) + x] - 1) % tileset_width) * tile_size;
            clip.y = ((tilemap[(y * map_width) + x] - 1) / tileset_width) * tile_size;
            texture_tile->render(renderer, x * tile_size, y * tile_size, &clip);
        }

    // Dibuja el degradado del marcador
    int color = 105;
    for (int i = 208; i < 240; i++)
    {
        SDL_SetRenderDrawColor(renderer, 0x69, color, 0x69, 0xFF);
        SDL_RenderDrawLine(renderer, 0, i, 320, i);
        color--;
    }

    // Dibuja el marco del marcador
    // SDL_SetRenderDrawColor(renderer, 85, 50, 85, 0xFF);
    // SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0xFF);
    // SDL_Rect rect = {0, 208, 320, 32};
    // SDL_RenderDrawRect(renderer, &rect);
    // rect = {1, 209, 318, 30};
    // SDL_RenderDrawRect(renderer, &rect);

    // Vuelve a colocar el renderizador
    SDL_SetRenderTarget(renderer, nullptr);
}

// Dibuja el mapa en pantalla
void Map::render()
{
    // Dibuja la textura con el mapa en pantalla
    SDL_Rect rect = {0, 0, GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT};
    SDL_RenderCopy(renderer, map_texture, &rect, NULL);

    for (auto enemy : enemy_list)
    {
        enemy->render();
    }
}

// Actualiza todas las variables
void Map::update()
{
    for (auto enemy : enemy_list)
    {
        enemy->update();
    }
}

// Devuelve el tipo de tile que hay en un punto
e_tile_map Map::getTile(SDL_Point p)
{
    // Normalizamos los puntos para que no busque fuera del mapa
    const int x = std::max(getPlayArea(b_left), (std::min(p.x, getPlayArea(b_right) - 1)));
    const int y = std::max(getPlayArea(b_top), (std::min(p.y, getPlayArea(b_bottom) - 1)));
    
    // Calcula el tile
    const int tile = tilemap[((y / tile_size) * map_width) + (x / tile_size)];
    const int png_width = 32;

    // Las 8 primeras filas son tiles de fondo
    if (tile >= 0 && tile < 8 * png_width)
    {
        return nothing;
    }
    // De la fila 8 a la 15 hay tiles de muro
    else if (tile >= (8 * png_width) && tile < 16 * png_width)
    {
        return wall;
    }
    // A partir de la fila 16 son tiles atravesables
    else
    {
        return passable;
    }

    return nothing;
}

// Devuelve el valor de la variable
int Map::getTileSize()
{
    return tile_size;
}

// Devuelve el indice del tile correspondiente a un punto del mapa
int Map::getTileIndex(SDL_Point p)
{
    return tilemap[((p.y / tile_size) * map_width) + (p.x / tile_size)];
}

// Devuelve el valor de los bordes de la zona de juego
int Map::getPlayArea(e_border border)
{
    switch (border)
    {
    case b_top:
        return 0;
        break;

    case b_left:
        return 0;
        break;

    case b_right:
        return tile_size * map_width;
        break;

    case b_bottom:
        return tile_size * map_height;
        break;

    default:
        break;
    }

    return -1;
}

// Devuelve el nombre del fichero de la habitación en funcion del borde
std::string Map::getRoomFileName(e_border border)
{
    switch (border)
    {
    case b_top:
        return room_up;
        break;

    case b_left:
        return room_left;
        break;

    case b_right:
        return room_right;
        break;

    case b_bottom:
        return room_down;
        break;

    default:
        break;
    }

    return "";
}