// ---------------------------------------------------------------------------
// 
//   Lily Engine alpha
// 
//   Copyright (c) 2010 Clifford Jolly
//     http://cliffjolly.com
//     cliff@cliffjolly.com
// 
// ---------------------------------------------------------------------------
// 
//   This program is free software; you can redistribute it and/or modify
//   it under the terms of the GNU General Public License as published by
//   the Free Software Foundation; either version 2 of the License, or
//   (at your option) any later version.
//  
//   This program is distributed in the hope that it will be useful, but
//   WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
//   General Public License for more details.
// 
//   You should have received a copy of the GNU General Public License
//   along with this program; if not, write to the Free Software
//   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
//   USA
// -------------------------- END HEADER -------------------------------------

#include <sstream>
#include <string>
#include <vector>
using namespace std;

#include "../hacks/winhacks.h"

#include <GL/glew.h>
#include <GL/gl.h>
#include <GL/glext.h>

#include "../console/console.h"
using namespace Console;

#include "../filesystem/filesystem.h"
#include "texturemanager.h"
#include "metatexture.h"


MetaTexture::MetaTexture(void) {
	sizesCopied = false;
	vertShaderID = 0;
	fragShaderID = 0;
	programID = 0;
	shaderError = false;

}

MetaTexture::~MetaTexture(void) {

	if(glUseProgramObjectARB) {

		if(vertShaderID) {
			if(glDeleteObjectARB) glDeleteObjectARB(vertShaderID);
		}
	
		if(fragShaderID) {
			if(glDeleteObjectARB) glDeleteObjectARB(fragShaderID);
		}
	
		if(programID) {
			if(glDeleteObjectARB) glDeleteObjectARB(programID);
		}
	}
}

static bool checkShaderCompileError(const std::string &fileName, int shader) {

	GLint success;
	glGetObjectParameterivARB(shader, GL_OBJECT_COMPILE_STATUS_ARB, &success);
	
	if(!success) {
		
		out("error") << "Shader compile error (" << fileName << "): " << endl;
		
		char *errBuf = new char[1024];
		int realLen;
		
		glGetInfoLogARB(shader, 1023, &realLen, errBuf);
		
		out("error") << errBuf << endl;
		
		delete[] errBuf;
		
		return false;
		
	}
		
	return true;

}

void MetaTexture::startLoad(const std::string &fileName) {
	
	// Nuke any shader programs here already.
	textureDeps.clear();
	sizesCopied = false;
	textureUniforms.clear();
	
	if(vertShaderID) {
		if(glDeleteObjectARB) glDeleteObjectARB(vertShaderID);
		vertShaderID = 0;
	}
	
	if(fragShaderID) {
		if(glDeleteObjectARB) glDeleteObjectARB(fragShaderID);
		fragShaderID = 0;
	}
	
	this->name = fileName;
	int fileLength;
	char *metaTexData;
	
	metaTexData = FileSystem::loadFile(fileName, &fileLength, true);

	string path = FileSystem::getParentName(fileName);
	
	istringstream inStr(string(metaTexData), istringstream::in);
	
	string vertShaderFileName;
	string fragShaderFileName;
	vector<string> allTexNames;
	string texName;
	
	inStr >> vertShaderFileName;
	inStr >> fragShaderFileName;
	
	vertShaderFileName = path + "/" + vertShaderFileName;
	fragShaderFileName = path + "/" + fragShaderFileName;
	
	do {
		texName = "";
		inStr >> texName;
		
		if(texName.size()) {
			allTexNames.push_back(path + "/" + texName);
		}
		
	} while(texName.size());
	
	TextureManager *manager = TextureManager::get();
	
	for(unsigned int i = 0; i < allTexNames.size(); i++) {		
		textureDeps.push_back(manager->getTextureIndexByName(allTexNames[i]));
	}
	
	delete[] metaTexData;
	
	if(!glUseProgramObjectARB) {
		// Ugly hack alert! Bail out now if we don't support shaders.
		return;
	}

	// Load vertex shader.
	metaTexData = FileSystem::loadFile(vertShaderFileName, &fileLength, true);
	
	if(metaTexData) {
		
		GLuint vertShader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
		glShaderSourceARB(vertShader, 1, (const GLchar**)&metaTexData, NULL);
		glCompileShaderARB(vertShader);
		delete[] metaTexData;
		
		if(checkShaderCompileError(vertShaderFileName, vertShader)) {
			vertShaderID = vertShader;
		} else {
			shaderError = true;
		}
		
	} else {
		
		out("error") << "Could not load file for vertex shader: " << vertShaderFileName << endl;
		
		shaderError = true;
	}
	
	// Load fragment shader.
	metaTexData = FileSystem::loadFile(fragShaderFileName, &fileLength, true);
	
	if(metaTexData) {
		
		GLuint fragShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
		glShaderSourceARB(fragShader, 1, (const GLchar**)&metaTexData, NULL);
		glCompileShaderARB(fragShader);
		delete[] metaTexData;
		
		if(checkShaderCompileError(fragShaderFileName, fragShader)) {
			fragShaderID = fragShader;
		} else {
			shaderError = true;
		}

	} else {
		
		out("error") << "Could not load file for fragment shader: " << fragShaderFileName << endl;
		
		shaderError = true;
	}

	if(vertShaderID && fragShaderID) {
		
		GLuint program = glCreateProgramObjectARB();
		glAttachObjectARB(program, vertShaderID);
		glAttachObjectARB(program, fragShaderID);
		glLinkProgramARB(program);
		glUseProgramObjectARB(program);
		
		// TODO: Check errors?
		programID = program;
	
		if(checkShaderCompileError("linked program", program)) {
	
			for(unsigned int i = 0; i < 8; i++) {
			
				ostringstream str;
				str << "textureSampler" << i;
				
				int uniformLoc = glGetUniformLocationARB(program, str.str().c_str());
				
				textureUniforms.push_back(uniformLoc);
			}
		
		} else {
			
			shaderError = true;
			
		}
		
		glUseProgramObjectARB(0);
	}
	
}

