//
// This file is part of MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2015 Richard S. Smith and collaborators.
//
// If you use MorphoGraphX in your work, please cite:
//   http://dx.doi.org/10.7554/eLife.05864
//
// MorphoGraphX is free software, and is licensed under under the terms of the 
// GNU General (GPL) Public License version 2.0, http://www.gnu.org/licenses.
// 
#ifndef SHADER_H
#define SHADER_H

#include <Config.hpp>
#include <GL.hpp>

#include <Assert.hpp>
#include <Matrix.hpp>
#include <Parms.hpp>
#include <Vector.hpp>

#include <iostream>
#include <QHash>
#include <QString>
#include <string>
#include <vector>

class QString;

namespace mgx 
{
  typedef Vector<1, GLint> ivec1;
  typedef Vector<2, GLint> ivec2;
  typedef Vector<3, GLint> ivec3;
  typedef Vector<4, GLint> ivec4;
  typedef Vector<1, GLfloat> vec1;
  typedef Vector<2, GLfloat> vec2;
  typedef Vector<3, GLfloat> vec3;
  typedef Vector<4, GLfloat> vec4;
  typedef Matrix<2, 2, GLfloat> mat2;
  typedef Matrix<3, 3, GLfloat> mat3;
  typedef Matrix<4, 4, GLfloat> mat4;
  
  enum UNIFORM_TYPE {
    UNIFORM_INT,
    UNIFORM_INT2,
    UNIFORM_INT3,
    UNIFORM_INT4,
    UNIFORM_FLOAT,
    UNIFORM_FLOAT2,
    UNIFORM_FLOAT3,
    UNIFORM_FLOAT4,
    UNIFORM_MATRIX2,
    UNIFORM_MATRIX3,
    UNIFORM_MATRIX4
  };
  
  class mgx_EXPORT GLSLValue {
    class Value {
  public:
      virtual ~Value() {
      }
      virtual void setUniform(GLint location) const = 0;
      virtual void setAttrib(GLuint location) const = 0;
      virtual QTextStream& read(QTextStream& s) = 0;
      virtual QTextStream& write(QTextStream& s) const = 0;
      virtual std::istream& read(std::istream& s) = 0;
      virtual std::ostream& write(std::ostream& s) const = 0;
      virtual Value* copy() = 0;
    }* value;
  
    template <typename T> class ValueImpl : public Value {
  public:
      typedef typename T::value_type value_type;
      typedef void (*uniform_fct)(GLint, GLsizei, const value_type*);
      typedef void (*attrib_fct)(GLuint, const value_type*);
  
      ValueImpl(uniform_fct ufct, attrib_fct afct, const T* v, int count)
        : Value()
        , value(v, v + count)
        , glUniform(ufct)
        , glVertexAttrib(afct)
      {
      }
  
      ValueImpl(uniform_fct ufct, attrib_fct afct)
        : Value()
        , glUniform(ufct)
        , glVertexAttrib(afct)
      {
      }
  
      ValueImpl(const ValueImpl& copy)
        : Value()
        , value(copy.value)
        , glUniform(copy.glUniform)
        , glVertexAttrib(copy.glVertexAttrib)
      {
      }
  
      virtual Value* copy() {
        return new ValueImpl(*this);
      }
  
      virtual void setUniform(GLint location) const {
        glUniform(location, (GLint)value.size(), value[0].c_data());
      }
      virtual void setAttrib(GLuint location) const {
        glVertexAttrib(location, value[0].c_data());
      }
      virtual QTextStream& read(QTextStream& s)
      {
        value.resize(1);
        s >> value[0];
        return s;
      }
      virtual QTextStream& write(QTextStream& s) const
      {
        s << value[0];
        return s;
      }
      virtual std::istream& read(std::istream& s)
      {
        value.resize(1);
        s >> value[0];
        return s;
      }
      virtual std::ostream& write(std::ostream& s) const
      {
        s << value[0];
        return s;
      }
      std::vector<T> value;
      uniform_fct glUniform;
      attrib_fct glVertexAttrib;
    };
  
    UNIFORM_TYPE type;
  
  public:
    GLSLValue()
      : value(0)
      , type(UNIFORM_INT)
    {
    }
  
    GLSLValue(const GLSLValue& copy)
      : value(0)
      , type(copy.type)
    {
      if(copy.value)
        value = copy.value->copy();
    }
  
    template <typename T>
    explicit GLSLValue(const T& val)
      : value(0)
    {
      setValue(val);
    }
  
    template <typename T>
    explicit GLSLValue(const std::vector<T>& val)
      : value(0)
    {
      setValue(val);
    }
  
    template <typename T>
    explicit GLSLValue(const T* val, int count)
      : value(0)
    {
      setValue(val, count);
    }
  
    ~GLSLValue() {
      delete value;
    }
    GLSLValue& operator=(const GLSLValue& copy)
    {
      delete value;
      value = 0;
      if(copy.value)
        value = copy.value->copy();
      type = copy.type;
      return *this;
    }
    void setUniform(GLint location) const {
      value->setUniform(location);
    }
    void setAttrib(GLuint location) const {
      value->setAttrib(location);
    }
    std::istream& read(std::istream& s);
    std::ostream& write(std::ostream& s) const;
    QTextStream& read(QTextStream& s);
    QTextStream& write(QTextStream& s) const;
  
    void setValue(const GLint* value, int count);
    void setValue(const ivec1* value, int count);
    void setValue(const ivec2* value, int count);
    void setValue(const ivec3* value, int count);
    void setValue(const ivec4* value, int count);
    void setValue(const GLfloat* value, int count);
    void setValue(const vec1* value, int count);
    void setValue(const vec2* value, int count);
    void setValue(const vec3* value, int count);
    void setValue(const vec4* value, int count);
    void setValue(const mat2* value, int count);
    void setValue(const mat3* value, int count);
    void setValue(const mat4* value, int count);
  
