/*
 * Copyright 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.
 */

//--------------------------------------------------------------------------------
// TeapotRenderer.cpp
// Render a teapot
//--------------------------------------------------------------------------------
//--------------------------------------------------------------------------------
// Include files
//--------------------------------------------------------------------------------
#include "TeapotRenderer.h"

//--------------------------------------------------------------------------------
// Teapot model data
//--------------------------------------------------------------------------------
#include "teapot.inl"

//--------------------------------------------------------------------------------
// Ctor
//--------------------------------------------------------------------------------
TeapotRenderer::TeapotRenderer()
{

}

//--------------------------------------------------------------------------------
// Dtor
//--------------------------------------------------------------------------------
TeapotRenderer::~TeapotRenderer()
{
    Unload();
}

void TeapotRenderer::Init()
{
    //Settings
    glFrontFace( GL_CCW );

    //Load shader
    LoadShaders( &shader_param_, "Shaders/VS_ShaderPlain.vsh",
            "Shaders/ShaderPlain.fsh" );

    //Create Index buffer
    num_indices_ = sizeof(teapotIndices) / sizeof(teapotIndices[0]);
    glGenBuffers( 1, &ibo_ );
    glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, ibo_ );
    glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof(teapotIndices), teapotIndices,
            GL_STATIC_DRAW );
    glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );

    //Create VBO
    num_vertices_ = sizeof(teapotPositions) / sizeof(teapotPositions[0]) / 3;
    int32_t iStride = sizeof(TEAPOT_VERTEX);
    int32_t iIndex = 0;
    TEAPOT_VERTEX* p = new TEAPOT_VERTEX[num_vertices_];
    for( int32_t i = 0; i < num_vertices_; ++i )
    {
        p[i].pos[0] = teapotPositions[iIndex];
        p[i].pos[1] = teapotPositions[iIndex + 1];
        p[i].pos[2] = teapotPositions[iIndex + 2];

        p[i].normal[0] = teapotNormals[iIndex];
        p[i].normal[1] = teapotNormals[iIndex + 1];
        p[i].normal[2] = teapotNormals[iIndex + 2];
        iIndex += 3;
    }
    glGenBuffers( 1, &vbo_ );
    glBindBuffer( GL_ARRAY_BUFFER, vbo_ );
    glBufferData( GL_ARRAY_BUFFER, iStride * num_vertices_, p, GL_STATIC_DRAW );
    glBindBuffer( GL_ARRAY_BUFFER, 0 );

    delete[] p;

    UpdateViewport();
    mat_model_ = ndk_helper::Mat4::Translation( 0, 0, -15.f );

    ndk_helper::Mat4 mat = ndk_helper::Mat4::RotationX( M_PI / 3 );
    mat_model_ = mat * mat_model_;
}

void TeapotRenderer::UpdateViewport()
{
    //Init Projection matrices
    int32_t viewport[4];
    glGetIntegerv( GL_VIEWPORT, viewport );
    float fAspect = (float) viewport[2] / (float) viewport[3];

    const float CAM_NEAR = 5.f;
    const float CAM_FAR = 10000.f;
    bool bRotate = false;
    mat_projection_ = ndk_helper::Mat4::Perspective( fAspect, 1.f, CAM_NEAR, CAM_FAR );
}

void TeapotRenderer::Unload()
{
    if( vbo_ )
    {
        glDeleteBuffers( 1, &vbo_ );
        vbo_ = 0;
    }

    if( ibo_ )
    {
        glDeleteBuffers( 1, &ibo_ );
        ibo_ = 0;
    }

    if( shader_param_.program_ )
    {
        glDeleteProgram( shader_param_.program_ );
        shader_param_.program_ = 0;
    }
}

void TeapotRenderer::Update( float fTime )
{
    const float CAM_X = 0.f;
    const float CAM_Y = 0.f;
    const float CAM_Z = 700.f;

    mat_view_ = ndk_helper::Mat4::LookAt( ndk_helper::Vec3( CAM_X, CAM_Y, CAM_Z ),
            ndk_helper::Vec3( 0.f, 0.f, 0.f ), ndk_helper::Vec3( 0.f, 1.f, 0.f ) );

    if( camera_ )
    {
        camera_->Update();
        mat_view_ = camera_->GetTransformMatrix() * mat_view_
                * camera_->GetRotationMatrix() * mat_model_;
    }
    else
    {
        mat_view_ = mat_view_ * mat_model_;
    }
}

