#include "ending2.h"
#include <algorithm>

// Constructor
Ending2::Ending2(SDL_Renderer *renderer, Screen *screen, Resource *resource, Asset *asset, options_t *options)
{
    // Copia los punteros
    this->renderer = renderer;
    this->screen = screen;
    this->resource = resource;
    this->asset = asset;
    this->options = options;

    // Reserva memoria para los punteros a objetos
    eventHandler = new SDL_Event();
    text = new Text(resource->getOffset("smb2.txt"), resource->getTexture("smb2.png"), renderer);
    music = JA_LoadMusic(asset->get("ending2.ogg").c_str());

    // Inicializa variables
    counterEnabled = false;
    preCounter = 0;
    postCounter = 0;
    postCounterEnabled = false;
    section.name = SECTION_PROG_ENDING2;
    section.subsection = 0;
    ticks = 0;
    ticksSpeed = 15;
    distSpriteText = 8;
    distSpriteSprite = 0;
    despSpeed = -0.2f;
    firstCol = GAMECANVAS_FIRST_QUARTER_X + (GAMECANVAS_WIDTH / 16);
    secondCol = GAMECANVAS_THIRD_QUARTER_X - (GAMECANVAS_WIDTH / 16);

    // Inicializa el vector de colores
    const std::vector<std::string> colorList = {"white", "yellow", "cyan", "green", "magenta", "red", "blue", "black"};
    for (auto cl : colorList)
    {
        colors.push_back(stringToColor(options->palette, cl));
    }

    // Cambia el color del borde
    screen->setBorderColor(stringToColor(options->palette, "black"));

    // Inicializa la lista de sprites
    iniSpriteList();

    // Carga todos los sprites desde una lista
    loadSprites();

    // Coloca los sprites en su sito
    placeSprites();

    // Crea los sprites con las texturas con los textos
    createSpriteTexts();

    // Crea los sprites con las texturas con los textos del final
    createTexts();
}

// Destructor
Ending2::~Ending2()
{
    // Libera la memoria de los objetos
    delete eventHandler;
    delete text;
    JA_DeleteMusic(music);

    deleteSprites();
    deleteSpriteTexts();
    deleteTexts();
}

// Actualiza el objeto
void Ending2::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 el manejador de eventos
        checkEventHandler();

        // Actualiza los contadores
        updateCounters();

        if (counterEnabled)
        {
            // Actualiza los sprites
            updateSprites();

            // Actualiza los sprites de texto
            updateTextSprites();

            // Actualiza los sprites de texto del final
            updateTexts();
        }

        // Actualiza el fade final
        updateFinalFade();

        // Actualiza el volumen de la musica
        updateMusicVolume();
    }
}

// Dibuja el final en pantalla
void Ending2::render()
{
    // Prepara para empezar a dibujar en la textura de juego
    screen->start();

    // Limpia la pantalla
    screen->clean(stringToColor(options->palette, "black"));

    // Dibuja los sprites
    renderSprites();

    // Dibuja los sprites con el texto
    renderSpriteTexts();

    // Dibuja los sprites con el texto del final
    renderTexts();

    const std::string txt = std::to_string(postCounter);
    // text->write(0, 192 - 8, txt);

    // Dibuja la cuadricula
    /*{
        SDL_SetRenderDrawColor(renderer, 128, 128, 128, 255);
        const int sw = maxSpriteWidth + 6;
        const int sh = maxSpriteHeight + 6;
        for (int i = 0; i < 256; i += sw)
        {
            SDL_RenderDrawLine(renderer, i, 0, i, 192);
        }
        for (int i = 0; i < 192; i += sh)
        {
            SDL_RenderDrawLine(renderer, 0, i, 255, i);
        }
    }*/

    {
        // Dibuja una trama arriba y abajo
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0xFF);
        for (int i = 0; i < 256; i += 2)
        {
            SDL_RenderDrawPoint(renderer, i + 0, 0);
            SDL_RenderDrawPoint(renderer, i + 1, 1);
            SDL_RenderDrawPoint(renderer, i + 0, 2);
            SDL_RenderDrawPoint(renderer, i + 1, 3);

            SDL_RenderDrawPoint(renderer, i, 4);
            SDL_RenderDrawPoint(renderer, i, 6);

            SDL_RenderDrawPoint(renderer, i + 0, 191);
            SDL_RenderDrawPoint(renderer, i + 1, 190);
            SDL_RenderDrawPoint(renderer, i + 0, 189);
            SDL_RenderDrawPoint(renderer, i + 1, 188);

            SDL_RenderDrawPoint(renderer, i, 187);
            SDL_RenderDrawPoint(renderer, i, 185);
        }
        // SDL_RenderDrawLine(renderer, 0, 1, 255, 1);
        // SDL_RenderDrawLine(renderer, 0, 3, 255, 3);
        // SDL_RenderDrawLine(renderer, 0, 188, 255, 188);
        // SDL_RenderDrawLine(renderer, 0, 190, 255, 190);
    }

    // Vuelca el contenido del renderizador en pantalla
    screen->blit();
}