    void setValue(const GLint& value) {
      setValue(&value, 1);
    }
    void setValue(const ivec1& value) {
      setValue(&value, 1);
    }
    void setValue(const ivec2& value) {
      setValue(&value, 1);
    }
    void setValue(const ivec3& value) {
      setValue(&value, 1);
    }
    void setValue(const ivec4& value) {
      setValue(&value, 1);
    }
    void setValue(const GLfloat& value) {
      setValue(&value, 1);
    }
    void setValue(const vec1& value) {
      setValue(&value, 1);
    }
    void setValue(const vec2& value) {
      setValue(&value, 1);
    }
    void setValue(const vec3& value) {
      setValue(&value, 1);
    }
    void setValue(const vec4& value) {
      setValue(&value, 1);
    }
    void setValue(const mat2& value) {
      setValue(&value, 1);
    }
    void setValue(const mat3& value) {
      setValue(&value, 1);
    }
    void setValue(const mat4& value) {
      setValue(&value, 1);
    }
  
    bool valid() const {
      return value != 0;
    }
  };
  
  inline QTextStream& operator<<(QTextStream& s, const GLSLValue& ut) {
    return ut.write(s);
  }
  
  inline QTextStream& operator>>(QTextStream& s, GLSLValue& ut) {
    return ut.read(s);
  }
  
  inline std::ostream& operator<<(std::ostream& s, const GLSLValue& ut) {
    return ut.write(s);
  }
  
  inline std::istream& operator>>(std::istream& s, GLSLValue& ut) {
    return ut.read(s);
  }
  
  class mgx_EXPORT Shader {
  public:
    enum ActiveTextures {
      AT_NONE = 0,
      AT_TEX3D,
      AT_SECOND_TEX3D,
      AT_TEX2D,
      AT_LABEL_TEX,
      AT_SURF_TEX,
      AT_HEAT_TEX,
      AT_DEPTH_TEX,
      AT_CMAP_TEX,
      AT_SECOND_CMAP_TEX,
      AT_SURF_RENDER_TEX,
      AT_FINAL_VOLUME_TEX,
      AT_FRONT_TEX,
      AT_BACK_TEX,
      AT_FRONT_COLOR_TEX,
      AT_OCCLUSION_TEX,
      AT_END
    };
    Shader(int verbosity = 1);
  
    static void activeTexture(ActiveTextures at) {
      glfuncs->glActiveTexture(at + GL_TEXTURE0);
    }
  
    static void activeTexture(int at) {
      glfuncs->glActiveTexture(at + GL_TEXTURE0);
    }
  
    bool init();
    void invalidate() {
      _initialized = false;
    }
  
    // void readParms(Parms& parms, QString shaders_section, QString uniforms_section);
  
    bool setupShaders();
    bool useShaders();
    static bool stopUsingShaders();
  
    bool isVertexShaderCode(unsigned int pos) const {
      return vertex_shaders_code[pos].second;
    }
  
    bool isFragmentShaderCode(unsigned int pos) const {
      return fragment_shaders_code[pos].second;
    }
  
    const QString& getVertexShader(unsigned int pos) const {
      return vertex_shaders_code[pos].first;
    }
  
    const QString& getFragmentShader(unsigned int pos) const {
      return fragment_shaders_code[pos].first;
    }
  
    void addVertexShaderCode(const QString& code);
    bool changeVertexShaderCode(int pos, const QString& code);
    void removeVertexShaderCode(const QString& code);
    void addFragmentShaderCode(const QString& code);
    bool changeFragmentShaderCode(int pos, const QString& code);
    void removeFragmentShaderCode(const QString& code);
  
    void addVertexShader(const QString& filename);
    bool changeVertexShader(int pos, const QString& filename);
    void removeVertexShader(const QString& filename);
    void addFragmentShader(const QString& filename);
    bool changeFragmentShader(int pos, const QString& filename);
    void removeFragmentShader(const QString& filename);
    void setVerbosity(int verb) {
      verbosity = verb;
    }
  
    QString shaderTypeName(GLenum shader_type);
  
    GLuint compileShaderFile(GLenum shader_type, QString filename);
    GLuint compileShader(GLenum shader_type, QString content);
  
    void printProgramInfoLog(GLuint object);
    void printShaderInfoLog(GLuint object);
    void cleanShaders();
  
    bool hasShaders() const {
      return has_shaders;
    }
  
    GLuint program() const {
      return _program;
    }
  
    void setupUniforms();
  
    GLuint attribLocation(const QString& name);
  
    void setAttrib(const QString& name, const GLSLValue& value);
    void setAttrib(GLuint loc, const GLSLValue& value);
  
    bool setUniform(const QString& name, const GLSLValue& value);
    void setUniform_instant(const QString& name, const GLSLValue& value);
  
    bool initialized() const {
      return _initialized;
    }
  
  protected:
    void loadUniform(GLint program, const QString& name, const GLSLValue& value);
  
    bool has_shaders;

    // if bool is true, then it's really code, otherwise, it's a file
    typedef std::pair<QString, bool> code_t;
    std::vector<code_t> vertex_shaders_code, fragment_shaders_code;
  
    std::vector<GLuint> vertex_shaders, fragment_shaders;
  
    std::vector<QString> uniform_names, model_uniform_names;
    std::vector<GLSLValue> uniform_values, model_uniform_values;
  
    int verbosity;
  
    GLuint _program;
  
    bool _initialized;
  };
}
#endif 