void TeapotRenderer::Render()
{
    //
    // Feed Projection and Model View matrices to the shaders
    ndk_helper::Mat4 mat_vp = mat_projection_ * mat_view_;

    // Bind the VBO
    glBindBuffer( GL_ARRAY_BUFFER, vbo_ );

    int32_t iStride = sizeof(TEAPOT_VERTEX);
    // Pass the vertex data
    glVertexAttribPointer( ATTRIB_VERTEX, 3, GL_FLOAT, GL_FALSE, iStride,
            BUFFER_OFFSET( 0 ) );
    glEnableVertexAttribArray( ATTRIB_VERTEX );

    glVertexAttribPointer( ATTRIB_NORMAL, 3, GL_FLOAT, GL_FALSE, iStride,
            BUFFER_OFFSET( 3 * sizeof(GLfloat) ) );
    glEnableVertexAttribArray( ATTRIB_NORMAL );

    // Bind the IB
    glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, ibo_ );

    glUseProgram( shader_param_.program_ );

    TEAPOT_MATERIALS material = { { 1.0f, 0.5f, 0.5f }, { 1.0f, 1.0f, 1.0f, 10.f }, {
            0.1f, 0.1f, 0.1f }, };

    //Update uniforms
    glUniform4f( shader_param_.material_diffuse_, material.diffuse_color[0],
            material.diffuse_color[1], material.diffuse_color[2], 1.f );

    glUniform4f( shader_param_.material_specular_, material.specular_color[0],
            material.specular_color[1], material.specular_color[2],
            material.specular_color[3] );
    //
    //using glUniform3fv here was troublesome
    //
    glUniform3f( shader_param_.material_ambient_, material.ambient_color[0],
            material.ambient_color[1], material.ambient_color[2] );

    glUniformMatrix4fv( shader_param_.matrix_projection_, 1, GL_FALSE, mat_vp.Ptr() );
    glUniformMatrix4fv( shader_param_.matrix_view_, 1, GL_FALSE, mat_view_.Ptr() );
    glUniform3f( shader_param_.light0_, 100.f, -200.f, -600.f );

    glDrawElements( GL_TRIANGLES, num_indices_, GL_UNSIGNED_SHORT, BUFFER_OFFSET(0) );

    glBindBuffer( GL_ARRAY_BUFFER, 0 );
    glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
}

bool TeapotRenderer::LoadShaders( SHADER_PARAMS* params,
        const char* strVsh,
        const char* strFsh )
{
    GLuint program;
    GLuint vert_shader, frag_shader;
    char *vert_shader_pathname, *frag_shader_pathname;

    // Create shader program
    program = glCreateProgram();
    LOGI( "Created Shader %d", program );

    // Create and compile vertex shader
    if( !ndk_helper::shader::CompileShader( &vert_shader, GL_VERTEX_SHADER, strVsh ) )
    {
        LOGI( "Failed to compile vertex shader" );
        glDeleteProgram( program );
        return false;
    }

    // Create and compile fragment shader
    if( !ndk_helper::shader::CompileShader( &frag_shader, GL_FRAGMENT_SHADER, strFsh ) )
    {
        LOGI( "Failed to compile fragment shader" );
        glDeleteProgram( program );
        return false;
    }

    // Attach vertex shader to program
    glAttachShader( program, vert_shader );

    // Attach fragment shader to program
    glAttachShader( program, frag_shader );

    // Bind attribute locations
    // this needs to be done prior to linking
    glBindAttribLocation( program, ATTRIB_VERTEX, "myVertex" );
    glBindAttribLocation( program, ATTRIB_NORMAL, "myNormal" );
    glBindAttribLocation( program, ATTRIB_UV, "myUV" );

    // Link program
    if( !ndk_helper::shader::LinkProgram( program ) )
    {
        LOGI( "Failed to link program: %d", program );

        if( vert_shader )
        {
            glDeleteShader( vert_shader );
            vert_shader = 0;
        }
        if( frag_shader )
        {
            glDeleteShader( frag_shader );
            frag_shader = 0;
        }
        if( program )
        {
            glDeleteProgram( program );
        }

        return false;
    }

    // Get uniform locations
    params->matrix_projection_ = glGetUniformLocation( program, "uPMatrix" );
    params->matrix_view_ = glGetUniformLocation( program, "uMVMatrix" );

    params->light0_ = glGetUniformLocation( program, "vLight0" );
    params->material_diffuse_ = glGetUniformLocation( program, "vMaterialDiffuse" );
    params->material_ambient_ = glGetUniformLocation( program, "vMaterialAmbient" );
    params->material_specular_ = glGetUniformLocation( program, "vMaterialSpecular" );

    // Release vertex and fragment shaders
    if( vert_shader )
        glDeleteShader( vert_shader );
    if( frag_shader )
        glDeleteShader( frag_shader );

    params->program_ = program;
    return true;
}

bool TeapotRenderer::Bind( ndk_helper::TapCamera* camera )
{
    camera_ = camera;
    return true;
}