#include "map.h"

// Constructor
Map::Map(std::string file, SDL_Renderer *renderer, Asset *asset, ItemTracker *itemTracker)
{
    // Inicializa variables
    tileSize = 8;
    mapWidth = 40;
    mapHeight = 30;
    name = file.substr(file.find_last_of("\\/") + 1);
    enemyFile = "";
    bgColor1 = bgColor2 = {0, 0, 0};
    bgScroll = false;
    counter = 0;

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

    // Crea las texturas para dibujar el mapa
    mapLayerBG = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, PLAY_AREA_WIDTH, PLAY_AREA_HEIGHT);
    if (mapLayerBG == NULL)
    {
        printf("Error: mapLayer0 could not be created!\nSDL Error: %s\n", SDL_GetError());
    }
    SDL_SetTextureBlendMode(mapLayerBG, SDL_BLENDMODE_BLEND);

    mapLayer0 = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, PLAY_AREA_WIDTH, PLAY_AREA_HEIGHT);
    if (mapLayer0 == NULL)
    {
        printf("Error: mapLayer0 could not be created!\nSDL Error: %s\n", SDL_GetError());
    }
    SDL_SetTextureBlendMode(mapLayer0, SDL_BLENDMODE_BLEND);

    mapLayer1 = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, PLAY_AREA_WIDTH, PLAY_AREA_HEIGHT);
    if (mapLayer1 == NULL)
    {
        printf("Error: mapLayer1 could not be created!\nSDL Error: %s\n", SDL_GetError());
    }
    SDL_SetTextureBlendMode(mapLayer1, SDL_BLENDMODE_BLEND);

    // Crea los objetos
    loadMapFile(file);
    textureTile = new LTexture(renderer, asset->get(tileset));
    tilesetWidth = textureTile->getWidth() / tileSize;

    loadMapTileFile(asset->get(tileMapFile));
}

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

    SDL_DestroyTexture(mapLayerBG);
    SDL_DestroyTexture(mapLayer0);
    SDL_DestroyTexture(mapLayer1);

    for (auto actor : actors)
    {
        delete actor;
    }
    actors.clear();
}

// Carga las variables desde un fichero de mapa
bool Map::loadMapFile(std::string file_path)
{
    const 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 [actor] se realiza el proceso de carga de los actores
            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());
                            }

                        } 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());
                            }

                        } 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());
                }
            }
        }

        // 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());
        return false;
    }

    return true;
}

// Lee la matriz de tiles desde un fichero tmx a un vector
std::vector<int> Map::readTilesFromFile(std::ifstream &file)
{
    std::vector<int> tilemap;
    std::string line;

    // Salta una linea y lee la siguiente
    std::getline(file, line);
    std::getline(file, line);
    while (line != "</data>")
    { // Procesa lineas mientras haya
        std::stringstream ss(line);
        std::string tmp;
        while (getline(ss, tmp, ','))
        {
            tilemap.push_back(std::stoi(tmp));
        }

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

    return tilemap;
}

// Carga las variables y texturas desde un fichero de mapa de tiles
bool Map::loadMapTileFile(std::string file_path)
{
    std::vector<int> tilemap;

    const 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))
        { // Lee el fichero linea a linea
            if (line.find("name=\"estatico\"") != std::string::npos)
            {
                tilemap = readTilesFromFile(file);
                fillGradientTexture(*mapLayerBG);
                fillMapTexture(*mapLayerBG, tilemap, false);
                tilemap.clear();
            }

            else if (line.find("name=\"fondo\"") != std::string::npos)
            {
                tilemap = readTilesFromFile(file);
                fillMapTexture(*mapLayer0, tilemap, true);
                tilemap.clear();
            }

            else if (line.find("name=\"mapa\"") != std::string::npos)
            {
                tilemap = readTilesFromFile(file);
                fillMapTexture(*mapLayer1, tilemap, true);
                tilemap.clear();
            }

            else if (line.find("name=\"colisiones\"") != std::string::npos)
            {
                collisionmap = readTilesFromFile(file);
            }
        }

        // Cierra el fichero
        printf("Closing file %s\n\n", filename.c_str());
        file.close();
    }

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

    return true;
}

