/*
 * 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 <stdint.h>
#include <vector>

#include "canvas.h"
#include "mesh.h"
#include "vec.h"
#include "program.h"
#include "gl-headers.h"

/** 
 * Renderer interface.
 */
class IRenderer
{
public:
    /** 
     * Sets up the renderer's target.
     */
    virtual void setup_onscreen(Canvas& canvas) = 0;
    virtual void setup_offscreen(const LibMatrix::vec2 &size, bool has_depth) = 0;
    /** 
     * Sets up the renderer's target texture (if any).
     */
    virtual void setup_texture(GLint min_filter, GLint mag_filter,
                               GLint wrap_s, GLint wrap_t) = 0;
    /** 
     * Sets the renderer's input texture.
     */
    virtual void input_texture(GLuint t) = 0;
    /** 
     * Gets the renderer's target texture (if any).
     */
    virtual GLuint texture() = 0;
    /** 
     * Gets the size of the renderer's target.
     */
    virtual LibMatrix::vec2 size() = 0;
    /** 
     * Makes the renderer current i.e. the rendering target.
     */
    virtual void make_current() = 0;
    /** 
     * Updates the mipmap of the texture backing the renderer (if any).
     */
    virtual void update_mipmap() = 0;
    /** 
     * Renders to the renderer's target.
     */
    virtual void render() = 0;

protected:
    virtual ~IRenderer() {}
};

/** 
 * A chain of renderers, which implements IRenderer
 */
class RendererChain : public IRenderer
{
public:
    RendererChain() {}
    virtual ~RendererChain() {}

    /* IRenderer methods */
    void setup_onscreen(Canvas& canvas);
    void setup_offscreen(const LibMatrix::vec2 &size, bool has_depth);
    void setup_texture(GLint min_filter, GLint mag_filter,
                       GLint wrap_s, GLint wrap_t);
    void input_texture(GLuint t);
    GLuint texture();
    LibMatrix::vec2 size();
    void make_current();
    void update_mipmap();
    void render();

    /** 
     * Appends a renderer to the chain.
     * 
     * @param renderer the renderer to append
     */
    void append(IRenderer &renderer);

private:
    std::vector<IRenderer *> renderers_;
};

/** 
 * A base implementation of the IRenderer interface.
 */
class BaseRenderer : public IRenderer
{
public:
    BaseRenderer();
    virtual ~BaseRenderer();

    /* IRenderer methods */
    virtual void setup_onscreen(Canvas& canvas);
    virtual void setup_offscreen(const LibMatrix::vec2 &size, bool has_depth);
    virtual void setup_texture(GLint min_filter, GLint mag_filter,
                               GLint wrap_s, GLint wrap_t);
    virtual void input_texture(GLuint t) { input_texture_ = t; }
    virtual GLuint texture() { return texture_; }
    virtual LibMatrix::vec2 size() { return size_; }
    virtual void make_current();
    virtual void update_mipmap();
    virtual void render() = 0;

protected:
    void recreate(Canvas* canvas, bool has_depth);
    void create_texture();
    void update_texture_parameters();
    void create_fbo(bool has_depth);

    LibMatrix::vec2 size_;
    GLuint texture_;
    GLuint input_texture_;
    GLuint fbo_;
    GLuint depth_renderbuffer_;
    bool owns_fbo_;
    GLint min_filter_;
    GLint mag_filter_;
    GLint wrap_s_;
    GLint wrap_t_;
};

/** 
 * A renderer that renders its input texture to its target,
 * according to the supplied GL Program.
 */
class TextureRenderer : public BaseRenderer
{
public:
    TextureRenderer(Program &program);
    virtual ~TextureRenderer() { }

    /* IRenderer/BaseRenderer methods */
    virtual void render();

    /**
     * Gets the program associated with the renderer.
     */
    Program &program() { return program_; }

private:
    void create_mesh();

    Mesh mesh_;
    Program &program_;
};

/** 
 * A renderer that copies the input texture to its target.
 */
class CopyRenderer : public TextureRenderer
{
public:
    CopyRenderer();

    virtual ~CopyRenderer() { delete copy_program_; }

private:
    static Program *copy_program(bool create_new);

    Program *copy_program_;
};

/** 
 * A renderer that renders simplex noise to its target.
 */
class SimplexNoiseRenderer : public TextureRenderer
{
public:
    SimplexNoiseRenderer();
    virtual ~SimplexNoiseRenderer() { delete noise_program_; }

