// ==============================================================
// This file is part of MegaGlest (www.glest.org)
//
// Shader.cpp  -- Copyright (c) 2006-2007 David Henry
// changed for use with MegaGlest: Copyright (C) 2011-  by Mark Vejvoda
//
// This code is licensed under the MIT license:
// http://www.opensource.org/licenses/mit-license.php
//
// Open Source Initiative OSI - The MIT License (MIT):Licensing
//
// The MIT License (MIT)
// Copyright (c) <year> <copyright holders>
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.//
//
// Implementation of GLSL shader related classes.
//
/////////////////////////////////////////////////////////////////////////////

#include <iostream>
#include <fstream>

#include "ShaderManager.h"
#include "GlErrors.h"

namespace Shared { namespace Graphics { namespace md5 {

using std::cout;
using std::cerr;
using std::endl;


/////////////////////////////////////////////////////////////////////////////
//
// Global shader related functions.
//
/////////////////////////////////////////////////////////////////////////////

static GLboolean GLSLCapable = GL_FALSE;

// --------------------------------------------------------------------------
// hasShaderSupport
//
// Return true if the host has GLSL, so that we can use shaders.
// --------------------------------------------------------------------------
GLboolean hasShaderSupport () {
  return GLSLCapable;
}

// --------------------------------------------------------------------------
// checkExtensionPresence
//
// Check if an extension is present on the host OpenGL implementation.
// Increment @missing if the extension is missing.
// --------------------------------------------------------------------------
static void checkExtensionPresence (const string &name, int &missing) {
  if (!glewIsSupported (name.c_str ()))
    {
      cerr << "* missing " << name << " extension" << endl;
      missing++;
    }
}

// --------------------------------------------------------------------------
// initShaderHandling
//
// Initialize variables and extensions needed for using GLSL.  This
// function should be called before any shader usage (at application
// initialization for example).
// --------------------------------------------------------------------------
void initShaderHandling () {
  int missing = 0;

  // Check for extensions needed for GLSL support on host
  checkExtensionPresence ("GL_ARB_shader_objects", missing);
  checkExtensionPresence ("GL_ARB_shading_language_100", missing);
  checkExtensionPresence ("GL_ARB_vertex_shader", missing);
  checkExtensionPresence ("GL_ARB_fragment_shader", missing);

  // Disable GLSL if one extension is missing
  if (missing > 0)
    GLSLCapable = GL_FALSE;
  else
    GLSLCapable = GL_TRUE;
}


/////////////////////////////////////////////////////////////////////////////
//
// class Shader implementation.
//
/////////////////////////////////////////////////////////////////////////////

// --------------------------------------------------------------------------
// Shader::Shader
//
// Constructor.
// --------------------------------------------------------------------------
Shader::Shader (const string &filename)
  : _name (filename), _handle (0), _compiled (0) {
}

// --------------------------------------------------------------------------
// Shader::~Shader
//
// Destructor.  Destroy the shader handle.
// --------------------------------------------------------------------------
Shader::~Shader () {
  if (GLEW_VERSION_2_0)
    {
      if (glIsShader (_handle))
	glDeleteShader (_handle);
    }
  else
    {
      GLint type;
      glGetObjectParameterivARB (_handle, GL_OBJECT_TYPE_ARB, &type);

      if (GL_SHADER_OBJECT_ARB == type)
	glDeleteObjectARB (_handle);
    }
}

// -------------------------------------------------------------------------
// Shader::printInfoLog
//
// Print log info about a vertex or a fragment shader.
// -------------------------------------------------------------------------
void Shader::printInfoLog () const {
  GLint infologLength = 0;

  // First check for previous OpenGL errors...
  checkOpenGLErrors (__FILE__, __LINE__);

  // Get log's length
  if (GLEW_VERSION_2_0)
    glGetShaderiv (_handle, GL_INFO_LOG_LENGTH, &infologLength);
  else
    glGetObjectParameterivARB (_handle, GL_OBJECT_INFO_LOG_LENGTH_ARB,
			       &infologLength);

  // If log is empty, quit
  if (infologLength <= 1)
    return;

  try
    {
      GLchar *infoLog = new GLchar[infologLength];

      // Get the log...
      if (GLEW_VERSION_2_0)
	glGetShaderInfoLog (_handle, infologLength, NULL, infoLog);
      else
	glGetInfoLogARB (_handle, infologLength, NULL, infoLog);

      // ...and print it to standard output
      cout << "Shader \"" << _name << "\" InfoLog ("
	   << infologLength << "):" << endl << infoLog << endl;

      delete [] infoLog;
    }
  catch (std::bad_alloc &err)
    {
      cerr << "Error: memory allocation failed for shader info log"
	   << endl << " Reason: " << err.what () << endl;
    }
}

// -------------------------------------------------------------------------
// Shader::compile
//
// Create and compile the shader.
// -------------------------------------------------------------------------
void Shader::compile ()
  throw (std::runtime_error) {
  const GLchar *code = _code.c_str ();

  if (GLEW_VERSION_2_0)
    {
      // Create a shader object
      _handle = glCreateShader (shaderType ());

      // Upload shader code to OpenGL
      glShaderSource (_handle, 1, &code, NULL);

      // Compile shader
      glCompileShader (_handle);
      glGetShaderiv (_handle, GL_COMPILE_STATUS, &_compiled);
      printInfoLog ();

      // Check for success
      if (GL_FALSE == _compiled)
	throw std::runtime_error ("Compilation failed");
    }
  else
    {
      // Create a shader object
      _handle = glCreateShaderObjectARB (shaderType ());

      // Upload shader code to OpenGL
      glShaderSourceARB (_handle, 1, &code, NULL);

      // Compile shader
      glCompileShaderARB (_handle);
      glGetObjectParameterivARB (_handle, GL_OBJECT_COMPILE_STATUS_ARB, &_compiled);
      printInfoLog ();

      // Check for success
      if (GL_FALSE == _compiled)
	throw std::runtime_error ("Compilation failed");
    }
}

// -------------------------------------------------------------------------
// Shader::loadShaderFile
//
// Load shader's GLSL code from file.  The code is stored into the
// _code string member variable.
// -------------------------------------------------------------------------
void Shader::loadShaderFile (const string &filename)  throw (std::runtime_error) {
  // Open the file
  std::ifstream ifs (filename.c_str (), std::ios::in | std::ios::binary);

  if (ifs.fail ())
    throw std::runtime_error ("Couldn't open shader file: " + filename);

  // Read whole file into string
  _code.assign (std::istreambuf_iterator<char>(ifs),
		std::istreambuf_iterator<char>());

  // Close file
  ifs.close ();
}


/////////////////////////////////////////////////////////////////////////////
//
// class VertexShader implementation.
//
/////////////////////////////////////////////////////////////////////////////

// --------------------------------------------------------------------------
// VertexShader::VertexShader
//
// Constructor.  Read vertex shader code from file and compile it.
// --------------------------------------------------------------------------
VertexShader::VertexShader (const string &filename)
  : Shader (filename) {
  try
    {
      // Load shader code from file
      loadShaderFile (filename);

      // Compile the shader
      compile ();

      cout << "* Vertex shader \"" << _name << "\" compiled" << endl;
    }
  catch (std::runtime_error &err)
    {
      cerr << "Error: Faild to create vertex shader from " << _name << endl;
      cerr << "Reason: " << err.what () << endl;
    }
}

/////////////////////////////////////////////////////////////////////////////
//
// class FragmentShader implementation.
//
/////////////////////////////////////////////////////////////////////////////

// --------------------------------------------------------------------------
// FragmentShader::FragmentShader
//
// Constructor.  Read fragment shader code from file and compile it.
// --------------------------------------------------------------------------
FragmentShader::FragmentShader (const string &filename)
  : Shader (filename) {
  try
    {
      // Load shader code from file
      loadShaderFile (filename);

      // Compile the shader
      compile ();

      cout << "* Fragment shader \"" << _name << "\" compiled" << endl;
    }
  catch (std::runtime_error &err)
    {
      cerr << "Error: Faild to create fragment shader from " << _name << endl;
      cerr << "Reason: " << err.what () << endl;
    }
}
/////////////////////////////////////////////////////////////////////////////
//
// class ShaderProgram implementation.
//
/////////////////////////////////////////////////////////////////////////////

// --------------------------------------------------------------------------
// ShaderProgram::ShaderProgram
//
// Constructor.  Link vertex and fragment shader.  If the vertex shader
// or the fragment shader is invalid (has failed to compile), the
// shader program creation is aborted.
// --------------------------------------------------------------------------

ShaderProgram::ShaderProgram (const string &filename,
			      const VertexShader &vertexShader,
			      const FragmentShader &fragmentShader)
  : _name (filename), _handle (0), _linked (0) {
  try {
      if (vertexShader.fail ())
	throw std::runtime_error ("Invalid vertex shader");

      if (fragmentShader.fail ())
	throw std::runtime_error ("Invalid fragment shader");

      if (GLEW_VERSION_2_0)
	{
	  // Create program and attach vertex and fragment shaders
	  _handle = glCreateProgram ();
	  glAttachShader (_handle, vertexShader.handle ());
	  glAttachShader (_handle, fragmentShader.handle ());

	  // Perform link stage
	  glLinkProgram (_handle);
	  glGetProgramiv (_handle, GL_LINK_STATUS, &_linked);

	  // Validate program
	  glValidateProgram (_handle);
	  printInfoLog ();

	  // Check for success
	  if (GL_FALSE == _linked)
	    throw std::runtime_error ("Link stage failed");
	}
      else
	{
	  // Create program and attach vertex and fragment shaders
	  _handle = glCreateProgramObjectARB ();
	  glAttachObjectARB (_handle, vertexShader.handle ());
	  glAttachObjectARB (_handle, fragmentShader.handle ());

	  // Perform link stage
	  glLinkProgramARB (_handle);
	  glGetObjectParameterivARB (_handle, GL_OBJECT_LINK_STATUS_ARB, &_linked);

	  // Validate program
	  glValidateProgramARB (_handle);
	  printInfoLog ();

	  // Check for success
	  if (GL_FALSE == _linked)
	    throw std::runtime_error ("Link stage failed");
	}

      cout << "* Shader \"" << _name << "\" successfully linked" << endl;
    }
  catch (std::runtime_error &err)
    {
      cerr << "Error: Faild to create shader " << _name << endl;
      cerr << "Reason: " << err.what () << endl;
    }
}

// --------------------------------------------------------------------------
// ShaderProgram::~ShaderProgram
//
// Destructor.  Destroy the shader program handle.
// --------------------------------------------------------------------------
ShaderProgram::~ShaderProgram () {
  if (GLEW_VERSION_2_0)
    {
      if (glIsProgram (_handle))
	glDeleteProgram (_handle);
    }
  else
    {
      GLint type;
      glGetObjectParameterivARB (_handle, GL_OBJECT_TYPE_ARB, &type);

      if (GL_PROGRAM_OBJECT_ARB == type)
	glDeleteObjectARB (_handle);
    }
}


// -------------------------------------------------------------------------
// ShaderProgram::use
// ShaderProgram::unuse
//
// Bind/unbind the shader.
// -------------------------------------------------------------------------
void ShaderProgram::use () const {
  if (GLEW_VERSION_2_0)
    glUseProgram (_handle);
  else
    glUseProgramObjectARB (_handle);
}

void ShaderProgram::unuse () const {
  if (GLEW_VERSION_2_0)
    glUseProgram (0);
  else
    glUseProgramObjectARB (0);
}

// -------------------------------------------------------------------------
// ShaderProgram::printInfoLog
//
// Print log info about a shader program.
// -------------------------------------------------------------------------
void ShaderProgram::printInfoLog () const {
  GLint infologLength = 0;

  // First check for previous OpenGL errors...
  checkOpenGLErrors (__FILE__, __LINE__);

  // Get log's length
  if (GLEW_VERSION_2_0)
    glGetProgramiv (_handle, GL_INFO_LOG_LENGTH, &infologLength);
  else
    glGetObjectParameterivARB (_handle, GL_OBJECT_INFO_LOG_LENGTH_ARB,
			       &infologLength);


  // If log is empty, quit
  if (infologLength <= 1)
    return;

  try
    {
      GLchar *infoLog = new GLchar[infologLength];

      // Get the log...
      if (GLEW_VERSION_2_0)
	glGetProgramInfoLog (_handle, infologLength, NULL, infoLog);
      else
	glGetInfoLogARB (_handle, infologLength, NULL, infoLog);

      // ...and print it to standard output
      cout << "Program \"" << _name << "\" InfoLog ("
	   << infologLength << "):" << endl << infoLog << endl;

      delete [] infoLog;
    }
  catch (std::bad_alloc &err)
    {
      cerr << "Error: memory allocation failed for shader program "
	   << "info log" << endl << " Reason: " << err.what () << endl;
    }
}

}}} //end namespace
