/*
 * Copyright © 2010-2011 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 (glmark2)
 *  Jesse Barker
 */
#include "scene.h"
#include "log.h"
#include "mat.h"
#include "options.h"
#include "stack.h"
#include "shader-source.h"
#include "util.h"
#include "gl-headers.h"
#include <cmath>

/***********************
 * Wave implementation *
 ***********************/

/**
 * A callback used to set up the grid by the Wave class.
 * It is called for each "quad" of the grid.
 */
static void
wave_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)
{
    // These parameters are unused in this instance of a virtual callback
    // function.
    static_cast<void>(x);
    static_cast<void>(y);
    static_cast<void>(n_x);
    static_cast<void>(n_y);

    /*
     * Order matters here, so that Wave::vertex_length_index() can work.
     * Vertices of the triangles at index i that belong to length index i
     * are even, those that belong to i + 1 are odd.
     */
    const LibMatrix::vec3* t[] = {
        &ll, &ur, &ul, &ur, &ll, &lr
    };

    for (int i = 0; i < 6; i++) {
        mesh.next_vertex();
        /*
         * Set the vertex position and the three vertex positions
         * of the triangle this vertex belongs to.
         */
        mesh.set_attrib(0, *t[i]);
        mesh.set_attrib(1, *t[3 * (i / 3)]);
        mesh.set_attrib(2, *t[3 * (i / 3) + 1]);
        mesh.set_attrib(3, *t[3 * (i / 3) + 2]);
    }
}

/**
 * Renders a grid mesh modulated by a sine wave
 */
class WaveMesh
{
public:
    /**
     * Creates a wave mesh.
     *
     * @param length the total length of the grid (in model coordinates)
     * @param width the total width of the grid (in model coordinates)
     * @param nlength the number of length-wise grid subdivisions
     * @param nwidth the number of width-wise grid subdivisions
     * @param wavelength the wave length as a proportion of the length
     * @param duty_cycle the duty cycle ()
     */
    WaveMesh(double length, double width, size_t nlength, size_t nwidth,
             double wavelength, double duty_cycle) :
        length_(length), width_(width), nlength_(nlength), nwidth_(nwidth),
        wave_k_(2 * M_PI / (wavelength * length)),
        wave_period_(2.0 * M_PI / wave_k_),
        wave_full_period_(wave_period_ / duty_cycle),
        wave_velocity_(0.1 * length), displacement_(nlength + 1)
    {
        create_program();
        create_mesh();
    }


    ~WaveMesh() { reset(); }

    /**
     * Updates the state of a wave mesh.
     *
     * @param elapsed the time elapsed since the beginning of the rendering
     */
    void update(double elapsed)
    {
        std::vector<std::vector<float> >& vertices(mesh_.vertices());

        /* Figure out which length index ranges need update */
        std::vector<std::pair<size_t, size_t> > ranges;

        for (size_t n = 0; n <= nlength_; n++) {
            double d(displacement(n, elapsed));

            if (d != displacement_[n]) {
                if (ranges.size() > 0 && ranges.back().second == n - 1) {
                    ranges.back().second = n;
                }
                else {
                    ranges.push_back(
                            std::pair<size_t, size_t>(n > 0 ? n - 1 : 0, n)
                            );
                }
            }

            displacement_[n] = d;
        }

        /* Update the vertex data of the changed ranges */
        for (std::vector<std::pair<size_t, size_t> >::iterator iter = ranges.begin();
             iter != ranges.end();
             iter++)
        {
            /* First vertex of length index range */
            size_t vstart(iter->first * nwidth_ * 6 + (iter->first % 2));
            /*
             * First vertex not included in the range. We should also update all
             * vertices of triangles touching index i.
             */
            size_t vend((iter->second + (iter->second < nlength_)) * nwidth_ * 6);

            for (size_t v = vstart; v < vend; v++) {
                size_t vt = 3 * (v / 3);
                vertices[v][0 * 3 + 2] = displacement_[vertex_length_index(v)];
                vertices[v][1 * 3 + 2] = displacement_[vertex_length_index(vt)];
                vertices[v][2 * 3 + 2] = displacement_[vertex_length_index(vt + 1)];
                vertices[v][3 * 3 + 2] = displacement_[vertex_length_index(vt + 2)];
            }

            /* Update pair with actual vertex range */
            iter->first = vstart;
            iter->second = vend - 1;
        }

        mesh_.update_vbo(ranges);
    }