    LibMatrix::vec2 uv_scale() { return uv_scale_; }
private:
    static Program *noise_program(bool create_new);
    static LibMatrix::vec2 uv_scale_;

    Program *noise_program_;
};

/** 
 * A renderer that renders a normal map to its target from a
 * height map in its input texture.
 */
class NormalFromHeightRenderer : public TextureRenderer
{
public:
    NormalFromHeightRenderer();
    virtual ~NormalFromHeightRenderer() { delete normal_from_height_program_; }

    virtual void setup_onscreen(Canvas& canvas);
    virtual void setup_offscreen(const LibMatrix::vec2 &size, bool has_depth);

private:
    static Program *normal_from_height_program(bool create_new);

    Program *normal_from_height_program_;
};

/** 
 * A renderer that renders the luminance of its input texture to its target.
 */
class LuminanceRenderer : public TextureRenderer
{
public:
    LuminanceRenderer();
    virtual ~LuminanceRenderer() { delete luminance_program_; }

private:
    static Program *luminance_program(bool create_new);

    Program *luminance_program_;
};


/** 
 * A renderer that renders a blurred version of the input texture to its target.
 */
class BlurRenderer : public TextureRenderer
{
public:
    enum BlurDirection {
        BlurDirectionHorizontal,
        BlurDirectionVertical,
        BlurDirectionBoth
    };

    BlurRenderer(int radius, float sigma, BlurDirection dir,
                 const LibMatrix::vec2 &step, float tilt_shift);
    virtual ~BlurRenderer() { delete blur_program_; }

private:
    static Program *blur_program(bool create_new, int radius, float sigma,
                                 BlurDirection dir, const LibMatrix::vec2 &step,
                                 float tilt_shift);

    Program *blur_program_;
};

/** 
 * A renderer that renders with opacity (overlays) it's input texture over
 * the target of another renderer.
 */
class OverlayRenderer : public IRenderer
{
public:
    OverlayRenderer(IRenderer &target, GLfloat opacity);
    virtual ~OverlayRenderer() { }

    /* IRenderable Methods */
    virtual void setup_onscreen(Canvas& canvas);
    virtual void setup_offscreen(const LibMatrix::vec2 &size, bool has_depth);
    void setup_texture(GLint min_filter, GLint mag_filter,
                       GLint wrap_s, GLint wrap_t);
    void input_texture(GLuint t) { input_texture_ = t; }
    virtual GLuint texture() { return target_renderer_.texture(); }
    virtual LibMatrix::vec2 size() { return target_renderer_.size(); }
    virtual void render();
    void make_current();
    void update_mipmap();

private:
    void create_mesh();
    void create_program();

    Mesh mesh_;
    Program program_;
    IRenderer &target_renderer_;
    GLfloat opacity_;
    GLuint input_texture_;
};

/** 
 * A renderer that renders a dynamic terrain as per the WebGL
 * dynamic terrain demo.
 */
class TerrainRenderer : public BaseRenderer
{
public:
    TerrainRenderer(const LibMatrix::vec2 &repeat_overlay);
    virtual ~TerrainRenderer();

    /* IRenderable Methods */
    virtual void render();

    /**
     * Gets the program associated with the renderer.
     */
    Program &program() { return program_; }
    /**
     * Sets the height map texture to use.
     */
    void height_map_texture(GLuint tex) { height_map_tex_ = tex; }
    /**
     * Sets the normal map texture to use.
     */
    void normal_map_texture(GLuint tex) { normal_map_tex_ = tex; }
    /**
     * Sets the specular map texture to use.
     */
    void specular_map_texture(GLuint tex) { specular_map_tex_ = tex; }
    /**
     * Returns the main diffuse texture.
     */
    GLuint diffuse1_texture() { return diffuse1_tex_; }
    LibMatrix::vec2 repeat_overlay() { return repeat_overlay_; }

private:
    void create_mesh();
    void init_textures();
    void init_program();
    void bind_textures();
    void deinit_textures();

    LibMatrix::vec3 color_to_vec3(uint32_t c)
    {
        return LibMatrix::vec3(((c >> 0) & 0xff) / 255.0,
                               ((c >> 8) & 0xff) / 255.0,
                               ((c >> 16) & 0xff) / 255.0);
    }

    Mesh mesh_;
    Program program_;

    GLuint height_map_tex_;
    GLuint normal_map_tex_;
    GLuint specular_map_tex_;
    GLuint diffuse1_tex_;
    GLuint diffuse2_tex_;
    GLuint detail_tex_;
    LibMatrix::vec2 repeat_overlay_;
};
