#include "graphics-pipeline_opengl.hpp"

#include <iostream>
#include <fstream>

using namespace std;

GraphicsPipeline_OpenGL::GraphicsPipeline_OpenGL(Viewport viewport) {
   this->viewport = viewport;
}

GraphicsPipeline_OpenGL::~GraphicsPipeline_OpenGL() {
}

void GraphicsPipeline_OpenGL::addVaryingAttribute(VaryingAttribType attribType, GLint size, GLenum type,
      size_t fieldOffset) {
   // TODO: Throw an exception instead
   if (type != GL_FLOAT && type != GL_UNSIGNED_INT) {
      cout << "Unknown shader program attribute type: " << type << endl;
      return;
   }

   VaryingAttribInfo attributeDesc;

   attributeDesc.attribType = attribType;
   attributeDesc.index = this->varyingAttribs.size();
   attributeDesc.size = size;
   attributeDesc.type = type;
   attributeDesc.fieldOffset = fieldOffset;

   this->varyingAttribs.push_back(attributeDesc);
}

void GraphicsPipeline_OpenGL::createPipeline(string vertShaderFile, string fragShaderFile) {
   shaderProgram = loadShaderProgram(vertShaderFile, fragShaderFile);

   this->numPoints = 0;
   glGenVertexArrays(1, &this->vao);
   glBindVertexArray(this->vao);

   vector<VaryingAttribInfo>::iterator it;
   for (it = this->varyingAttribs.begin(); it != this->varyingAttribs.end(); it++) {
      glEnableVertexAttribArray(it->index);

      glGenBuffers(1, &it->buffer);
      glBindBuffer(GL_ARRAY_BUFFER, it->buffer);

      switch (it->type) {
         case GL_FLOAT: {
            glVertexAttribPointer(it->index, it->size, it->type, GL_FALSE, 0, NULL);
            break;
         }
         case GL_UNSIGNED_INT: {
            glVertexAttribIPointer(it->index, it->size, it->type, 0, NULL);
            break;
         }
      }
   }
}

GLuint GraphicsPipeline_OpenGL::loadShaderProgram(string vertShaderFile, string fragShaderFile) {
   GLuint vs = loadShader(GL_VERTEX_SHADER, vertShaderFile);
   GLuint fs = loadShader(GL_FRAGMENT_SHADER, fragShaderFile);

   GLuint shader_program = glCreateProgram();
   glAttachShader(shader_program, vs);
   glAttachShader(shader_program, fs);

   glLinkProgram(shader_program);

   return shader_program;
}

GLuint GraphicsPipeline_OpenGL::loadShader(GLenum type, string file) {
   cout << "Loading shader from file " << file << endl;

   ifstream shaderFile(file);
   GLuint shaderId = 0;

   if (shaderFile.is_open()) {
      string line, shaderString;

      while (getline(shaderFile, line)) {
         shaderString += line + "\n";
      }
      shaderFile.close();
      const char* shaderCString = shaderString.c_str();

      shaderId = glCreateShader(type);
      glShaderSource(shaderId, 1, &shaderCString, NULL);
      glCompileShader(shaderId);

      cout << "Loaded successfully" << endl;
   }
   else {
      cout << "Failed to load the file" << endl;
   }

   return shaderId;
}