    Mesh& mesh() { return mesh_; }
    Program& program() { return program_; }

    void reset()
    {
        program_.stop();
        program_.release();
        mesh_.reset();
    }

private:
    Mesh mesh_;
    Program program_;
    double length_;
    double width_;
    size_t nlength_;
    size_t nwidth_;
    /* Wave parameters */
    double wave_k_;
    double wave_period_;
    double wave_full_period_;
    double wave_velocity_;

    std::vector<double> displacement_;

    /**
     * Calculates the length index of a vertex.
     */
    size_t vertex_length_index(size_t v)
    {
        return v / (6 * nwidth_) + (v % 2);
    }

    /**
     * The sine wave function with duty-cycle.
     *
     * @param x the space coordinate
     *
     * @return the operation error code
     */
    double wave_func(double x)
    {
        double r(fmod(x, wave_full_period_));
        if (r < 0)
            r += wave_full_period_;

        /*
         * Return either the sine value or 0.0, depending on the
         * wave duty cycle.
         */
        if (r > wave_period_)
        {
            return 0;
        }
        else
        {
            return 0.2 * std::sin(wave_k_ * r);
        }
    }

    /**
     * Calculates the displacement of the wave.
     *
     * @param n the length index
     * @param elapsed the time elapsed since the beginning of the rendering
     *
     * @return the displacement at point n at time elapsed
     */
    double displacement(size_t n, double elapsed)
    {
        double x(n * length_ / nlength_);

        return wave_func(x - wave_velocity_ * elapsed);
    }

    /**
     * Creates the GL shader program.
     */
    void create_program()
    {
        /* Set up shaders */
        static const std::string vtx_shader_filename(
                Options::data_path + "/shaders/buffer-wireframe.vert");
        static const std::string frg_shader_filename(
                Options::data_path + "/shaders/buffer-wireframe.frag");

        ShaderSource vtx_source(vtx_shader_filename);
        ShaderSource frg_source(frg_shader_filename);

        if (!Scene::load_shaders_from_strings(program_, vtx_source.str(),
                                              frg_source.str()))
        {
            return;
        }
    }

    /**
     * Creates the grid mesh.
     */
    void create_mesh()
    {
        /*
         * We need to pass the positions of all vertex of the triangle
         * in order to draw the wireframe.
         */
        std::vector<int> vertex_format;
        vertex_format.push_back(3);     // Position of vertex
        vertex_format.push_back(3);     // Position of triangle vertex 0
        vertex_format.push_back(3);     // Position of triangle vertex 1
        vertex_format.push_back(3);     // Position of triangle vertex 2
        mesh_.set_vertex_format(vertex_format);

        std::vector<GLint> attrib_locations;
        attrib_locations.push_back(program_["position"].location());
        attrib_locations.push_back(program_["tvertex0"].location());
        attrib_locations.push_back(program_["tvertex1"].location());
        attrib_locations.push_back(program_["tvertex2"].location());
        mesh_.set_attrib_locations(attrib_locations);

        mesh_.make_grid(nlength_, nwidth_, length_, width_,
                        0.0, wave_grid_conf);
    }

};

/******************************
 * SceneBuffer implementation *
 ******************************/

struct SceneBufferPrivate {
    WaveMesh *wave;
    SceneBufferPrivate() : wave(0) {}
    ~SceneBufferPrivate() { delete wave; }
};

SceneBuffer::SceneBuffer(Canvas &pCanvas) :
    Scene(pCanvas, "buffer")
{
    priv_ = new SceneBufferPrivate();
    options_["interleave"] = Scene::Option("interleave", "false",
                                           "Whether to interleave vertex attribute data",
                                           "false,true");
    options_["update-method"] = Scene::Option("update-method", "map",
                                              "Which method to use to update vertex data",
                                              "map,subdata");
    options_["update-fraction"] = Scene::Option("update-fraction", "1.0",
                                                "The fraction of the mesh length that is updated at every iteration (0.0-1.0)");
    options_["update-dispersion"] = Scene::Option("update-dispersion", "0.0",
                                                  "How dispersed the updates are [0.0 - 1.0]");
    options_["columns"] = Scene::Option("columns", "100",
                                       "The number of mesh subdivisions length-wise");
    options_["rows"] = Scene::Option("rows", "20",
                                      "The number of mesh subdisivisions width-wise");
    options_["buffer-usage"] = Scene::Option("buffer-usage", "static",
                                             "How the buffer will be used",
                                             "static,stream,dynamic");
}

