#include "game.h"

// Constructor
Game::Game(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input, Debug *debug)
{
    // Copia punteros
    this->renderer = renderer;
    this->asset = asset;
    this->screen = screen;
    this->input = input;
    this->debug = debug;
    this->debug->setEnabled(true);

    // Reserva memoria para los objetos
    scoreboard = new ScoreBoard(renderer, asset, &board);
    eventHandler = new SDL_Event();
    itemTracker = new ItemTracker();
    map = new Map(asset->get("01.map"), renderer, asset, itemTracker);
    player = new Player(renderer, asset, input, map);
    enemyEngine = new EnemyEngine(renderer, asset, player, map, asset->get(map->getEnemyFile()));
    music = JA_LoadMusic(asset->get("music_surface.ogg").c_str());

    // Crea la textura para dibujar los sprites
    spriteLayer = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, PLAY_AREA_WIDTH, PLAY_AREA_HEIGHT);
    if (spriteLayer == nullptr)
        printf("Error: map_layer0 could not be created!\nSDL Error: %s\n", SDL_GetError());
    SDL_SetTextureBlendMode(spriteLayer, SDL_BLENDMODE_BLEND);

    // Inicializa variables
    ticks = 0;
    ticksSpeed = 15;

    section.name = SECTION_PROG_GAME;
    section.subsection = SUBSECTION_GAME_PLAY;

    musicEnabled = !debug->getEnabled();

    board.diamonds = 0;
    board.lives = 5;
}

// Destructor
Game::~Game()
{
    SDL_DestroyTexture(spriteLayer);

    delete scoreboard;
    delete eventHandler;
    delete itemTracker;
    delete map;
    delete player;
    delete enemyEngine;
    JA_DeleteMusic(music);
}

// Bucle para el juego
section_t Game::run()
{
    JA_PlayMusic(music);
    if (!musicEnabled)
    {
        JA_PauseMusic();
    }

    while (section.name == SECTION_PROG_GAME)
    {
        // Sección juego jugando
        if (section.subsection == SUBSECTION_GAME_PLAY)
        {
            update();
            render();
        }
    }

    JA_StopMusic();

    return section;
}

// Actualiza el juego, las variables, comprueba la entrada, etc.
void Game::update()
{
    // Comprueba que la diferencia de ticks sea mayor a la velocidad del juego
    if (SDL_GetTicks() - ticks > ticksSpeed)
    {
        // Actualiza el contador de ticks
        ticks = SDL_GetTicks();

        // Comprueba los eventos de la cola
        checkEventHandler();

        // Comprueba si se ha pulsado alguna tecla
        if (input->checkAnyInput())
            scoreboard->reset();

        board.diamonds = player->diamonds;

        // Actualiza los objetos
        debug->clear();
        scoreboard->update();
        map->update();
        enemyEngine->update();
        player->update();
        checkScreenBorders();

        updateDebugInfo();
    }
}

// Pinta los objetos en pantalla
void Game::render()
{
    // Rellena las texturas
    fillSpriteTexture();
    scoreboard->fillTexture();

    // Prepara para dibujar el frame
    screen->start();
    screen->clean();

    // Dibuja la capa 0
    map->renderLayer0();

    // Dibuja la capa 1
    map->renderLayer1();

    // Dibuja la capa de sprites
    renderSprites();

    // Dibuja el marcador
    scoreboard->render();

    // Dibuja la información de debug
    renderDebugInfo();

    // Actualiza la pantalla
    screen->blit();
}

// Dibuja los sprites en la textura
void Game::fillSpriteTexture()
{
    // Cambia el puntero del renderizador a la textura y la limpia
    SDL_SetRenderTarget(renderer, spriteLayer);
    SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0x00);
    SDL_RenderClear(renderer);

    // Dibuja los sprites en la textura
    map->renderActors();
    enemyEngine->render();
    player->render();

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

// Dibuja los sprites
void Game::renderSprites()
{
    // Copia la textura al renderizador
    SDL_Rect rect = {PLAY_AREA_X, PLAY_AREA_Y, PLAY_AREA_WIDTH, PLAY_AREA_HEIGHT};
    SDL_RenderCopy(renderer, spriteLayer, NULL, &rect);
}

