//
//  Wumbo-Shader.cpp
//  Wumbo
//
//  Created by Wade McGillis on 11/23/13.
//  Copyright (c) 2013 Wade McGillis. All rights reserved.
//

#include "Wumbo-ShaderBuilder.h"
#include "Wumbo-Functions.h"
namespace Wumbo
{
	ShaderBuilder::ShaderBuilder()
	{
		hasFragmentShader = false;
		hasVertexShader = false;
		vertPath = NULL;
		fragPath = NULL;
	}
#if PLATFORM_APPLE
	bool ShaderBuilder::loadVertexShader(NSString *fname)
#else
	bool ShaderBuilder::loadVertexShader(char *fname)
#endif
	{
#if PLATFORM_APPLE
			vertPath = (char*)[fname UTF8String];
#else
			vertPath = (char*)fname;
#endif
		FILE *f = fopen(vertPath,"rb");
		if (f == NULL)
		{
			printf("Failed to load vertex shader: %s\n",vertPath);
			return false;
		}
		hasVertexShader = true;
		return true;
	}
#if PLATFORM_APPLE
	bool ShaderBuilder::loadFragmentShader(NSString *fname)
#else
	bool ShaderBuilder::loadFragmentShader(char *fname)
#endif
	{
#if PLATFORM_APPLE
		fragPath = (char*)[fname UTF8String];
#else
		fragPath = (char*)fname;
#endif
		FILE *f = fopen(fragPath,"rb");
		if (f == NULL)
		{
			printf("Failed to load fragment shader: %s\n",fragPath);
			//fclose(f);
			return false;
		}
		hasFragmentShader = true;
		return true;
	}

	Shader *ShaderBuilder::generate()
	{
		GLuint vertShader = 0, fragShader = 0;
		GLuint prog = glCreateProgram();
		if (hasVertexShader)
			if (!compileShader(&vertShader,GL_VERTEX_SHADER,vertPath))
			{
				printf("Vertex shader failed to compile.\n");
				return NULL;
			}
		if (hasFragmentShader)
			if (!compileShader(&fragShader,GL_FRAGMENT_SHADER,fragPath))
			{
				printf("Fragment shader failed to compile.\n");
				return NULL;
			}
		glAttachShader(prog, vertShader);
		glAttachShader(prog, fragShader);
		for(int i=0;i<attribNames.size();i++)
			glBindAttribLocation(prog, attribIDs.at(i), attribNames.at(i));
		attribNames.clear();
		attribIDs.clear();
		
		if (!linkProgram(prog))
		{
			printf("Shaders failed to link.\n");
			glDeleteShader(vertShader);
			glDeleteShader(fragShader);
			glDeleteProgram(prog);
			return NULL;
		}
		// EVERYTHING OKAY SO FAR
		if (vertShader) {
			glDetachShader(prog, vertShader);
			glDeleteShader(vertShader);
		}
		if (fragShader) {
			glDetachShader(prog, fragShader);
			glDeleteShader(fragShader);
		}
		Shader *newshader = new Shader();
		
		newshader->builderSetProgram(prog);
		for(int i=0;i<uniforms.size();i++)
			newshader->builderAddUniform(uniforms.at(i));
		printf("Shaders are A-OK!\n");
		return newshader;
	}
	void ShaderBuilder::addAttrib(GLuint attribID, const GLchar *attribName)
	{
		attribIDs.push_back(attribID);
		attribNames.push_back(attribName);
	}
	void ShaderBuilder::addUniform(const GLchar *uniformName)
	{
		uniforms.push_back(uniformName);
	}
	bool ShaderBuilder::linkProgram(GLuint program)
	{
		GLint status;
		glLinkProgram(program);
		
#if 1//defined(DEBUG)
		GLint logLength;
		glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
		if (logLength > 0) {
			GLchar *log = (GLchar *)malloc(logLength);
			glGetProgramInfoLog(program, logLength, &logLength, log);
			printf("Program link log:\n%s", log);
			free(log);
		}
#endif
		
		glGetProgramiv(program, GL_LINK_STATUS, &status);
		if (status == 0) {
			return false;
		}
		
		return true;
	}
	
	bool ShaderBuilder::compileShader(GLuint *shader, GLenum type, const char *path)
	{
		GLint status;
		char *contents;
		int len;
		GLchar *source;
		
		FILE *f = fopen(path,"rb");
		fseek(f,0,SEEK_END);
		len = ftell(f);
		fseek(f,0,SEEK_SET);
		contents = new char[len+1];
		fread(contents, len, 1, f);
		fclose(f);
		source = (GLchar *)contents;
		if (!source) {
			delete source;
			return false;
		}
		
		*shader = glCreateShader(type);
		glShaderSource(*shader, 1, (const GLchar**)&source, &len);
		glCompileShader(*shader);
		
#if 1//defined(DEBUG)
		GLint logLength;
		glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
		if (logLength > 0) {
			GLchar *log = (GLchar *)malloc(logLength);
			glGetShaderInfoLog(*shader, logLength, &logLength, log);
			printf("Shader compile log:\n%s", log);
			free(log);
		}
#endif
		
		glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
		if (status == 0) {
			glDeleteShader(*shader);
			delete source;
			return false;
		}
		delete source;
		return true;
	}
};