//////////////////////////////////////////////////////////////////////////
//                                                                      //
//  This file contains the definition of the CShaderProgram2_0 class    //
//  and its derived classes.                                            //
//                                                                      //
//////////////////////////////////////////////////////////////////////////
#include <GL/glew.h>
#include <iostream>
using std::cout;
using std::endl;
using std::string;

#include "popassert.hpp"
#include "CShaderProgram2_0.hpp"




//////////////////////////////////////////////////////////////////////////
//                                                                      //
//  Class    : CShaderProgram2_0                                        //
//                                                                      //
//  Function : CShaderProgram2_0() (default constructor)                //
//                                                                      //
//  Comments :                                                          //
//                                                                      //
//////////////////////////////////////////////////////////////////////////
CShaderProgram2_0::CShaderProgram2_0() :
  m_vShaders(),
  m_shaderProgramId(0)
{
}




//////////////////////////////////////////////////////////////////////////
//                                                                      //
//  Class    : CShaderProgram2_0                                        //
//                                                                      //
//  Function : ~CShaderProgram2_0() (destructor)                        //
//                                                                      //
//  Comments :                                                          //
//                                                                      //
//////////////////////////////////////////////////////////////////////////
CShaderProgram2_0::~CShaderProgram2_0()
{
  if (m_shaderProgramId) {
    for (shaders_type::iterator it = m_vShaders.begin();
         it != m_vShaders.end();
         ++it)
      glDetachShader(m_shaderProgramId, *it);

    glDeleteProgram(m_shaderProgramId);
  }

  for (shaders_type::iterator it = m_vShaders.begin();
       it != m_vShaders.end();
       ++it)
    glDeleteShader(*it);
}




//////////////////////////////////////////////////////////////////////////
//                                                                      //
//  Class    : CShaderProgram2_0                                        //
//                                                                      //
//  Function : addShader()                                              //
//                                                                      //
//  Comments : This function compiles the shader passed as a string     //
//             and adds it to this shader program.                      //
//                                                                      //
//////////////////////////////////////////////////////////////////////////
bool CShaderProgram2_0::addShader(EShaderType shaderType,
                                  const string& shader)
{
  GLuint shaderId = 0;
  if (shaderType == VERTEX_SHADER)
    shaderId = glCreateShader(GL_VERTEX_SHADER);
  else if (shaderType == FRAGMENT_SHADER)
    shaderId = glCreateShader(GL_FRAGMENT_SHADER);

  if (shaderId == 0) {
    POP_DEBUG("Unable to create shader.");
    return false;
  }

  const char * shaderString = shader.c_str();

  glShaderSource(shaderId, 1, &shaderString, NULL);
  glCompileShader(shaderId);

  printShaderInfoLog(shaderId);

  // Compiled fine?
  int status;
  glGetShaderiv(shaderId, GL_COMPILE_STATUS, &status);
  if (status != GL_TRUE) {
    POP_DEBUG("Error compiling the shader.");
    glDeleteShader(shaderId);
    return false;
  }

  m_vShaders.push_back(shaderId);

  return true;
}




//////////////////////////////////////////////////////////////////////////
//                                                                      //
//  Class    : CShaderProgram2_0                                        //
//                                                                      //
//  Function : link()                                                   //
//                                                                      //
//  Comments : This function compiles the shader passed as a string     //
//             and adds it to this shader program.                      //
//                                                                      //
//////////////////////////////////////////////////////////////////////////
bool CShaderProgram2_0::link()
{
  m_shaderProgramId = glCreateProgram();

  if (m_shaderProgramId == 0) {
    POP_DEBUG("Unable to create shader program.");
    return false;
  }

  for (shaders_type::iterator it = m_vShaders.begin();
       it != m_vShaders.end();
       ++it) {
    glAttachShader(m_shaderProgramId, *it);
  }

  glLinkProgram(m_shaderProgramId);

  printProgramInfoLog(m_shaderProgramId);

  // Linked fine?
  int status;
  glGetProgramiv(m_shaderProgramId, GL_LINK_STATUS, &status);
  if (status != GL_TRUE) {
    POP_DEBUG("Error linking the shader.");
    glDeleteProgram(m_shaderProgramId);
    m_shaderProgramId = 0;
    return false;
  }

  return true;
}