// Comprueba el manejador de eventos
void Ending2::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;
        }

        // Comprueba las teclas que se han pulsado
        if ((eventHandler->type == SDL_KEYDOWN && eventHandler->key.repeat == 0) || (eventHandler->type == SDL_JOYBUTTONDOWN))
        {
            switch (eventHandler->key.keysym.scancode)
            {
            case SDL_SCANCODE_ESCAPE:
                section.name = SECTION_PROG_QUIT;
                break;

            case SDL_SCANCODE_B:
                screen->switchBorder();
                resource->reLoadTextures();
                break;

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

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

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

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

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

            case SDL_SCANCODE_F5:
                switchPalette();
                break;

            default:
                break;
            }
        }
    }
}

// Bucle principal
section_t Ending2::run()
{
    JA_PlayMusic(music);

    while (section.name == SECTION_PROG_ENDING2)
    {
        update();
        render();
    }

    JA_StopMusic();
    JA_SetVolume(128);

    return section;
}

// Actualiza los contadores
void Ending2::updateCounters()
{
    // Incrementa el contador
    if (preCounter < 200)
    {
        preCounter++;
    }
    else
    {
        counterEnabled = true;
    }

    if (postCounterEnabled)
    {
        postCounter++;
    }

    if (postCounter > 600)
    {
        section.name = SECTION_PROG_LOGO;
        section.subsection = SUBSECTION_LOGO_TO_INTRO;
    }
}

// Inicializa la lista de sprites
void Ending2::iniSpriteList()
{
    // Reinicia el vector
    spriteList.clear();

    // Añade los valores
    spriteList.push_back("bin");
    spriteList.push_back("diskette");
    spriteList.push_back("bird");
    spriteList.push_back("chip");
    spriteList.push_back("code");
    spriteList.push_back("paco");
    spriteList.push_back("printer");

    spriteList.push_back("bell");
    spriteList.push_back("dong");

    spriteList.push_back("amstrad_cs");
    spriteList.push_back("breakout");

    spriteList.push_back("arounder_fly");
    spriteList.push_back("arounder_stop");
    spriteList.push_back("arounder_walk");
    spriteList.push_back("arounders_door");
    spriteList.push_back("arounders_machine");

    spriteList.push_back("abad");
    spriteList.push_back("abad_bell");

    spriteList.push_back("bat");
    spriteList.push_back("batman_bell");
    spriteList.push_back("batman_fire");
    spriteList.push_back("batman");

    spriteList.push_back("demon");
    spriteList.push_back("heavy");
    spriteList.push_back("dimallas");
    spriteList.push_back("guitar");

    spriteList.push_back("jb_alien");
    spriteList.push_back("jb_human");

    spriteList.push_back("bry");
    spriteList.push_back("jailer");
    spriteList.push_back("jailer2");
    spriteList.push_back("jailer3");

    spriteList.push_back("lamp");
    spriteList.push_back("macaronni_ted");
    spriteList.push_back("congo");

    spriteList.push_back("matatunos");
    spriteList.push_back("tuno");

    spriteList.push_back("mummy");
    spriteList.push_back("sam");

    spriteList.push_back("qvoid");
    spriteList.push_back("sigmasua");

    spriteList.push_back("tv_panel");
    spriteList.push_back("tv");

    spriteList.push_back("spider");
    spriteList.push_back("shock");
    spriteList.push_back("wave");

    spriteList.push_back("player");
}

// Carga todos los sprites desde una lista
void Ending2::loadSprites()
{
    // Borra la memoria ocupada por los sprites
    deleteSprites();

    // Inicializa variables
    maxSpriteWidth = 0;
    maxSpriteHeight = 0;

    // Carga los sprites
    for (auto sl : spriteList)
    {
        sprites.push_back(new AnimatedSprite(renderer, resource->getAnimation(sl + ".ani")));
        maxSpriteWidth = std::max(sprites.back()->getAnimationClip(0, 0).w, maxSpriteWidth);
        maxSpriteHeight = std::max(sprites.back()->getAnimationClip(0, 0).h, maxSpriteHeight);
    }
}

