//
// viewporteffect.cpp
//
// Copyright © 2020 Celestia Development Team. All rights reserved.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

#include "viewporteffect.h"

#include <array>

#include "framebuffer.h"
#include "render.h"
#include "warpmesh.h"

namespace gl = celestia::gl;
namespace util = celestia::util;

static const Renderer::PipelineState ps;

bool ViewportEffect::preprocess(Renderer* renderer, FramebufferObject* fbo)
{
    return fbo->bind();
}

bool ViewportEffect::prerender(Renderer* renderer, FramebufferObject* fbo, FramebufferObject* dst)
{
    // For renderbuffer MSAA (desktop GL / GLES3), blit the MSAA color buffer into
    // the resolve texture before switching to the destination framebuffer
    if (!fbo->resolve())
        return false;

    if (!dst->bind())
        return false;

    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    return true;
}

bool ViewportEffect::distortXY(float &x, float &y)
{
    return true;
}

PassthroughViewportEffect::PassthroughViewportEffect(StaticShader shaderName,
                                                     bool needsFloatSource) :
    m_shaderName(shaderName),
    m_needsFloatSource(needsFloatSource)
{
}

bool PassthroughViewportEffect::render(Renderer* renderer, FramebufferObject* fbo, int width, int height)
{
    auto *prog = renderer->getShaderManager().getShader(m_shaderName);
    if (prog == nullptr)
        return false;

    initialize();

    prog->use();
    prog->samplerParam("tex") = 0;
    glBindTexture(GL_TEXTURE_2D, fbo->colorTexture());
    renderer->setPipelineState(ps);
    vo.draw();
    glBindTexture(GL_TEXTURE_2D, 0);

    return true;
}

void PassthroughViewportEffect::initialize()
{
    if (initialized)
        return;
    initialized = true;

    static constexpr std::array quadVertices = {
        // positions   // texCoords
        -1.0f,  1.0f,  0.0f, 1.0f,
        -1.0f, -1.0f,  0.0f, 0.0f,
         1.0f, -1.0f,  1.0f, 0.0f,

        -1.0f,  1.0f,  0.0f, 1.0f,
         1.0f, -1.0f,  1.0f, 0.0f,
         1.0f,  1.0f,  1.0f, 1.0f
    };

    vo = gl::VertexObject(gl::VertexObject::Primitive::Triangles);
    bo = gl::Buffer(gl::Buffer::TargetHint::Array, quadVertices, gl::Buffer::BufferUsage::StaticDraw);

    vo.setCount(6);
    vo.addVertexBuffer(
        bo,
        CelestiaGLProgram::VertexCoordAttributeIndex,
        2,
        gl::VertexObject::DataType::Float,
        false,
        4 * sizeof(float),
        0);
    vo.addVertexBuffer(
        bo,
        CelestiaGLProgram::TextureCoord0AttributeIndex,
        2,
        gl::VertexObject::DataType::Float,
        false,
        4 * sizeof(float),
        2 * sizeof(float));
}

WarpMeshViewportEffect::WarpMeshViewportEffect(std::unique_ptr<WarpMesh>&& _mesh) :
    mesh(std::move(_mesh))
{
}

WarpMeshViewportEffect::~WarpMeshViewportEffect() = default;

bool WarpMeshViewportEffect::prerender(Renderer* renderer, FramebufferObject* fbo, FramebufferObject* dst)
{
    if (mesh == nullptr)
        return false;

    return ViewportEffect::prerender(renderer, fbo, dst);
}

bool WarpMeshViewportEffect::render(Renderer* renderer, FramebufferObject* fbo, int width, int height)
{
    auto *prog = renderer->getShaderManager().getShader(StaticShader::WarpMesh);
    if (prog == nullptr)
        return false;

    initialize();

    prog->use();
    prog->samplerParam("tex") = 0;
    prog->floatParam("screenRatio") = (float)height / width;
    glBindTexture(GL_TEXTURE_2D, fbo->colorTexture());
    renderer->setPipelineState(ps);
    vo.draw();
    glBindTexture(GL_TEXTURE_2D, 0);

    return true;
}

void WarpMeshViewportEffect::initialize()
{
    if (initialized)
        return;
    initialized = true;

    vo = gl::VertexObject(gl::VertexObject::Primitive::Triangles);
    bo = gl::Buffer(gl::Buffer::TargetHint::Array);

    mesh->setUpVertexObject(vo, bo);
}

bool WarpMeshViewportEffect::distortXY(float &x, float &y)
{
    if (mesh == nullptr)
        return false;

    float u;
    float v;
    if (!mesh->mapVertex(x * 2.0f, y * 2.0f, u, v))
        return false;

    x = u / 2.0f;
    y = v / 2.0f;
    return true;
}
