//  SuperTuxKart - a fun racing game with go-kart
//  Copyright (C) 2014-2015 SuperTuxKart-Team
//
//  This program 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.
//
//  This program 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 this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


/**
\page shaders_overview Shaders Overview

 \section shader_declaration Shader declaration
 You need to create a class for each shader in shaders.cpp
 This class should inherit from the template ShaderHelper<>.
 The template first parameter is the shader class being declared and the following ones are the types
 of every uniform (except samplers) required by the shaders.

 The template inheritance will provide the shader with a setUniforms() variadic function which calls
 the glUniform*() that pass uniforms value to the shader according to the type given as parameter
 to the template.

 The class constructor is used to
 \li \ref shader_declaration_compile
 \li \ref shader_declaration_uniform_names
 \li \ref shader_declaration_bind_texture_unit

 Of course it's possible to use the constructor to declare others things if needed.

 \subsection shader_declaration_compile Compile the shader

 The LoadProgram() function is provided to ease shader compilation and link.
 It takes a flat sequence of SHADER_TYPE, filename pairs that will be linked together.
 This way you can add any shader stage you want (geometry, domain/hull shader)

 It is highly recommended to use explicit attribute location for a program input.
 However as not all hardware support this extension, default location are provided for
 input whose name is either Position (location 0) or Normal (location 1) or
 Texcoord (location 3) or Color (location 2) or SecondTexcoord (location 4).
 You can use these predefined name and location in your vao for shader
 that needs GL pre 3.3 support.

 \subsection shader_declaration_uniform_names Declare uniforms

 Use the assignUniforms() function to pass name of the uniforms in the program.
 The order of name declaration is the same as the argument passed to setUniforms function.

 \subsection shader_declaration_bind_texture_unit Bind texture unit and name

 Texture are optional but if you have one, you must give them determined texture unit (up to 32).
 You can do this using the assignTextureUnit function that takes pair of texture unit and sampler name
 as argument.

 \section shader_usage

 Shader's class are singleton that can be retrieved using ShaderClassName::getInstance() which automatically
 creates an instance the first time it is called.

 As the program id of a shader instance is public it can be used to bind a program :
 \code
 glUseProgram(MyShaderClass::getInstance()->Program);
 \endcode

 To set uniforms use the automatically generated function setUniforms:

 \code
 MyShaderClass::getInstance()->setUniforms(Args...)
 \endcode

 A Vertex Array must be bound (VAO creation process is currently left to the reader) :

 \code
 glBindVertexAttrib(vao);
 \endcode

 To actually perform the rendering you also need to call a glDraw* function (left to the reader as well) :

 \code
 glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_SHORT);
 \endcode

*/

#include "shaders.h"

//#include "callbacks.h"
#include "central_settings.h"
#include "glwrap.h"
//#include "gpu_particles.h"
#include "irr_driver.h"
#include "shared_gpu_objects.h"
#include "../io/file_manager.h"
#include "../utils/log.h"

#include <assert.h>
#include <IGPUProgrammingServices.h>


bool                               Shaders::m_has_been_initialised = false;
video::IShaderConstantSetCallBack *Shaders::m_callbacks[ES_COUNT];
int                                Shaders::m_shaders[ES_COUNT];

// Use macro FOREACH_SHADER from shaders.hpp to create an array
// with all shader names.
#define STR(a) #a,
const char *Shaders::shader_names[] = { FOREACH_SHADER(STR) };
#undef STR

using namespace video;

void Shaders::init()
{
    assert(!m_has_been_initialised);
    // Callbacks
    memset(m_callbacks, 0, sizeof(m_callbacks));

//    m_callbacks[ES_WATER] = new WaterShaderProvider();
//    m_callbacks[ES_GRASS] = new GrassShaderProvider();
//    m_callbacks[ES_MOTIONBLUR] = new MotionBlurProvider();
//    m_callbacks[ES_MIPVIZ] = new MipVizProvider();
//    m_callbacks[ES_DISPLACE] = new DisplaceProvider();

    for (s32 i = 0; i < ES_COUNT; i++)
        m_shaders[i] = -1;

    loadShaders();
    m_has_been_initialised = true;
}   // init

// ----------------------------------------------------------------------------
/** Frees all memory used by the shader manager.
 */
void Shaders::destroy()
{
    assert(m_has_been_initialised);
    u32 i;
    for (i = 0; i < ES_COUNT; i++)
    {
        if (i == ES_GAUSSIAN3V || !m_callbacks[i]) continue;
        delete m_callbacks[i];
    }
    m_has_been_initialised = false;
}   // destroy

// ----------------------------------------------------------------------------
// Shader loading  related hook

static std::string loadHeader()
{
    std::string result;
    std::ifstream Stream("header.txt", std::ios::in);

    if (Stream.is_open())
    {
        std::string Line = "";
        while (getline(Stream, Line))
            result += "\n" + Line;
        Stream.close();
    }

    return result;
}   // loadHeader

