#include "map.h"

// Constructor
Map::Map(std::string file, SDL_Renderer *renderer, Asset *asset, ItemTracker *itemTracker)
{
    // Inicializa variables
    tile_size = 8;
    map_width = 40;
    map_height = 26;
    name = file.substr(file.find_last_of("\\/") + 1);
    enemy_file = "";
    bgColor1 = bgColor2 = {0, 0, 0};

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

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

    // Crea las texturas para dibujar el mapa
    map_layer0 = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT);
    if (map_layer0 == NULL)
        printf("Error: map_layer0 could not be created!\nSDL Error: %s\n", SDL_GetError());

    map_layer1 = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT);
    if (map_layer1 == NULL)
        printf("Error: map_layer1 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_layer1);

    for (auto actor : actors)
    {
        delete actor;
    }
    actors.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\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 map_col_read = false;
                            bool map_l0_read = false;
                            bool map_l1_read = false;
                            while (std::getline(file2, line)) // Lee el fichero linea a linea
                            {
                                if (!map_l0_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
                                        map_l0_read = true;
                                        std::getline(file2, line);
                                        if (line != "</data>")
                                        {
                                            std::stringstream ss(line);
                                            std::string tmp;
                                            while (getline(ss, tmp, ','))
                                            {
                                                tilemap_l0.push_back(std::stoi(tmp));
                                            }
                                        }
                                    } while (line != "</data>");
                                }

                                if (!map_l1_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
                                        map_l1_read = true;
                                        std::getline(file2, line);
                                        if (line != "</data>")
                                        {
                                            std::stringstream ss(line);
                                            std::string tmp;
                                            while (getline(ss, tmp, ','))
                                            {
                                                tilemap_l1.push_back(std::stoi(tmp));
                                            }
                                        }
                                    } while (line != "</data>");
                                }

                                if (!map_col_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
                                        map_col_read = true;
                                        std::getline(file2, line);
                                        if (line != "</data>")
                                        {
                                            std::stringstream ss(line);
                                            std::string tmp;
                                            while (getline(ss, tmp, ','))
                                            {
                                                collisionmap.push_back(std::stoi(tmp));
                                            }
                                        }
                                    } while (line != "</data>");
                                }
                            }
                        }
                    }
                } while (line != "[/tilemap]");
            }

            // Si la linea contiene el texto [actor] se realiza el proceso de carga de los actores
            else if (line == "[actors]")
            {
                do
                {
                    std::getline(file, line);

                    if (line == "[moving platform]")
                    {
                        actor_t actor;
                        actor.asset = asset;
                        actor.renderer = renderer;
                        actor.name = a_moving_platform;
                        SDL_Point p1, p2;

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

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

                            // Procesa las dos subcadenas
                            if (!setActor(&actor, &p1, &p2, 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 != "[/moving platform]");

                        printf("** actor moving platform loaded\n\n");
                        actors.push_back(new ActorMovingPlatform(actor, p1, p2));
                    }

                    if (line == "[diamond]")
                    {
                        actor_t actor;
                        actor.asset = asset;
                        actor.renderer = renderer;
                        actor.name = a_diamond;
                        actor.vx = 0.0f;
                        actor.vy = 0.0f;
                        SDL_Point p1, p2;

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

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

                            // Procesa las dos subcadenas
                            if (!setActor(&actor, &p1, &p2, 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 != "[/diamond]");

                        // Comprueba si el actor no ha sido recogido previamente
                        if (!itemTracker->hasBeenPicked(name, {(int)actor.x, (int)actor.y}))
                        {
                            printf("** actor diamond loaded\n\n");
                            actors.push_back(new ActorDiamond(actor));
                        }
                    }

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

            // 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\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 == "bgColor1")
    {
        // Se introducen los valores separados por comas en un vector
        std::stringstream ss(value);
        std::string tmp;
        getline(ss, tmp, ',');
        bgColor1.r = std::stoi(tmp);
        getline(ss, tmp, ',');
        bgColor1.g = std::stoi(tmp);
        getline(ss, tmp, ',');
        bgColor1.b = std::stoi(tmp);
    }
    else if (var == "bgColor2")
    {
        // Se introducen los valores separados por comas en un vector
        std::stringstream ss(value);
        std::string tmp;
        getline(ss, tmp, ',');
        bgColor2.r = std::stoi(tmp);
        getline(ss, tmp, ',');
        bgColor2.g = std::stoi(tmp);
        getline(ss, tmp, ',');
        bgColor2.b = std::stoi(tmp);
    }
    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 == "enemy_file")
    {
        enemy_file = value;
    }
    else if (var == "")
    {
    }
    else
    {
        success = false;
    }

    return success;
}

// Asigna variables a una estructura enemy_t
bool Map::setActor(actor_t *actor, SDL_Point *p1, SDL_Point *p2, std::string var, std::string value)
{
    // Indicador de éxito en la asignación
    bool success = true;

    if (var == "tileset")
    {
        actor->tileset = value;
    }
    else if (var == "animation")
    {
        actor->animation = value;
    }
    else if (var == "width")
    {
        actor->w = std::stoi(value);
    }
    else if (var == "height")
    {
        actor->h = std::stoi(value);
    }
    else if (var == "x")
    {
        actor->x = std::stof(value) * tile_size;
    }
    else if (var == "y")
    {
        actor->y = std::stof(value) * tile_size;
    }
    else if (var == "vx")
    {
        actor->vx = std::stof(value);
    }
    else if (var == "vy")
    {
        actor->vy = std::stof(value);
    }
    else if (var == "x1")
    {
        p1->x = std::stoi(value) * tile_size;
    }
    else if (var == "x2")
    {
        p2->x = std::stoi(value) * tile_size;
    }
    else if (var == "y1")
    {
        p1->y = std::stoi(value) * tile_size;
    }
    else if (var == "y2")
    {
        p2->y = std::stoi(value) * tile_size;
    }
    else if ((var == "[/moving platform]") || (var == "[/diamond]"))
    {
    }
    else
    {
        success = false;
    }

    return success;
}

// Crea la textura con el mapeado de la habitación
void Map::fillMapTexture()
{
    // Crea variables
    SDL_Rect clip = {0, 0, tile_size, tile_size};

    // Rellena la capa 0
    {
        SDL_SetRenderTarget(renderer, map_layer0);
        SDL_SetTextureBlendMode(map_layer0, SDL_BLENDMODE_BLEND);
        SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF);
        SDL_RenderClear(renderer);

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

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

        // Dibuja el mapeado de tiles
        for (int y = 0; y < map_height; y++)
            for (int x = 0; x < map_width; x++)
            {
                // Resta uno porque Tiled almacena los indices empezando de 1 en vez de 0.
                // El problema es que los tiles vacios los pone como 0 y aqui pasan a ser -1
                // con lo que esta pintando desde fuera de la textura
                clip.x = ((tilemap_l0[(y * map_width) + x] - 1) % tileset_width) * tile_size;
                clip.y = ((tilemap_l0[(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--;
        }
    }

    // Rellena la capa 1
    {
        SDL_SetRenderTarget(renderer, map_layer1);
        SDL_SetTextureBlendMode(map_layer1, SDL_BLENDMODE_BLEND);
        SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0x00);
        SDL_RenderClear(renderer);

        // Dibuja el mapeado de tiles
        for (int y = 0; y < map_height; y++)
            for (int x = 0; x < map_width; x++)
            {
                // Resta uno porque Tiled almacena los indices empezando de 1 en vez de 0.
                // El problema es que los tiles vacios los pone como 0 y aqui pasan a ser -1
                // con lo que esta pintando desde fuera de la textura
                clip.x = ((tilemap_l1[(y * map_width) + x] - 1) % tileset_width) * tile_size;
                clip.y = ((tilemap_l1[(y * map_width) + x] - 1) / tileset_width) * tile_size;
                texture_tile->render(renderer, x * tile_size, y * tile_size, &clip);
            }
    }

    // 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_layer0, &rect, NULL);
    SDL_RenderCopy(renderer, map_layer1, &rect, NULL);

    for (auto actor : actors)
    {
        actor->render();
    }
}

// Actualiza todas las variables
void Map::update()
{
    for (auto actor : actors)
    {
        actor->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 = collisionmap[((y / tile_size) * map_width) + (x / tile_size)];

    // Las 8 primeras filas son tiles de fondo
    if (tile == 0)
    {
        return nothing;
    }

    else if (tile == 5627)
    {
        return wall;
    }

    else if (tile == 5628)
    {
        return passable;
    }

    return nothing;
}

// Devuelve el valor de la variable
int Map::getTileSize()
{
    return 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 "";
}

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

// Indica si hay colision con un actor a partir de un rectangulo
int Map::actorCollision(SDL_Rect &rect)
{
    int index = 0;
    for (auto actor : actors)
    {
        if (checkCollision(rect, actor->getCollider()))
        {
            return index;
        }
        index++;
    }

    return -1;
}

// Indica si hay colision con un actor a partir de un rectangulo
int Map::actorCollision(SDL_Point &p)
{
    int index = 0;
    for (auto actor : actors)
    {
        if (checkCollision(p, actor->getCollider()))
        {
            return index;
        }
        index++;
    }

    return -1;
}

// Devuelve el nombre del actor a pàrtir de un índice
int Map::getActorName(int index)
{
    if (index != -1)
    {
        return actors[index]->getName();
    }

    return -1;
}

// Devuelve el rectangulo de colisión
SDL_Rect Map::getActorCollider(int index)
{
    SDL_Rect rect = {0, 0, 0, 0};
    if (index != -1)
    {
        rect = actors[index]->getCollider();
    }

    return rect;
}

// Devuelve el desplazamiento relativo del actor
int Map::getActorIncX(int index)
{
    int shift = 0;
    if (index != -1)
    {
        shift = actors[index]->getIncX();
    }

    return shift;
}

// Elimina un actor
bool Map::deleteActor(int index)
{
    bool success = false;
    if (actors[index] != nullptr)
    {
        delete actors[index];
        success = true;
    }

    actors.erase(actors.begin() + index);
    return success;
}

// Coge un item
void Map::getItem(int index)
{
    const SDL_Rect r = getActorCollider(index);
    itemTracker->addItem(name, {r.x, r.y});
}

// Obtiene el valor de la variable
std::string Map::getEnemyFile()
{
    return enemy_file;
}