// Actualiza los sprites
void Ending2::updateSprites()
{
    for (auto sprite : sprites)
    {
        sprite->update();
    }
}

// Actualiza los sprites de texto
void Ending2::updateTextSprites()
{
    for (auto sprite : spriteTexts)
    {
        sprite->update();
    }
}

// Actualiza los sprites de texto del final
void Ending2::updateTexts()
{
    if (texts.back()->getPosY() > GAMECANVAS_CENTER_Y)
    {
        for (auto sprite : texts)
        {
            sprite->update();
        }
    }
    else
    {
        postCounterEnabled = true;
    }
}

// Dibuja los sprites
void Ending2::renderSprites()
{
    const color_t color = stringToColor(options->palette, "red");
    for (auto sprite : sprites)
    {
        const bool a = sprite->getRect().y + sprite->getRect().h > 0;
        const bool b = sprite->getRect().y < GAMECANVAS_HEIGHT;
        if (a && b)
        {
            sprite->getTexture()->setColor(color.r, color.g, color.b);
            sprite->render();
        }
    }

    // Pinta el ultimo elemento de otro color
    const color_t c = stringToColor(options->palette, "white");
    sprites.back()->getTexture()->setColor(c.r, c.g, c.b);
    sprites.back()->render();
}

// Dibuja los sprites con el texto
void Ending2::renderSpriteTexts()
{
    const color_t color = stringToColor(options->palette, "white");
    for (auto sprite : spriteTexts)
    {
        const bool a = sprite->getRect().y + sprite->getRect().h > 0;
        const bool b = sprite->getRect().y < GAMECANVAS_HEIGHT;
        if (a && b)
        {
            sprite->getTexture()->setColor(color.r, color.g, color.b);
            sprite->render();
        }
    }
}

// Dibuja los sprites con el texto del final
void Ending2::renderTexts()
{
    for (auto sprite : texts)
    {
        const bool a = sprite->getRect().y + sprite->getRect().h > 0;
        const bool b = sprite->getRect().y < GAMECANVAS_HEIGHT;
        if (a && b)
        {
            sprite->render();
        }
    }
}

// Coloca los sprites en su sito
void Ending2::placeSprites()
{
    for (int i = 0; i < (int)sprites.size(); ++i)
    {
        const int x = i % 2 == 0 ? firstCol : secondCol;
        const int y = (i / 1) * (maxSpriteHeight + distSpriteText + text->getCharacterSize() + distSpriteSprite) + GAMECANVAS_HEIGHT + 40;
        const int w = sprites.at(i)->getAnimationClip(0, 0).w;
        const int h = sprites.at(i)->getAnimationClip(0, 0).h;
        const int dx = -(w / 2);
        const int dy = i % 1 == 0 ? maxSpriteHeight - h : (int)(maxSpriteHeight * 1.5f) - h;

        sprites.at(i)->setRect({x + dx, y + dy, w, h});
        sprites.at(i)->setVelY(despSpeed);
    }

    // Recoloca el último sprite, que es el del jugador
    const int w = sprites.back()->getAnimationClip(0, 0).w;
    const int x = GAMECANVAS_CENTER_X - (w / 2);
    const int y = sprites.back()->getPosY() + maxSpriteHeight * 2;
    sprites.back()->setPosX(x);
    sprites.back()->setPosY(y);
    sprites.back()->setCurrentAnimation("walk");
}

// Crea los sprites con las texturas con los textos
void Ending2::createSpriteTexts()
{
    // Borra la memoria ocupada por los sprites con las texturas de los textos
    deleteSpriteTexts();

    // Crea los sprites de texto a partir de la lista
    for (int i = 0; i < (int)spriteList.size(); ++i)
    {
        // Calcula constantes
        std::string txt = spriteList.at(i);
        std::replace(txt.begin(), txt.end(), '_', ' ');
        txt = txt == "player" ? "JAILDOCTOR" : txt; // Reemplaza el texto
        const int w = text->lenght(txt, 1);
        const int h = text->getCharacterSize();
        const int x = i % 2 == 0 ? firstCol : secondCol;
        const int dx = -(w / 2);
        const int y = sprites.at(i)->getPosY() + sprites.at(i)->getHeight() + distSpriteText;

        // Cambia la posición del último sprite
        const int X = (i == (int)spriteList.size() - 1) ? GAMECANVAS_CENTER_X - (w / 2) : x + dx;

        // Crea la textura
        Texture *texture = new Texture(renderer);
        texture->createBlank(renderer, w, h, SDL_TEXTUREACCESS_TARGET);
        texture->setAsRenderTarget(renderer);
        texture->setBlendMode(SDL_BLENDMODE_BLEND);
        text->write(0, 0, txt);

        // Crea el sprite
        MovingSprite *sprite = new MovingSprite(X, y, w, h, 0.0f, despSpeed, 0.0f, 0.0f, texture, renderer);
        spriteTexts.push_back(sprite);
    }
}