// Comprueba los eventos de la cola
void Game::checkEventHandler()
{
    // Comprueba los eventos que hay en la cola
    while (SDL_PollEvent(eventHandler) != 0)
    {
        // Evento de salida de la aplicación
        if (eventHandler->type == SDL_QUIT)
        {
            section.name = SECTION_PROG_QUIT;
            break;
        }
        else if ((eventHandler->type == SDL_KEYDOWN) and (eventHandler->key.repeat == 0))
        {
            switch (eventHandler->key.keysym.scancode)
            {
            case SDL_SCANCODE_ESCAPE:
                section.name = SECTION_PROG_QUIT;
                break;

            case SDL_SCANCODE_D:
                debug->switchEnabled();
                musicEnabled = !debug->getEnabled();
                musicEnabled ? JA_ResumeMusic() : JA_PauseMusic();
                break;

            case SDL_SCANCODE_M:
                musicEnabled = !musicEnabled;
                musicEnabled ? JA_ResumeMusic() : JA_PauseMusic();
                break;

            case SDL_SCANCODE_R:
                delete map;
                map = new Map(asset->get("01.map"), renderer, asset, itemTracker);
                delete player;
                player = new Player(renderer, asset, input, map);
                break;

            case SDL_SCANCODE_F:
                screen->switchVideoMode();
                reLoadTextures();
                break;

            case SDL_SCANCODE_F1:
                screen->setWindowSize(1);
                reLoadTextures();
                break;

            case SDL_SCANCODE_F2:
                screen->setWindowSize(2);
                reLoadTextures();
                break;

            case SDL_SCANCODE_F3:
                screen->setWindowSize(3);
                reLoadTextures();
                break;

            case SDL_SCANCODE_F4:
                screen->setWindowSize(4);
                reLoadTextures();
                break;

            default:
                break;
            }
        }
    }
}

// Pasa la información de debug
void Game::updateDebugInfo()
{
    debug->add("R - Reload player and map");
    debug->add("D - Toggle debug mode");
    debug->add(std::to_string((int)player->sprite->getPosX()) + "," + std::to_string((int)player->sprite->getPosY()) + "," + std::to_string((int)player->sprite->getWidth()) + "," + std::to_string((int)player->sprite->getHeight()));
    debug->add("VY " + std::to_string(player->vy) + " " + std::to_string(player->jumpStrenght));
    debug->add("VX " + std::to_string(player->vx));
    debug->add("jump_pressed " + std::to_string(player->jumpPressed));
    debug->add("isOnFloor " + std::to_string(player->isOnFloor()));
    debug->add("getTile(" + std::to_string(player->underFeet[0].x) + "," + std::to_string(player->underFeet[0].y) + ") = " + std::to_string(player->map->getTile(player->underFeet[0])));
    debug->add("state " + std::to_string(player->state));
    debug->add(map->getName() + " (" + map->getRoomFileName(b_top) + ", " + map->getRoomFileName(b_right) + ", " + map->getRoomFileName(b_bottom) + ", " + map->getRoomFileName(b_left) + ")");
    debug->add("hookedOn = " + std::to_string(player->hookedOnMovingPlatform));
    debug->add("DIAMONDS = " + std::to_string(player->diamonds));
}

// Pone la información de debug en pantalla
void Game::renderDebugInfo()
{
    if (!debug->getEnabled())
    {
        return;
    }

    // Pinta la rejilla
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 16);
    for (int i = 0; i < 240; i += 8)
    {
        SDL_RenderDrawLine(renderer, 0, i, 320, i);
    }
    for (int i = 0; i < 320; i += 8)
    {
        SDL_RenderDrawLine(renderer, i, 0, i, 240);
    }

    // Pinta mascaras
    SDL_SetRenderDrawColor(renderer, 0, 255, 0, 192);
    SDL_Rect rect = player->sprite->getRect();
    SDL_RenderFillRect(renderer, &rect);

    // Pinta el texto
    debug->setPos({1, 1});
    debug->render();
}

// Cambia el mapa
bool Game::changeMap(std::string file)
{
    bool success = false;
    // En las habitaciones los limites tienen la cadena del fichero o un 0 en caso de no limitar con nada
    if (file != "0")
    { // Verifica que exista el fichero que se va a cargar
        if (asset->get(file) != "")
        {
            // Elimina la habitación actual y crea un objeto nuevo a partir del fichero
            delete map;
            map = new Map(asset->get(file), renderer, asset, itemTracker);

            // Elimina el gestor de enemigos y crea uno nuevo a partir de un fichero
            delete enemyEngine;
            enemyEngine = new EnemyEngine(renderer, asset, player, map, asset->get(map->getEnemyFile()));

            success = true;
        }
    }

    return success;
}

// Comprueba si el jugador está en el borde y se ha de cambiar el mapa
void Game::checkScreenBorders()
{
    if (player->isOnScreenBorder())
    {
        if (changeMap(map->getRoomFileName(player->getBorder())))
        {
            player->setMap(map);
            player->switchBorders();
        }
    }
}

// Recarga las texturas
void Game::reLoadTextures()
{
    map->reLoadTextures();
    player->reLoadTextures();
    scoreboard->reLoadTexture();
}