/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
#include "GlowingScene.h"
#include "BlurMeshNode.h"

#include <Trace.h>

#include <graphics/PerspectiveMeshNode.h>
#include <graphics/PerspectiveProgram.h>
#include <graphics/Program.h>
#include <graphics/GLUtils.h>
#include <graphics/Mesh.h>
#include <graphics/ProgramNode.h>
#include <graphics/TransformationNode.h>

GlowingScene::GlowingScene(int width, int height) :
        Scene(width, height), mFboId(0), mMainProgram(NULL), mBlurProgram(NULL) {
    mFboWidth = GLUtils::roundUpToSmallestPowerOf2(width);
    mFboHeight = GLUtils::roundUpToSmallestPowerOf2(height);
    mFboRatio = mFboWidth / mFboHeight;
    mFboModelMatrix = setUpModelMatrix();
    mFboViewMatrix = setUpViewMatrix();
    mFboProjectionMatrix = setUpProjectionMatrix(mFboWidth, mFboHeight);
}

bool GlowingScene::setUpContext() {
    if (!Scene::setUpContext()) {
        return false;
    }
    // Create a fbo
    glGenFramebuffers(1, &mFboId);
    return true;
}

bool GlowingScene::setUpPrograms() {
    // Main Program
    const char* vertex = GLUtils::openTextFile("vertex/perspective");
    const char* fragment = GLUtils::openTextFile("fragment/perspective");
    if (vertex == NULL || fragment == NULL) {
        return false;
    }
    GLuint programId = GLUtils::createProgram(&vertex, &fragment);
    delete[] vertex;
    delete[] fragment;
    if (programId == 0) {
        return false;
    }
    mMainProgram = new PerspectiveProgram(programId);
    // Blur Program
    vertex = GLUtils::openTextFile("vertex/blur");
    fragment = GLUtils::openTextFile("fragment/blur");
    if (vertex == NULL || fragment == NULL) {
        return false;
    }
    programId = GLUtils::createProgram(&vertex, &fragment);
    delete[] vertex;
    delete[] fragment;
    if (programId == 0) {
        return false;
    }
    mBlurProgram = new Program(programId);
    return true;
}

Matrix* GlowingScene::setUpModelMatrix() {
    return new Matrix();
}

Matrix* GlowingScene::setUpViewMatrix() {
    // Position the eye in front of the origin.
    float eyeX = 0.0f;
    float eyeY = 0.0f;
    float eyeZ = 10.0f;

    // Look at the origin
    float centerX = 0.0f;
    float centerY = 0.0f;
    float centerZ = 0.0f;

    // Set up vector.
    float upX = 0.0f;
    float upY = 1.0f;
    float upZ = 0.0f;

    // Set the view matrix.
    return Matrix::newLookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ);
}

Matrix* GlowingScene::setUpProjectionMatrix(float width, float height) {
    // Create a new perspective projection matrix. The height will stay the same
    // while the width will vary as per aspect ratio.
    float ratio = width / height;
    float left = -ratio;
    float right = ratio;
    float bottom = -1.0f;
    float top = 1.0f;
    float near = 8.0f;
    float far = 12.0f;

    return Matrix::newFrustum(left, right, bottom, top, near, far);
}

bool GlowingScene::setUpTextures() {
    SCOPED_TRACE();
    mTextureIds.push_back(GLUtils::genTexture(mWidth, mHeight, 0)); // fbo
    mTextureIds.push_back(GLUtils::genTexture(mWidth, mHeight, 0)); // tmp1
    mTextureIds.push_back(GLUtils::genTexture(mWidth, mHeight, 0)); // tmp2
    mTextureIds.push_back(GLUtils::loadTexture("texture/arc.png"));
    return true;
}

bool GlowingScene::setUpMeshes() {
    SCOPED_TRACE();
    mMeshes.push_back(GLUtils::loadMesh("mesh/plane.cob"));
    mMeshes.push_back(GLUtils::loadMesh("mesh/arc.cob"));
    return true;
}

bool GlowingScene::tearDown() {
    SCOPED_TRACE();
    if (mMainProgram != NULL) {
        delete mMainProgram;
        mMainProgram = NULL;
    }
    if (mBlurProgram != NULL) {
        delete mBlurProgram;
        mBlurProgram = NULL;
    }
    if (mFboId != 0) {
        glDeleteFramebuffers(1, &mFboId);
        mFboId = 0;
    }
    delete mFboModelMatrix;
    mFboModelMatrix = NULL;
    delete mFboViewMatrix;
    mFboViewMatrix = NULL;
    delete mFboProjectionMatrix;
    mFboProjectionMatrix = NULL;
    return Scene::tearDown();
}

bool GlowingScene::updateSceneGraphs(int frame) {
    // To render the mesh to the FBO
    ProgramNode* lightSceneGraph = new ProgramNode(*mMainProgram);
    mSceneGraphs.push_back(lightSceneGraph);
    MeshNode* meshNode = new PerspectiveMeshNode(mMeshes[1], mTextureIds[3]);
    lightSceneGraph->addChild(meshNode);

    // To blur the image
    ProgramNode* blurSceneGraph = new ProgramNode(*mBlurProgram);
    mSceneGraphs.push_back(blurSceneGraph);
    meshNode = new BlurMeshNode(mMeshes[0], mTextureIds[0], mTextureIds[1], mTextureIds[2],
            mFboWidth, mFboHeight);
    blurSceneGraph->addChild(meshNode);

    // Blur To screen
    ProgramNode* glowSceneGraph = new ProgramNode(*mMainProgram);
    mSceneGraphs.push_back(glowSceneGraph);
    Matrix* transformMatrix = Matrix::newScale(mFboRatio, 1.0f, 1.0f);
    TransformationNode* transformNode = new TransformationNode(transformMatrix);
    glowSceneGraph->addChild(transformNode);
    meshNode = new PerspectiveMeshNode(mMeshes[0], mTextureIds[2]);
    transformNode->addChild(meshNode);
    return true;
}

bool GlowingScene::draw() {
    SCOPED_TRACE();
    glBindFramebuffer(GL_FRAMEBUFFER, mFboId); // Use FBO
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextureIds[0], 0);
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        return false;
    }
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClear (GL_COLOR_BUFFER_BIT);
    glViewport(0, 0, mFboWidth, mFboHeight);
    mFboModelMatrix->identity();
    mSceneGraphs[0]->drawProgram(*mFboModelMatrix, *mFboViewMatrix, *mFboProjectionMatrix); // Mesh
    mFboModelMatrix->identity();
    mSceneGraphs[1]->drawProgram(*mFboModelMatrix, *mFboViewMatrix, *mFboProjectionMatrix); // Blur

    glBindFramebuffer(GL_FRAMEBUFFER, 0); // Use Screen
    glViewport(0, 0, mWidth, mHeight);
    Scene::drawSceneGraph(2); // Blur to Screen
    return true;
}
