#include "graphics/postprocess.h"
#include <graphics/quad.h>
#include <util.h>

Postprocessor::Postprocessor(const std::shared_ptr<ResourceManager>& resourceManager)
{
    postProcessShader = resourceManager->getShaderByID(
        resourceManager->loadShader(
            "GL_postprocess", 
            "shaders/GL_postprocess.vert", 
            "shaders/GL_postprocess.frag")
        );
    // TODO: ADD FALLBACK SHADER!
    if (postProcessShader == nullptr) {
        LOG(ERROR, "Failed to load post processing shader! 'shaders/GL_postprocess.*", NULL);
        assert(postProcessShader != nullptr); // force crash
    }
    postProcessShader->use();
    postProcessShader->setIntArray("edge_kernel", 9, edge_kernel);
    postProcessShader->setFloatArray("blur_kernel", 9, blur_kernel);

    unsigned int uboIndex  = glGetUniformBlockIndex(postProcessShader->ID, "uPostProcess");
    if (uboIndex == GL_INVALID_INDEX) {
        LOG(ERROR, "Could not find 'uPostProcess'!", NULL);
    }
    unsigned int bindPoint = 1;
    glUniformBlockBinding(postProcessShader->ID, uboIndex, bindPoint);

    glGenBuffers(1, &UBO);
    glBindBuffer(GL_UNIFORM_BUFFER, UBO);
    glBufferData(GL_UNIFORM_BUFFER, sizeof(PostProcessData_t), NULL, GL_DYNAMIC_DRAW);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);

    glBindBufferBase(GL_UNIFORM_BUFFER, bindPoint, UBO);

    lastTime = SDL_GetTicks();
}

void Postprocessor::ApplyEffect(EFFECT effect, float intensity, float effectDuration, glm::vec3 colorModifer)
{
    postProcessData.curEffects |= effect;
    if ((effect & EFFECT::BLUR) != 0) {
        postProcessData.blurIntensity = intensity;
        postProcessData.blurDuration = effectDuration;
    } else if ((effect & EFFECT::SHAKE) != 0) {
        postProcessData.shakeIntensity = intensity;
        postProcessData.shakeDuration = effectDuration;
    }
    if ((effect & EFFECT::COLORMOD) != 0) {
        postProcessData.colorMod = colorModifer;
    }
}

void Postprocessor::RemoveAllEffects() 
{
    postProcessData.curEffects = 0;
    postProcessData.curEffects_hud = 0;
}

void Postprocessor::RemoveEffects(EFFECT effect)
{
    postProcessData.curEffects &= (postProcessData.curEffects ^ effect);
    postProcessData.curEffects_hud &= (postProcessData.curEffects_hud ^ effect);
}

void Postprocessor::applyPostProcess(bool worldOrHud)
{
    unsigned int curTime = SDL_GetTicks();
    postProcessData.curTime = curTime;
    postProcessData.dt = curTime - lastTime;

    tickEffectTime(worldOrHud);

    postProcessShader->use();
    postProcessShader->setBool("worldOrHud", worldOrHud);
    glBindBuffer(GL_UNIFORM_BUFFER, UBO);

    glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(PostProcessData_t), &postProcessData);

    glBindBuffer(GL_UNIFORM_BUFFER, 0);

    lastTime = curTime;
}

void Postprocessor::tickEffectTime(bool worldOrHud)
{
    auto subtractTime = [](float remaining, float dt){
        if (remaining == UTIL::INF_TIME) {
            return remaining;
        }
        else if ((remaining - 1.0f / dt) <= 0.0f)
            return 0.0f;
        return remaining - (1.0f / dt);
    };
    auto checkToRemove = [](float timeLeft) {
        return timeLeft != UTIL::INF_TIME && timeLeft <= 0.f; // checking if the duration is not INF_TIME and is leq than 0.0f
    };
    // !!! FIX THIS, IT WORKS BUT NOT A SCALABLE SOLUTION !!!
    float dt = static_cast<float>(postProcessData.dt);
    if (worldOrHud) {
        postProcessData.blurDuration_hud = subtractTime(postProcessData.blurDuration_hud, dt);
        postProcessData.shakeDuration_hud = subtractTime(postProcessData.shakeDuration_hud, dt);
        if (checkToRemove(postProcessData.blurDuration_hud)) {
            //RemoveEffects(BLUR);
        }
        if (checkToRemove(postProcessData.shakeDuration_hud)) {
            //RemoveEffects(SHAKE);
        }
    } else {
        postProcessData.blurDuration = subtractTime(postProcessData.blurDuration, dt);
        postProcessData.shakeDuration = subtractTime(postProcessData.shakeDuration, dt);
        if (checkToRemove(postProcessData.blurDuration)) {
            RemoveEffects(BLUR);
        }
        if (checkToRemove(postProcessData.shakeDuration)) {
            RemoveEffects(SHAKE);
        }
    }
}