void MetaTexture::backgroundLoad(void) {
    backgroundLoadFinished = true;
}

void MetaTexture::waitForLoad(void) {
	
	TextureManager *manager = TextureManager::get();

	// Wait for all the dependencies to load.
	for(unsigned int i = 0; i < textureDeps.size(); i++) {
		Texture *tex = manager->getTextureByIndex(textureDeps[i]);
		tex->waitForLoad();
		tex->getGLTextureNumber();
	}
	
	finishLoad();
}

bool MetaTexture::isLoaded(void) {
	
	TextureManager *manager = TextureManager::get();
	
	// Check all dependencies. (ANDed together.)
	for(unsigned int i = 0; i < textureDeps.size(); i++) {
		Texture *tex = manager->getTextureByIndex(textureDeps[i]);
		if(!tex || !tex->isLoaded()) {
			return false;
		}
	}
	
	finishLoad();
	
	if(!sizesCopied) return false;
	
	return true;
	
}

void MetaTexture::bindTexture(void) {
	
	// TODO: We need stricter nearest neighbor filtering setup. Maybe.
	
	TextureManager *manager = TextureManager::get();
	waitForLoad();
	
	if(!glUseProgramObjectARB || !manager->getShadersEnabled() || shaderError) {
		
		// Ugly fallback for things that don't support shaders, or are
		// stupid and crappy about it.
		
		if(textureDeps.size() && textureDeps[0] != -1) {

			Texture *tex = manager->getTextureByIndex(textureDeps[0]);

			if(tex) {

				tex->bindTexture();

			} else {

				glBindTexture(GL_TEXTURE_2D, 0);
				glDisable(GL_TEXTURE_2D);
			}

		} else {
			
			glBindTexture(GL_TEXTURE_2D, 0);
			glDisable(GL_TEXTURE_2D);
		}
		

		return;
	}

	glEnable(GL_TEXTURE_2D);

	glUseProgramObjectARB(programID);

	for(unsigned int i = 0; i < textureDeps.size() && i < textureUniforms.size(); i++) {
		
		// TODO: Get the uniform location on load/init.
		// TODO: Add time scaling properties.
		glUniform1fARB(glGetUniformLocationARB(programID, "time"), float(SDL_GetTicks()) / 1000.0);
		
		if(textureUniforms[i] != -1) {
			
			Texture *tex = manager->getTextureByIndex(textureDeps[i]);
			
			if(tex) {
				
				int texNum = tex->getGLTextureNumber();
				
				glUniform1iARB(textureUniforms[i], i);
				
				// Someone kill me now. There must be a better way than this.				
				glActiveTexture(GL_TEXTURE0 + i);
				glBindTexture(GL_TEXTURE_2D, texNum);
				
				// Be careful with this. It binds the texture too.
				tex->setFilter(getFilter());
				
			}
		}
		
	}
	
	glActiveTexture(GL_TEXTURE0);
	
}

Image *MetaTexture::getOriginalImage(void) {
	
	if(textureDeps.size()) {
		
		TextureManager *manager = TextureManager::get();
		Texture *tex = manager->getTextureByIndex(textureDeps[0]);
		finishLoad();
		return tex->getOriginalImage();
	}
	
	return NULL;
}

void MetaTexture::finishLoad(void) {
	
	if(!sizesCopied) {
		
		if(textureDeps.size()) {
			
			TextureManager *manager = TextureManager::get();
			Texture *tex = manager->getTextureByIndex(textureDeps[0]);
			
			if(tex->isLoaded() && tex->hasGLTexture()) {
				
				pow2Height = tex->getAdjustedHeight();
				pow2Width  = tex->getAdjustedWidth();
				origHeight = tex->getOrigHeight();
				origWidth  = tex->getOrigWidth();
				
				sizesCopied = true;
			}
			
		} else {
		
			pow2Height = 
			pow2Width  = 
			origHeight = 
			origWidth  = 0;
			
		}
	}
}