// ----------------------------------------------------------------------------
// Mostly from shader tutorial
GLuint loadShader(const char * file, unsigned type)
{
    GLuint Id = glCreateShader(type);
    char versionString[20];
    sprintf(versionString, "#version %d\n", CVS->getGLSLVersion());
    std::string Code = versionString;
    if (CVS->isAMDVertexShaderLayerUsable())
        Code += "#extension GL_AMD_vertex_shader_layer : enable\n";
    if (CVS->isAZDOEnabled())
    {
        Code += "#extension GL_ARB_bindless_texture : enable\n";
        Code += "#define Use_Bindless_Texture\n";
    }
    std::ifstream Stream(file, std::ios::in);
    Code += "//" + std::string(file) + "\n";
    if (!CVS->isARBUniformBufferObjectUsable())
        Code += "#define UBO_DISABLED\n";
    if (CVS->isAMDVertexShaderLayerUsable())
        Code += "#define VSLayer\n";
    if (CVS->needsRGBBindlessWorkaround())
        Code += "#define SRGBBindlessFix\n";
    Code += loadHeader();
    if (Stream.is_open())
    {
        std::string Line = "";
        while (getline(Stream, Line))
            Code += "\n" + Line;
        Stream.close();
    }
    GLint Result = GL_FALSE;
    int InfoLogLength;
    Log::info("GLWrap", "Compiling shader : %s", file);
    char const * SourcePointer = Code.c_str();
    int length = (int)strlen(SourcePointer);
    glShaderSource(Id, 1, &SourcePointer, &length);
    glCompileShader(Id);

    glGetShaderiv(Id, GL_COMPILE_STATUS, &Result);
    if (Result == GL_FALSE)
    {
        Log::error("GLWrap", "Error in shader %s", file);
        glGetShaderiv(Id, GL_INFO_LOG_LENGTH, &InfoLogLength);
        if (InfoLogLength<0)
            InfoLogLength = 1024;
        char *ErrorMessage = new char[InfoLogLength];
        ErrorMessage[0] = 0;
        glGetShaderInfoLog(Id, InfoLogLength, NULL, ErrorMessage);
        Log::error("GLWrap", ErrorMessage);
        delete[] ErrorMessage;
    }

    glGetError();

    return Id;
}   // loadShader

// ----------------------------------------------------------------------------
void Shaders::loadShaders()
{
    const std::string &dir = file_manager->getAsset(FileManager::SHADER, "");

    IGPUProgrammingServices * const gpu = irr_driver->getVideoDriver()
                                        ->getGPUProgrammingServices();

#define glsl(a, b, c) gpu->addHighLevelShaderMaterialFromFiles((a).c_str(), (b).c_str(), (IShaderConstantSetCallBack*) c)
#define glslmat(a, b, c, d) gpu->addHighLevelShaderMaterialFromFiles((a).c_str(), (b).c_str(), (IShaderConstantSetCallBack*) c, d)
#define glsl_noinput(a, b) gpu->addHighLevelShaderMaterialFromFiles((a).c_str(), (b).c_str(), (IShaderConstantSetCallBack*) 0)

    // Save previous shaders (used in case some shaders don't compile)
    int saved_shaders[ES_COUNT];
    memcpy(saved_shaders, m_shaders, sizeof(m_shaders));

    // Ok, go
    m_shaders[ES_NORMAL_MAP] = glsl_noinput(dir + "pass.vert", dir + "pass.frag");
    m_shaders[ES_NORMAL_MAP_LIGHTMAP] = glsl_noinput(dir + "pass.vert", dir + "pass.frag");

    m_shaders[ES_SKYBOX] = glslmat(dir + "pass.vert", dir + "pass.frag",
                                   m_callbacks[ES_SKYBOX], EMT_TRANSPARENT_ALPHA_CHANNEL);

    m_shaders[ES_SPLATTING] = glsl_noinput(dir + "pass.vert", dir + "pass.frag");

    m_shaders[ES_WATER] = glslmat(dir + "pass.vert", dir + "pass.frag",
                                  m_callbacks[ES_WATER], EMT_TRANSPARENT_ALPHA_CHANNEL);
    m_shaders[ES_WATER_SURFACE] = glsl(dir + "pass.vert", dir + "pass.frag",
                                       m_callbacks[ES_WATER]);

    m_shaders[ES_SPHERE_MAP] = glsl_noinput(dir + "pass.vert", dir + "pass.frag");

    m_shaders[ES_GRASS] = glslmat(dir + "pass.vert", dir + "pass.frag",
                                  m_callbacks[ES_GRASS], EMT_TRANSPARENT_ALPHA_CHANNEL);
    m_shaders[ES_GRASS_REF] = glslmat(dir + "pass.vert", dir + "pass.frag",
                                      m_callbacks[ES_GRASS], EMT_TRANSPARENT_ALPHA_CHANNEL_REF);

    m_shaders[ES_MOTIONBLUR] = glsl(dir + "pass.vert", dir + "pass.frag",
                                    m_callbacks[ES_MOTIONBLUR]);

    m_shaders[ES_GAUSSIAN3H] = glslmat(dir + "pass.vert", dir + "pass.frag",
                                       m_callbacks[ES_GAUSSIAN3H], EMT_SOLID);
    m_shaders[ES_GAUSSIAN3V] = glslmat(dir + "pass.vert", dir + "pass.frag",
                                       m_callbacks[ES_GAUSSIAN3V], EMT_SOLID);

    m_shaders[ES_MIPVIZ] = glslmat(dir + "pass.vert", dir + "pass.frag",
                                   m_callbacks[ES_MIPVIZ], EMT_SOLID);

    m_shaders[ES_OBJECTPASS] = glsl_noinput(dir + "pass.vert", dir + "pass.frag");
    m_shaders[ES_OBJECT_UNLIT] = glsl_noinput(dir + "pass.vert", dir + "pass.frag");
    m_shaders[ES_OBJECTPASS_REF] = glsl_noinput(dir + "pass.vert", dir + "pass.frag");
    m_shaders[ES_OBJECTPASS_RIMLIT] = glsl_noinput(dir + "pass.vert", dir + "pass.frag");

    m_shaders[ES_DISPLACE] = glslmat(dir + "pass.vert", dir + "pass.frag",
        m_callbacks[ES_DISPLACE], EMT_TRANSPARENT_ALPHA_CHANNEL);

    // Check that all successfully loaded
    for (s32 i = 0; i < ES_COUNT; i++) {

        // Old Intel Windows drivers fail here.
        // It's an artist option, so not necessary to play.
        if (i == ES_MIPVIZ)
            continue;

        check(i);
    }

#undef glsl
#undef glslmat
#undef glsl_noinput

    // In case we're reloading and a shader didn't compile: keep the previous, working one
    for (s32 i = 0; i < ES_COUNT; i++)
    {
        if (m_shaders[i] == -1)
            m_shaders[i] = saved_shaders[i];
    }

    initGL();
    SharedGPUObjects::init();
}   // loadShaders