// Asigna variables a partir de dos cadenas
bool Map::setVars(std::string var, std::string value)
{
    if (var == "tilemap")
    {
        tileMapFile = value;
    }

    else if (var == "tileset")
    {
        tileset = 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 == "bgScroll")
    {
        bgScroll = stringToBool(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 == "enemyFile")
    {
        enemyFile = value;
    }

    else if (var == "")
    {
    }

    else
    {
        return false;
    }

    return true;
}

// 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)
{
    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) * tileSize;
    }

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

    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) * tileSize;
    }

    else if (var == "x2")
    {
        p2->x = std::stoi(value) * tileSize;
    }

    else if (var == "y1")
    {
        p1->y = std::stoi(value) * tileSize;
    }

    else if (var == "y2")
    {
        p2->y = std::stoi(value) * tileSize;
    }

    else if ((var == "[/moving platform]") || (var == "[/diamond]"))
    {
    }

    else
    {
        return false;
    }

    return true;
}
// Pinta el degradado en la textura
void Map::fillGradientTexture(SDL_Texture &layer)
{
    // Cambia el puntero del renderizador a la textura
    SDL_SetRenderTarget(renderer, &layer);

    // Dibuja el degradado de fondo
    const float numLines = PLAY_AREA_BOTTOM - PLAY_AREA_TOP;

    for (int i = PLAY_AREA_TOP; i < PLAY_AREA_BOTTOM; ++i)
    {
        float step = ((float)i / numLines);
        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, PLAY_AREA_LEFT, i, PLAY_AREA_RIGHT, i);
    }

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

// Crea la textura con el mapeado de la habitación
void Map::fillMapTexture(SDL_Texture &layer, std::vector<int> tilemap, bool clean)
{
    // Crea variables
    SDL_Rect clip = {0, 0, tileSize, tileSize};

    // Cambia el puntero del renderizador a la textura
    SDL_SetRenderTarget(renderer, &layer);

    // Limpia la textura
    if (clean)
    {
        SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0x00);
        SDL_RenderClear(renderer);
    }

    // Dibuja el mapeado de tiles
    for (int y = 0; y < mapHeight; ++y)
        for (int x = 0; x < mapWidth; ++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[(y * mapWidth) + x] - 1) % tilesetWidth) * tileSize;
            clip.y = ((tilemap[(y * mapWidth) + x] - 1) / tilesetWidth) * tileSize;
            textureTile->render(renderer, x * tileSize, y * tileSize, &clip);
        }

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

// Dibuja todos los elementos del mapa
void Map::render()
{
    renderLayerBG();
    renderLayer0();
    renderLayer1();
    renderActors();
}

// Dibuja la capa BG
void Map::renderLayerBG()
{
    // Dibuja la textura con el mapa en pantalla
    SDL_RenderCopy(renderer, mapLayerBG, nullptr, nullptr);
}

// Dibuja la capa 0
void Map::renderLayer0()
{
    // Dibuja la textura con el mapa en pantalla
    if (bgScroll)
    {
        const int offset = PLAY_AREA_WIDTH - ((counter / 20) % PLAY_AREA_WIDTH);
        SDL_Rect src1 = {PLAY_AREA_X, PLAY_AREA_Y, offset, PLAY_AREA_HEIGHT};
        SDL_Rect dst1 = {PLAY_AREA_X + PLAY_AREA_WIDTH - offset, PLAY_AREA_Y, offset, PLAY_AREA_HEIGHT};

        SDL_Rect src2 = {PLAY_AREA_X + offset, PLAY_AREA_Y, PLAY_AREA_WIDTH - offset, PLAY_AREA_HEIGHT};
        SDL_Rect dst2 = {PLAY_AREA_X, PLAY_AREA_Y, PLAY_AREA_WIDTH - offset, PLAY_AREA_HEIGHT};

        SDL_RenderCopy(renderer, mapLayer0, &src1, &dst1);
        SDL_RenderCopy(renderer, mapLayer0, &src2, &dst2);
    }
    else
    {
        SDL_RenderCopy(renderer, mapLayer0, nullptr, nullptr);
    }
}

// Dibuja la capa 1
void Map::renderLayer1()
{
    // Dibuja la textura con el mapa en pantalla
    SDL_RenderCopy(renderer, mapLayer1, nullptr, nullptr);
}

// Dibuja los actores
void Map::renderActors()
{
    for (auto actor : actors)
    {
        actor->render();
    }
}

// Actualiza todas las variables
void Map::update()
{
    counter++;

    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 / tileSize) * mapWidth) + (x / tileSize)];

    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 tileSize;
}

// 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 tileSize * mapWidth;
        break;

    case b_bottom:
        return tileSize * mapHeight;
        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 roomUp;
        break;

    case b_left:
        return roomLeft;
        break;

    case b_right:
        return roomRight;
        break;

    case b_bottom:
        return roomDown;
        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 enemyFile;
}

// Recarga las texturas
void Map::reLoadTextures()
{
    textureTile->reLoad();
}