// Crea los sprites con las texturas con los textos del final
void Ending2::createTexts()
{
    // Borra la memoria ocupada por los sprites con las texturas de los textos del final
    deleteTexts();

    // Crea los primeros textos
    std::vector<std::string> list;
    list.push_back("STARRING");

    // Crea los sprites de texto a partir de la lista
    for (int i = 0; i < (int)list.size(); ++i)
    {
        // Calcula constantes
        const int w = text->lenght(list.at(i), 1);
        const int h = text->getCharacterSize();
        const int x = GAMECANVAS_CENTER_X;
        const int dx = -(w / 2);
        const int y = GAMECANVAS_HEIGHT + (text->getCharacterSize() * (i * 2));

        // Crea la textura
        Texture *texture = new Texture(renderer);
        texture->createBlank(renderer, w, h, SDL_TEXTUREACCESS_TARGET);
        texture->setAsRenderTarget(renderer);
        texture->setBlendMode(SDL_BLENDMODE_BLEND);
        text->write(0, 0, list.at(i));

        // Crea el sprite
        MovingSprite *sprite = new MovingSprite(x + dx, y, w, h, 0.0f, despSpeed, 0.0f, 0.0f, texture, renderer);
        texts.push_back(sprite);
    }

    // Crea los últimos textos
    // El primer texto va a continuación del ultimo spriteText
    const int start = spriteTexts.back()->getPosY() + text->getCharacterSize() * 15;
    list.clear();
    list.push_back("THANK YOU");
    list.push_back("FOR PLAYING!");

    // Crea los sprites de texto a partir de la lista
    for (int i = 0; i < (int)list.size(); ++i)
    {
        // Calcula constantes
        const int w = text->lenght(list.at(i), 1);
        const int h = text->getCharacterSize();
        const int x = GAMECANVAS_CENTER_X;
        const int dx = -(w / 2);
        const int y = start + (text->getCharacterSize() * (i * 2));

        // Crea la textura
        Texture *texture = new Texture(renderer);
        texture->createBlank(renderer, w, h, SDL_TEXTUREACCESS_TARGET);
        texture->setAsRenderTarget(renderer);
        texture->setBlendMode(SDL_BLENDMODE_BLEND);
        text->write(0, 0, list.at(i));

        // Crea el sprite
        MovingSprite *sprite = new MovingSprite(x + dx, y, w, h, 0.0f, despSpeed, 0.0f, 0.0f, texture, renderer);
        texts.push_back(sprite);
    }
}

// Borra la memoria ocupada por los sprites con las texturas de los textos
void Ending2::deleteSpriteTexts()
{
    for (auto sprite : spriteTexts)
    {
        delete sprite->getTexture();
        delete sprite;
    }
    spriteTexts.clear();
}

// Borra la memoria ocupada por los sprites
void Ending2::deleteSprites()
{
    for (auto sprite : sprites)
    {
        delete sprite;
    }
    sprites.clear();
}

// Borra la memoria ocupada por los sprites con las texturas de los textos del final
void Ending2::deleteTexts()
{
    for (auto text : texts)
    {
        delete text;
    }
    texts.clear();
}

// Actualiza el fade final
void Ending2::updateFinalFade()
{
    // La variable step va de 0 a 40 en el tramo de postCounter que va de 500 a 540. Al dividirlo por 40, va de 0.0f a 1.0f
    const float step = std::min(std::max(postCounter, 500) - 500, 40) / 40.0f;
    const int index = (colors.size() - 1) * step;

    for (auto t : texts)
    {
        t->getTexture()->setColor(colors.at(index).r, colors.at(index).g, colors.at(index).b);
    }
}

// Actualiza el volumen de la musica
void Ending2::updateMusicVolume()
{
    if (postCounter > 0)
    {
        const float step = (600.0f - postCounter) / 600.0f;
        const int volume = 128 * step;
        JA_SetVolume(volume);
    }
}

// Cambia la paleta
void Ending2::switchPalette()
{
    options->palette = (options->palette == p_zxspectrum) ? p_zxarne : p_zxspectrum;
}