// shader handling
// Author: Supakorn "Jamie" Rassameemasmuang

#include "common.h"

#ifdef HAVE_GL

#include <fstream>
#include <sstream>
#include <vector>
#include <string>
#include <iostream>

#include "settings.h"
#include "fpu.h"
#include "shaders.h"

int GLSLversion;

GLuint compileAndLinkShader(std::vector<ShaderfileModePair> const& shaders,
                            std::vector<std::string> const& defineflags,
                            bool ssbo, bool interlock, bool compute, bool test)
{
  GLuint shader = glCreateProgram();
  std::vector<GLuint> compiledShaders;

  size_t n=shaders.size();
  for(size_t i=0; i < n; ++i) {
    GLint newshader=createShaderFile(shaders[i].first,shaders[i].second,
                                     defineflags,ssbo,interlock,compute,test);
    if(test && newshader == 0) return 0;
    glAttachShader(shader,newshader);
    compiledShaders.push_back(newshader);
  }

  glBindAttribLocation(shader,positionAttrib,"position");
  glBindAttribLocation(shader,normalAttrib,"normal");
  glBindAttribLocation(shader,materialAttrib,"material");
  glBindAttribLocation(shader,colorAttrib,"color");
  glBindAttribLocation(shader,widthAttrib,"width");

  fpu_trap(false); // Work around FE_INVALID
  glLinkProgram(shader);
  fpu_trap(settings::trap());

  for(size_t i=0; i < n; ++i) {
    glDetachShader(shader,compiledShaders[i]);
    glDeleteShader(compiledShaders[i]);
  }

  return shader;
}

GLuint createShader(const std::string& src, int shaderType,
                    const std::string& filename, bool ssbo, bool interlock,
                    bool compute, bool test)
{
  const GLchar *source=src.c_str();
  GLuint shader=glCreateShader(shaderType);
  glShaderSource(shader, 1, &source, NULL);
  glCompileShader(shader);

  GLint status;
  glGetShaderiv(shader, GL_COMPILE_STATUS, &status);

  if(status != GL_TRUE) {
    if(test) return 0;
    GLint length;

    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);

    std::vector<GLchar> msg(length);

    glGetShaderInfoLog(shader, length, &length, msg.data());

    size_t n=msg.size();
    for(size_t i=0; i < n; ++i)
      std::cerr << msg[i];

    std::cerr << std::endl << "GL Compile error" << std::endl;
    std::stringstream s(src);
    std::string line;
    unsigned int k=0;
    while(getline(s,line))
      std::cerr << ++k << ": " << line << std::endl;
    exit(-1);
  }
  return shader;
}

GLuint createShaderFile(std::string file, int shaderType,
                        std::vector<std::string> const& defineflags,
                        bool ssbo, bool interlock, bool compute, bool test)
{
  std::ifstream shaderFile;
  shaderFile.open(file.c_str());
  std::stringstream shaderSrc;

  shaderSrc << "#version " << GLSLversion << "\n";
#ifndef __APPLE__
  shaderSrc << "#extension GL_ARB_uniform_buffer_object : enable" << "\n";
#ifdef HAVE_SSBO
  if(ssbo) {
    shaderSrc << "#extension GL_ARB_shader_storage_buffer_object : enable" << "\n";
    shaderSrc << "#extension GL_ARB_shader_atomic_counters : enable" << "\n";
    if(interlock)
      shaderSrc << "#extension GL_ARB_fragment_shader_interlock : enable"
                << "\n";
    if(compute)
      shaderSrc << "#extension GL_ARB_compute_shader : enable" << "\n";
  }
#endif
#endif

  size_t n=defineflags.size();
  for(size_t i=0; i < n; ++i)
    shaderSrc << "#define " << defineflags[i] << "\n";

  if(shaderFile) {
    shaderSrc << shaderFile.rdbuf();
    shaderFile.close();
  } else {
    std::cerr << "Cannot read from shader file " << file << std::endl;
    exit(-1);
  }

  return createShader(shaderSrc.str(),shaderType,file,ssbo,interlock,compute,
                      test);
}
#endif