// ----------------------------------------------------------------------------
void Shaders::check(const int num)
{
    if (m_shaders[num] == -1)
    {
        Log::error("shaders",
                   "Shader %s failed to load. Update your drivers, if the issue "
                   "persists, report a bug to us.", shader_names[num] + 3);
    }
}   // check

// ============================================================================
// Solid Normal and depth pass shaders
Shaders::ObjectPass1Shader::ObjectPass1Shader()
{
    loadProgram(OBJECT, GL_VERTEX_SHADER, "object_pass.vert",
                        GL_FRAGMENT_SHADER, "utils/encode_normal.frag",
                        GL_FRAGMENT_SHADER, "object_pass1.frag");
    assignUniforms("ModelMatrix", "InverseModelMatrix");
    assignSamplerNames(0, "tex", ST_TRILINEAR_ANISOTROPIC_FILTERED);
}   // ObjectPass1Shader

// ============================================================================
// Solid Lit pass shaders
Shaders::ObjectPass2Shader::ObjectPass2Shader()
{
    loadProgram(OBJECT, GL_VERTEX_SHADER, "object_pass.vert",
                        GL_FRAGMENT_SHADER, "utils/getLightFactor.frag",
                        GL_FRAGMENT_SHADER, "object_pass2.frag");
    assignUniforms("ModelMatrix", "TextureMatrix");
    assignSamplerNames(0, "DiffuseMap", ST_NEAREST_FILTERED,
                       1, "SpecularMap", ST_NEAREST_FILTERED,
                       2, "SSAO", ST_BILINEAR_FILTERED,
                       3, "Albedo", ST_TRILINEAR_ANISOTROPIC_FILTERED,
                       4, "SpecMap", ST_TRILINEAR_ANISOTROPIC_FILTERED);
}   // ObjectPass2Shader

// ============================================================================
Shaders::TransparentShader::TransparentShader()
{
    loadProgram(OBJECT, GL_VERTEX_SHADER, "object_pass.vert",
                        GL_FRAGMENT_SHADER, "transparent.frag");
    assignUniforms("ModelMatrix", "TextureMatrix");
    assignSamplerNames(0, "tex", ST_TRILINEAR_ANISOTROPIC_FILTERED);
}   // TransparentShader

// ============================================================================
Shaders::TransparentFogShader::TransparentFogShader()
{
    loadProgram(OBJECT, GL_VERTEX_SHADER, "object_pass.vert",
                        GL_FRAGMENT_SHADER, "transparentfog.frag");
    assignUniforms("ModelMatrix", "TextureMatrix", "fogmax", "startH",
                   "endH", "start", "end", "col");
    assignSamplerNames(0, "tex", ST_TRILINEAR_ANISOTROPIC_FILTERED);
}   // TransparentFogShader

// ============================================================================
Shaders::ColoredLine::ColoredLine()
{
    loadProgram(OBJECT, GL_VERTEX_SHADER,   "object_pass.vert",
                        GL_FRAGMENT_SHADER, "coloredquad.frag");

    assignUniforms("color");

    glGenVertexArrays(1, &m_vao);
    glBindVertexArray(m_vao);
    glGenBuffers(1, &m_vbo);
    glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
    glBufferData(GL_ARRAY_BUFFER, 6 * 1024 * sizeof(float), 0, GL_DYNAMIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);
    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}   // Shaders::ColoredLine