SceneBuffer::~SceneBuffer()
{
    delete priv_;
}

bool
SceneBuffer::supported(bool show_errors)
{
    if (options_["update-method"].value != "subdata" &&
        (GLExtensions::MapBuffer == 0 || GLExtensions::UnmapBuffer == 0))
    {
        if (show_errors) {
            Log::error("Requested MapBuffer VBO update method but GL_OES_mapbuffer"
                       " is not supported!\n");
        }
        return false;
    }

    return true;
}

bool
SceneBuffer::load()
{
    running_ = false;

    return true;
}

void
SceneBuffer::unload()
{
}

bool
SceneBuffer::setup()
{
    using LibMatrix::vec3;

    if (!Scene::setup())
        return false;

    bool interleave = (options_["interleave"].value == "true");
    Mesh::VBOUpdateMethod update_method;
    Mesh::VBOUsage usage;
    double update_fraction;
    double update_dispersion;
    size_t nlength;
    size_t nwidth;

    if (options_["update-method"].value == "map")
        update_method = Mesh::VBOUpdateMethodMap;
    else if (options_["update-method"].value == "subdata")
        update_method = Mesh::VBOUpdateMethodSubData;
    else
        update_method = Mesh::VBOUpdateMethodMap;

    if (options_["buffer-usage"].value == "static")
        usage = Mesh::VBOUsageStatic;
    else if (options_["buffer-usage"].value == "stream")
        usage = Mesh::VBOUsageStream;
    else
        usage = Mesh::VBOUsageDynamic;

    update_fraction = Util::fromString<double>(options_["update-fraction"].value);
    update_dispersion = Util::fromString<double>(options_["update-dispersion"].value);
    nlength = Util::fromString<size_t>(options_["columns"].value);
    nwidth = Util::fromString<size_t>(options_["rows"].value);


    priv_->wave = new WaveMesh(5.0, 2.0, nlength, nwidth,
                               update_fraction * (1.0 - update_dispersion + 0.0001),
                               update_fraction);

    priv_->wave->mesh().interleave(interleave);
    priv_->wave->mesh().vbo_update_method(update_method);
    priv_->wave->mesh().vbo_usage(usage);
    priv_->wave->mesh().build_vbo();

    priv_->wave->program().start();
    priv_->wave->program()["Viewport"] = LibMatrix::vec2(canvas_.width(), canvas_.height());

    glDisable(GL_CULL_FACE);

    currentFrame_ = 0;
    running_ = true;
    startTime_ = Util::get_timestamp_us() / 1000000.0;
    lastUpdateTime_ = startTime_;

    return true;
}

void
SceneBuffer::teardown()
{
    delete priv_->wave;
    priv_->wave = 0;

    glEnable(GL_CULL_FACE);

    Scene::teardown();
}

void
SceneBuffer::update()
{
    Scene::update();

    double elapsed_time = lastUpdateTime_ - startTime_;

    priv_->wave->update(elapsed_time);
}

void
SceneBuffer::draw()
{
    LibMatrix::Stack4 model_view;

    // Load the ModelViewProjectionMatrix uniform in the shader
    LibMatrix::mat4 model_view_proj(canvas_.projection());
    model_view.translate(0.0, 0.0, -4.0);
    model_view.rotate(45.0, -1.0, 0.0, 0.0);
    model_view_proj *= model_view.getCurrent();

    priv_->wave->program()["ModelViewProjectionMatrix"] = model_view_proj;

    priv_->wave->mesh().render_vbo();
}

Scene::ValidationResult
SceneBuffer::validate()
{
    static const double radius_3d(std::sqrt(3.0 * 2.0 * 2.0));

    Canvas::Pixel ref(0x34, 0x99, 0xd7, 0xff);
    Canvas::Pixel pixel = canvas_.read_pixel(402, 189);

    double dist = pixel.distance_rgb(ref);
    if (dist < radius_3d + 0.01) {
        return Scene::ValidationSuccess;
    }
    else {
        Log::debug("Validation failed! Expected: 0x%x Actual: 0x%x Distance: %f\n",
                    ref.to_le32(), pixel.to_le32(), dist);
        return Scene::ValidationFailure;
    }

    return Scene::ValidationUnknown;
}
