// OpenGL support functions.
// Written by Nils Liaaen Corneliusen.
// License: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication license
// https://www.ignorantus.com

#include <GL/glew.h>
#include <GL/gl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <fcntl.h>
#include <assert.h>
#include <string.h>

void initBuffer( GLuint *buffer, GLenum target, int size, void *src )
{
	glGenBuffers( 1, buffer );
	glBindBuffer( target, *buffer );
	glBufferData( target, size, src, GL_DYNAMIC_DRAW );
	glUnmapBuffer( target );
}

void copyToBuffer( GLenum target, GLuint buffer, void *src, int size, GLint bufMask )
{
	glBindBuffer( target, buffer );
	void *dst = glMapBufferRange( target, 0, size, bufMask );
	memcpy( dst, src, size );
	glUnmapBuffer( target );
}

void copyFromBuffer( GLenum target, GLuint buffer, void *dst, int size, GLint bufMask )
{
	glBindBuffer( target, buffer );
	void *src = glMapBufferRange( target, 0, size, bufMask );
	memcpy( dst, src, size );
	glUnmapBuffer( target );
}

static GLuint CompileShader( GLenum type, const char *shaderSrc, const char *shaderName )
{
	GLuint shader;

	shader = glCreateShader( type );
	if( shader == 0 ) {
		printf( "glCreateShader failed\n" );
		return 0;
	}

	glShaderSource( shader, 1, &shaderSrc, NULL );

	glCompileShader( shader );

	char log[1024];

	GLint success = GL_FALSE;
	glGetShaderiv( shader, GL_COMPILE_STATUS, &success );

	glGetShaderInfoLog( shader, sizeof(log), NULL, log );
	if( log[0] ) {
		printf( "Shader compilation log for %s:\n", shaderName  );
		puts( log );
	}

	if( !success ) return 0;

	return shader;
}

GLuint loadGraphicsProgram( const char *vertSrc, const char *vertName, const char *fragSrc, const char *fragName )
{
	GLuint vertShader = 0;
	GLuint fragShader = 0;

	const char *vname = vertName ? vertName : "default vertex";
	const char *fname = fragName ? fragName : "default fragment";

	if( vertSrc ) vertShader = CompileShader( GL_VERTEX_SHADER,   vertSrc, vname );
	if( fragSrc ) fragShader = CompileShader( GL_FRAGMENT_SHADER, fragSrc, fname );

	if( (vertSrc != NULL && vertShader == 0)  || (fragSrc != NULL && fragShader == 0) ) return 0;

	GLuint program = glCreateProgram();
	if( program == 0 ) return 0;

	if( vertShader ) glAttachShader( program, vertShader );
	if( fragShader ) glAttachShader( program, fragShader );

	glLinkProgram ( program );

	char log[1024];

	GLint success = GL_FALSE;
	glGetProgramiv( program, GL_LINK_STATUS, &success );

	glGetProgramInfoLog( program, sizeof(log), NULL, log );
	if( log[0] ) {
		printf( "Program linking log for %s and %s:\n", vname, fname  );
		puts( log );
	}

	if( !success ) return 0;

	glValidateProgram( program );

	success = GL_FALSE;
	glGetProgramiv( program, GL_VALIDATE_STATUS, &success );

	glGetProgramInfoLog( program, sizeof(log), NULL, log );
	if( log[0] ) {
		printf( "Validation log for %s and %s:\n", vname, fname );
		puts( log );
	}

	if( !success ) return 0;

	glDeleteShader( vertShader );
	glDeleteShader( fragShader );

	return program;
}

GLuint loadComputeProgram( const char *compSrc, const char *compName )
{
	const char *cname = compName ? compName : "default compute";

	GLuint compShader = CompileShader( GL_COMPUTE_SHADER,  compSrc, compName );

	if( compShader == 0 ) return 0;

	GLuint program = glCreateProgram();
	if( program == 0 ) return 0;

	glAttachShader( program, compShader );

	glLinkProgram ( program );

	char log[1024];

	GLint success = GL_FALSE;
	glGetProgramiv( program, GL_LINK_STATUS, &success );

	glGetProgramInfoLog( program, sizeof(log), NULL, log );
	if( log[0] ) {
		printf( "Program linking log for %s:\n", cname );
		puts( log );
	}

	if( !success ) return 0;

	glValidateProgram( program );

	success = GL_FALSE;
	glGetProgramiv( program, GL_VALIDATE_STATUS, &success );

	glGetProgramInfoLog( program, sizeof(log), NULL, log );
	if( log[0] ) {
		printf( "Validation log for %s:\n", cname );
		puts( log );
	}

	if( !success ) return 0;

	glDeleteShader( compShader );

	return program;
}
