// load and update shader

#include "shader.h"

// Apple's annoying non-standard GL include location
#if defined(__APPLE__) || defined(MACOSX)
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif

#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>

using namespace std;

// info on vertex and fragment shaders
ShaderInfo vertInfo(ShaderInfo::VERTEX);
ShaderInfo fragInfo(ShaderInfo::FRAGMENT);

// GL enum code for low-level shader code
static int programEnum[ShaderInfo::NUM_SHADER_TYPES] = {
    GL_VERTEX_PROGRAM_ARB,
    GL_FRAGMENT_PROGRAM_ARB
};
static int glslEnum[ShaderInfo::NUM_SHADER_TYPES] = {
    GL_VERTEX_SHADER_ARB,
    GL_FRAGMENT_SHADER_ARB
};
// first few characters of file identifying low-level shader code
static char *shaderMagic[ShaderInfo::NUM_SHADER_TYPES] = {
    "!!ARBvp", 
    "!!ARBfp"
};

// create a new object, remembering type
ShaderInfo::ShaderInfo(ShaderInfo::ShaderType t)
    : type(t), glsl(0) 
{
    struct stat reset={0};
    time=reset;
}

// change shader file name
void ShaderInfo::setName(string n)
{
    name = n;
}

// read a vertex or fragment shader if it's changed since last update
void ShaderInfo::updateShader()
{
    GLint err;

    // nothing to do if name isn't set
    if (! name.length())
	return;

    // open shader file
    int fd = open(name.c_str(), O_RDONLY, 0);
    
    static bool openerr=false;
    if (fd == -1) {
	// report error just onece
	if (! openerr)
	    printf("error opening %s\n", name.c_str());
	openerr=true;
	return;
    }
    openerr = false;

    // check modification time (and file length)
    struct stat sb;
    fstat(fd,&sb);
    if (time.st_mtime != sb.st_mtime) {

	time = sb;		// remember new change time

	// load using memory mapping of file
	char *sh = static_cast<char*>(mmap(0,sb.st_size,PROT_READ,
					   MAP_FILE,fd,0));

	// low-level code recognized by string at start of file
	if (strncmp(sh,shaderMagic[type],strlen(shaderMagic[type])) == 0) {
	    // disable high-level shader
	    if (glsl) {
		glDeleteObjectARB(glsl);
		glsl=0;
	    }

	    // load and report errors
	    glProgramStringARB(programEnum[type], GL_PROGRAM_FORMAT_ASCII_ARB,
			       sb.st_size, sh);
	    glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB,&err);
	    if (err != -1) {
		string estr = 
		    (const char*)(glGetString(GL_PROGRAM_ERROR_STRING_ARB));
		printf("%s: %s\n",name.c_str(), estr.c_str());
	    }
	    // enable low-level shader
	    else 
		glEnable(programEnum[type]);
	}

	// high-level code
	else {
	    // disable low-level
	    glDisable(programEnum[type]);

	    // load and compile
	    if (! glsl)
		glsl = glCreateShaderObjectARB(glslEnum[type]);
	    GLint shlen = sb.st_size;
	    glShaderSourceARB(glsl, 1, (const GLchar**)(&sh), &shlen);
	    glCompileShaderARB(glsl);

	    glGetObjectParameterivARB(glsl, GL_OBJECT_COMPILE_STATUS_ARB, &err);
	    if (! err) {
		glGetObjectParameterivARB(glsl, GL_OBJECT_INFO_LOG_LENGTH_ARB,
					  &err);
		if (err > 1) {
		    GLchar log[err];
		    glGetInfoLogARB(glsl, err+1, 0, log);
		    printf("%s:\n%s\n", name.c_str(), log);
		}
		glDeleteObjectARB(glsl);
		glsl=0;
	    }

	    // link GLSL program object
	    glUseProgramObjectARB(0);
	    if (vertInfo.glsl || fragInfo.glsl) {
		GLhandleARB prog = glCreateProgramObjectARB();
		if (vertInfo.glsl)
		    glAttachObjectARB(prog, vertInfo.glsl);
		if (fragInfo.glsl)
		    glAttachObjectARB(prog, fragInfo.glsl);
		glLinkProgramARB(prog);

		// report link errors
		glGetObjectParameterivARB(glsl, GL_OBJECT_LINK_STATUS_ARB,
					  &err);
		if (! err) {
		    glGetObjectParameterivARB(glsl, 
					      GL_OBJECT_INFO_LOG_LENGTH_ARB,
					      &err);
		    if (err > 1) {
			GLchar log[err];
			glGetInfoLogARB(glsl, err+1, 0, log);
			printf("GLSL link:\n%s\n", name.c_str(), log);
		    }
		}
		else 
		    glUseProgramObjectARB(prog);
	    }
	}
	
	glutPostRedisplay();
	munmap(sh,sb.st_size);
    }
    close(fd);
}

// read and load shader update (set*name must have already been called)
extern "C"
void updateShaders()
{
    vertInfo.updateShader();
    fragInfo.updateShader();

}


