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

#include "popassert.hpp"
#include "CShaderProgramARB.hpp"




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




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

    glDeleteObjectARB(m_shaderProgramId);
  }

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




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

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

  const char * shaderString = shader.c_str();

  glShaderSourceARB(shaderId, 1, &shaderString, NULL);
  glCompileShaderARB(shaderId);

  printInfoLog(shaderId);

  // Compiled fine?
  int status;
  glGetObjectParameterivARB(shaderId,
                            GL_OBJECT_COMPILE_STATUS_ARB,
                            &status);
  if (status == 0) {
    POP_DEBUG("Error compiling the shader.");
    glDeleteObjectARB(shaderId);
    return false;
  }

  m_vShaders.push_back(shaderId);

  return true;
}




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

  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) {
    glAttachObjectARB(m_shaderProgramId, *it);
  }

  glLinkProgramARB(m_shaderProgramId);

  printInfoLog(m_shaderProgramId);

  // Linked fine?
  GLint status;
  glGetObjectParameterivARB(m_shaderProgramId,
                            GL_OBJECT_LINK_STATUS_ARB,
                            &status);
  if (status != GL_TRUE) {
    POP_DEBUG("Error linking the shader.");
    glDeleteObjectARB(m_shaderProgramId);
    m_shaderProgramId = 0;
    return false;
  }

  return true;
}




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




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




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




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




//////////////////////////////////////////////////////////////////////////
//                                                                      //
//  Class    : CShaderProgramARB                                        //
//                                                                      //
//  Function : isSupported() (static)                                   //
//                                                                      //
//  Comments : This function returns true if OpenGL 2.0 is supported.   //
//                                                                      //
//////////////////////////////////////////////////////////////////////////
bool CShaderProgramARB::isSupported()
{
  return (GLEW_ARB_vertex_shader && GLEW_ARB_fragment_shader);
}




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

  glGetObjectParameterivARB(obj, GL_OBJECT_INFO_LOG_LENGTH_ARB,
                            &infologLength);

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