/*
 * Copyright © 2012 Linaro Limited
 *
 * This file is part of the glmark2 OpenGL (ES) 2.0 benchmark.
 *
 * glmark2 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 3 of the License, or (at your option) any later
 * version.
 *
 * glmark2 is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * glmark2.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *  Alexandros Frantzis
 */
#include "options.h"
#include "scene.h"
#include "renderer.h"
#include "texture.h"
#include "shader-source.h"

TerrainRenderer::TerrainRenderer(const LibMatrix::vec2 &size,
                                 const LibMatrix::vec2 &repeat_overlay) :
    BaseRenderer(size), height_map_tex_(0), normal_map_tex_(0),
    specular_map_tex_(0), repeat_overlay_(repeat_overlay)
{
    create_mesh();
    init_textures();
    init_program();
}

TerrainRenderer::~TerrainRenderer()
{
    deinit_textures();
}

void
TerrainRenderer::render()
{
    make_current();
    glClearColor(0.825f, 0.7425f, 0.61875f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    program_.start();

    bind_textures();
    mesh_.render_vbo();

    program_.stop();

    update_mipmap();
}

void
TerrainRenderer::init_textures()
{
    /* Create textures */
    Texture::load("terrain-grasslight-512", &diffuse1_tex_,
            GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, 0);
    Texture::load("terrain-backgrounddetailed6", &diffuse2_tex_,
            GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, 0);
    Texture::load("terrain-grasslight-512-nm", &detail_tex_,
            GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, 0);

    /* Set REPEAT wrap mode */
    glBindTexture(GL_TEXTURE_2D, diffuse1_tex_);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    glBindTexture(GL_TEXTURE_2D, diffuse2_tex_);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    glBindTexture(GL_TEXTURE_2D, detail_tex_);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}


void
TerrainRenderer::init_program()
{
    ShaderSource vtx_shader(Options::data_path + "/shaders/terrain.vert");
    ShaderSource frg_shader(Options::data_path + "/shaders/terrain.frag");

    if (!Scene::load_shaders_from_strings(program_, vtx_shader.str(), frg_shader.str()))
        return;

    program_.start();
    /* Fog */
    program_["fogDensity"] = 0.00025f;
    program_["fogNear"] = 1.0f;
    program_["fogFar"] = 2000.0f;
    program_["fogColor"] = LibMatrix::vec3(0.825, 0.7425, 0.61875);

    /* Lights */
    program_["ambientLightColor"] = color_to_vec3(0xffffff);

    program_["pointLightColor[0]"] = color_to_vec3(0xffffff);
    program_["pointLightPosition[0]"] = LibMatrix::vec3(0.0, 0.0, 0.0);
    program_["pointLightDistance[0]"] = 0.0f;

    /* Textures */
    program_["tDiffuse1"] = 0;
    program_["tDiffuse2"] = 1;
    program_["tDetail"] = 2;
    program_["tNormal"] = 3;
    program_["tSpecular"] = 4;
    program_["tDisplacement"] = 5;

    program_["uNormalScale"] = 3.5f;

    program_["uDisplacementBias"] = 0.0f;
    program_["uDisplacementScale"] = 375.0f;

    program_["uDiffuseColor"] = color_to_vec3(0xffffff);
    program_["uSpecularColor"] = color_to_vec3(0xffffff);
    program_["uAmbientColor"] = color_to_vec3(0x888888);
    program_["uShininess"] = 30.0f;
    program_["uOpacity"] = 1.0f;

    program_["uRepeatOverlay"] = repeat_overlay_;

    program_["uOffset"] = LibMatrix::vec2(0.0, 0.0);

    std::vector<GLint> attrib_locations;
    attrib_locations.push_back(program_["position"].location());
    attrib_locations.push_back(program_["normal"].location());
    attrib_locations.push_back(program_["tangent"].location());
    attrib_locations.push_back(program_["uv"].location());
    mesh_.set_attrib_locations(attrib_locations);

    program_.stop();
}

void
TerrainRenderer::bind_textures()
{
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, diffuse1_tex_);

    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, diffuse2_tex_);

    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, detail_tex_);

    glActiveTexture(GL_TEXTURE3);
    glBindTexture(GL_TEXTURE_2D, normal_map_tex_);

    glActiveTexture(GL_TEXTURE4);
    glBindTexture(GL_TEXTURE_2D, specular_map_tex_);

    glActiveTexture(GL_TEXTURE5);
    glBindTexture(GL_TEXTURE_2D, height_map_tex_);
}

void
TerrainRenderer::deinit_textures()
{
    glDeleteTextures(1, &diffuse1_tex_);
    glDeleteTextures(1, &diffuse2_tex_);
    glDeleteTextures(1, &detail_tex_);
}

static void
grid_conf(Mesh &mesh, int x, int y, int n_x, int n_y,
          LibMatrix::vec3 &ul,
          LibMatrix::vec3 &ll,
          LibMatrix::vec3 &ur,
          LibMatrix::vec3 &lr)
{
    struct PlaneMeshVertex {
        LibMatrix::vec3 position;
        LibMatrix::vec2 texcoord;
        LibMatrix::vec3 normal;
        LibMatrix::vec3 tangent;
    };

    LibMatrix::vec2 uv_ul(static_cast<float>(x) / n_x,
            1.0 - static_cast<float>(y) / n_y);
    LibMatrix::vec2 uv_lr(static_cast<float>(x + 1) / n_x,
            1.0 - static_cast<float>(y + 1) / n_y);

    LibMatrix::vec3 normal(0.0, 0.0, 1.0);
    LibMatrix::vec3 tangent(1.0, 0.0, 0.0);

    PlaneMeshVertex cell_vertices[] = {
        {
            ll,
            LibMatrix::vec2(uv_ul.x(), uv_lr.y()),
            normal,
            tangent
        },
        {
            lr,
            LibMatrix::vec2(uv_lr.x(), uv_lr.y()),
            normal,
            tangent
        },
        {
            ur,
            LibMatrix::vec2(uv_lr.x(), uv_ul.y()),
            normal,
            tangent
        },
        {
            ul,
            LibMatrix::vec2(uv_ul.x(), uv_ul.y()),
            normal,
            tangent
        }
    };

    unsigned int vertex_index[] = {0, 1, 2, 0, 2, 3};

    for (size_t i = 0; i < sizeof(vertex_index) / sizeof(*vertex_index); i++) {
        PlaneMeshVertex& vertex = cell_vertices[vertex_index[i]];

        mesh.next_vertex();
        mesh.set_attrib(0, vertex.position);
        mesh.set_attrib(1, vertex.normal);
        mesh.set_attrib(2, vertex.tangent);
        mesh.set_attrib(3, vertex.texcoord);
    }
}

void
TerrainRenderer::create_mesh()
{
    std::vector<int> vertex_format;
    vertex_format.push_back(3);
    vertex_format.push_back(3);
    vertex_format.push_back(3);
    vertex_format.push_back(2);
    mesh_.set_vertex_format(vertex_format);

    mesh_.make_grid(256, 256, 6000, 6000, 0, grid_conf);
    mesh_.build_vbo();
}

