// -*- c++ -*-

#ifndef SHADER_H
#define SHADER_H

#include <stdio.h>
#include <string>
#include "pstring.h"
#include "util.h"

static uint pobject_in_use = 0;

class pShader {
public:
  pShader(const char *path, const char *main_body_vs,
          const char *main_body_fs = NULL)
  {init(path,main_body_vs,main_body_fs);}
  pShader()
  {
    validated = true;
    pobject = 0;
  }
# ifdef GL_VERTEX_SHADER
  GLint uniform_location(const char *name)
  {
    const GLint rv = ptr_glGetUniformLocation(pobject,name);
    if ( rv == -1 ) pError_Msg("Could not get uniform.");
    return rv;
  }
  // Note: Location is "generic attribute index"
  GLint attribute_location(const char *name)
  {return glGetAttribLocation(pobject,name);}
  GLint varying_location(const char *name)
  {return ptr_glGetVaryingLocationNV(pobject, name);}

private:

  GLuint shader_load
  (GLenum shader_type, const char *source_path, const char *main_body)
  {
    const GLuint sobject = glCreateShader(shader_type);
    pError_Check();
    if ( !sobject )
      {
        // Could not create shader, perhaps feature not supported.
        return 0;
      }

    FILE* const shader_h = fopen(source_path,"r");
    if ( !shader_h )
      {
        fprintf(stderr,"Shader source file %s could not be open.\n",
                source_path);
        return 0;
      }
    std::string shader_text;
    while ( !feof(shader_h) )
      {
        const int c = getc(shader_h);
        if ( c == EOF ) break;
        shader_text += c;
      }
    fclose(shader_h);
    shader_text += "void main() {\n";
    shader_text += main_body;
    shader_text += "}\n";
    const char *shader_text_lines = shader_text.c_str();
    glShaderSource(sobject,1,&shader_text_lines,NULL);
    glCompileShader(sobject);
    int rv;
    glGetShaderiv(sobject,GL_COMPILE_STATUS,&rv);
    int info_log_length;
    if ( !rv )
      {
        printf(" Compile status for %s: %d\n",main_body,rv);
        glGetShaderiv(sobject,GL_INFO_LOG_LENGTH,&info_log_length);
        char* const info_log = (char*) alloca(info_log_length+1);
        glGetShaderInfoLog(sobject,info_log_length+1,NULL,info_log);
        printf(" Info log:\n%s\n",info_log);
      }

    return sobject;
  }

public:
  uint init
  (const char *source_pathp, const char *main_body_vs,
   const char *main_body_fs = NULL)
  {
    validated = false;
    source_path = source_pathp;

    pobject = glCreateProgram();
    if ( pobject == 0 ) return 0;

    vs_object = shader_load(GL_VERTEX_SHADER,source_path,main_body_vs);
    pError_Check();
    if ( vs_object == 0 ) return 0;
    glAttachShader(pobject,vs_object);

    if ( main_body_fs )
      {
        fs_object = shader_load(GL_FRAGMENT_SHADER,source_path,main_body_fs);
        if ( fs_object == 0 ) return 0;
        glAttachShader(pobject,fs_object);
      }
    else
      {
        fs_object = 0;
      }

    pError_Check();
    glLinkProgram(pobject);
    pError_Check();

    GLint link_status;
    glGetProgramiv(pobject,GL_LINK_STATUS,&link_status);
    if ( !link_status )
      {
        printf(" Link status for %s:%s %d\n",
               source_path.s,main_body_vs, link_status);
        GLint info_log_length;
        glGetProgramiv(pobject,GL_INFO_LOG_LENGTH,&info_log_length);
        char* const prog_info_log = (char*) alloca(info_log_length+1);
        glGetProgramInfoLog(pobject,info_log_length+1,NULL,prog_info_log);
        printf(" Program Info log:\n%s\n",prog_info_log);
      }

    return pobject;
  }

  void print_active_attrib()
  {
    if ( !pobject ) return;
    int active_attributes, max_length;
    pError_Check();
    glGetProgramiv(pobject,GL_ACTIVE_ATTRIBUTES,&active_attributes);
    pError_Check();
    glGetProgramiv(pobject,GL_ACTIVE_ATTRIBUTE_MAX_LENGTH,&max_length);
    pError_Check();
    char* const buffer = (char*) alloca(max_length);
    for ( int i=0; i<active_attributes; i++ )
      {
        int size;
        GLenum type;
        glGetActiveAttrib(pobject,i,max_length,NULL,&size,&type,buffer);
        pError_Check();
        const int location = attribute_location(buffer);
        printf("A %2d: SIZE %2d  TYPE %6d  %s\n",location,size,type,buffer);
      }
  }
  void print_active_uniform()
  {
    if ( !pobject ) return;
    int active_attributes, max_length;
    glGetProgramiv(pobject,GL_ACTIVE_UNIFORMS,&active_attributes);
    glGetProgramiv(pobject,GL_ACTIVE_UNIFORM_MAX_LENGTH,&max_length);
    char* const buffer = (char*) alloca(max_length);
    for ( int i=0; i<active_attributes; i++ )
      {
        int size;
        GLenum type;
        glGetActiveUniform(pobject,i,max_length,NULL,&size,&type,buffer);
        printf("U %2d: SIZE %2d  TYPE %6d  %s\n",i,size,type,buffer);
      }
  }
  void print_active_varying()
  {
#ifndef GL_NV_transform_feedback
    return;
#else
    if ( !pobject ) return;
    int active_attributes, max_length;
    glGetProgramiv(pobject,GL_ACTIVE_VARYINGS_NV,&active_attributes);
    glGetProgramiv(pobject,GL_ACTIVE_VARYING_MAX_LENGTH_NV,&max_length);
    char* const buffer = (char*) alloca(max_length);
    for ( int i=0; i<active_attributes; i++ )
      {
        int size;
        GLenum type;
        ptr_glGetActiveVaryingNV(pobject,i,max_length,NULL,&size,&type,buffer);
        printf("V %2d: SIZE %2d  TYPE %6d  %s\n",i,size,type,buffer);
      }
#endif
  }
  void validate_once()
  {
    if ( validated ) return;
    validate();
  }
  void validate()
  {
    validated = true;
    glValidateProgram(pobject);
    int validate_status;
    glGetProgramiv(pobject,GL_VALIDATE_STATUS,&validate_status);
    printf(" Validate status for %s: %d\n",source_path.s,validate_status);
    int info_log_length;
    glGetProgramiv(pobject,GL_INFO_LOG_LENGTH,&info_log_length);
    char* const prog_info_log = (char*) alloca(info_log_length+1);
    glGetProgramInfoLog(pobject,info_log_length+1,NULL,prog_info_log);
    printf(" Validation log:\n%s\n",prog_info_log);
  }

  bool use()
  {
    if ( pobject_in_use == pobject ) return false;
    glUseProgram(pobject);
    pError_Check();
    pobject_in_use = pobject;
    return true;
  }

#else
  // OpenGL Before Vertex Shaders

  GLint uniform_location(const char *name) { return 0; }
  GLint attribute_location(const char *name) { return 0; }
  GLint varying_location(const char *name) { return 0; }
  uint init(const char *source_pathp, const char *main_body)
  {
    validated = false;
    source_path = source_pathp;
    pobject = 0;
    pobject_in_use = 0;
    return pobject;
  }
  void print_active_attrib() { }
  void print_active_uniform() { }
  void print_active_varying() { }
  void validate_once() { }
  void validate() { };
  bool use() { return false; }

#endif

  pString source_path;
  GLuint pobject;
  GLuint vs_object, fs_object;
  bool validated;
};



#endif