//////////////////////////////////////////////////////////////////////////
//                                                                      //
//  Class    : CShaderProgram2_0                                        //
//                                                                      //
//  Function : use()                                                    //
//                                                                      //
//  Comments : This function replaces the OpenGL fixed functionnality   //
//             pipe-line by the programmable one defined by this        //
//             shader.                                                  //
//                                                                      //
//////////////////////////////////////////////////////////////////////////
void CShaderProgram2_0::use()
{
  glUseProgram(m_shaderProgramId);
}




//////////////////////////////////////////////////////////////////////////
//                                                                      //
//  Class    : CShaderProgram2_0                                        //
//                                                                      //
//  Function : unuse()                                                  //
//                                                                      //
//  Comments : This function resets the OpenGL pipe-line to the fixed   //
//             functionnality.                                          //
//                                                                      //
//////////////////////////////////////////////////////////////////////////
void CShaderProgram2_0::unuse()
{
  glUseProgram(0);
}




//////////////////////////////////////////////////////////////////////////
//                                                                      //
//  Class    : CShaderProgram2_0                                        //
//                                                                      //
//  Function : setUniform1f()                                           //
//                                                                      //
//  Comments : This function sets the uniform variable for the shader.  //
//                                                                      //
//////////////////////////////////////////////////////////////////////////
void CShaderProgram2_0::setUniform1f(const string& name,
                                     float v0)
{
  GLint location = glGetUniformLocation(m_shaderProgramId,
                                        name.c_str());
  glUniform1f(location, v0);
}




//////////////////////////////////////////////////////////////////////////
//                                                                      //
//  Class    : CShaderProgram2_0                                        //
//                                                                      //
//  Function : setUniform3f()                                           //
//                                                                      //
//  Comments : This function sets the uniform variable for the shader.  //
//                                                                      //
//////////////////////////////////////////////////////////////////////////
void CShaderProgram2_0::setUniform3f(const string& name,
                                     float v0, float v1, float v2)
{
  GLint location = glGetUniformLocation(m_shaderProgramId,
                                        name.c_str());
  glUniform3f(location, v0, v1, v2);
}




//////////////////////////////////////////////////////////////////////////
//                                                                      //
//  Class    : CShaderProgram2_0                                        //
//                                                                      //
//  Function : isSupported() (static)                                   //
//                                                                      //
//  Comments : This function returns true if OpenGL 2.0 is supported.   //
//                                                                      //
//////////////////////////////////////////////////////////////////////////
bool CShaderProgram2_0::isSupported()
{
  return glewIsSupported("GL_VERSION_2_0") ? true : false;
}




//////////////////////////////////////////////////////////////////////////
//                                                                      //
//  Class    : CShaderProgram2_0                                        //
//                                                                      //
//  Function : printShaderInfoLog()                                     //
//                                                                      //
//  Comments : This function prints the compile log of a shader.  It    //
//             comes directly from                                      //
//             http://www.lighthouse3d.com/opengl/glsl/                 //
//                                                                      //
//////////////////////////////////////////////////////////////////////////
void CShaderProgram2_0::printShaderInfoLog(GLuint obj)
{
  int infologLength = 0;
  int charsWritten  = 0;
  char *infoLog;

  glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infologLength);

  if (infologLength > 0)
    {
      infoLog = (char *)malloc(infologLength);
      glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog);
      cout << infoLog << endl;
      free(infoLog);
    }
}





//////////////////////////////////////////////////////////////////////////
//                                                                      //
//  Class    : CShaderProgram2_0                                        //
//                                                                      //
//  Function : printProgramInfoLog()                                    //
//                                                                      //
//  Comments : This function prints the link log of a shader.  It       //
//             comes directly from                                      //
//             http://www.lighthouse3d.com/opengl/glsl/                 //
//                                                                      //
//////////////////////////////////////////////////////////////////////////
void CShaderProgram2_0::printProgramInfoLog(GLuint obj)
{
  int infologLength = 0;
  int charsWritten  = 0;
  char *infoLog;

  glGetProgramiv(obj, GL_INFO_LOG_LENGTH,&infologLength);

  if (infologLength > 0)
    {
      infoLog = (char *)malloc(infologLength);
      glGetProgramInfoLog(obj, infologLength, &charsWritten, infoLog);
      cout << infoLog << endl;
      free(infoLog);
    }
}
