/*
   render/textures.cc
   This file is part of the Osirion project and is distributed under
   the terms of the GNU General Public License version 2
*/

#include <string.h>

#include "render/render.h"
#include "render/gl.h"
#include "render/image.h"
#include "render/textures.h"
#include "render/tgafile.h"
#include "render/pngfile.h"
#include "render/jpgfile.h"
#include "render/state.h"

#include "sys/sys.h"
#include "core/application.h"

namespace render
{

std::map<std::string, size_t> Textures::registry;
GLuint Textures::textures[MAXTEXTURES];
math::Vector2f Textures::texture_size[MAXTEXTURES];

void material_loader_func(model::Material *material)
{
	Textures::material_loader(material);
}

void Textures::init()
{
	con_print << "^BLoading textures..." << std::endl;

	if (registry.size()) {
		clear();
	} else {
		memset(textures, 0, sizeof(textures));
	}

	// "no texture" bitmap
	load("textures/common/notex");

	// gui font
	if (!load("bitmaps/fonts/gui", false)) {
		con_error << "Essential file bitmaps/fonts/gui missing" << std::endl;
		core::application()->shutdown();
	}

	// crosshairs
	load("bitmaps/pointers/pointer");
	load("bitmaps/pointers/aim");
	load("bitmaps/pointers/center");
	load("bitmaps/pointers/control");
	load("bitmaps/pointers/target");

	model::Material::set_loader_func(Textures::material_loader);
}

void Textures::shutdown()
{
	model::Material::set_loader_func(0);
	clear();
}

void Textures::list()
{
	for (iterator it = registry.begin(); it != registry.end(); it++) {
		con_print << "  " << (*it).first << " " << (*it).second << std::endl;
	}
	con_print << registry.size() << " loaded textures" << std::endl;
}

void Textures::clear()
{
	for (size_t i = 0; i < MAXTEXTURES; i++) {
		if (textures[i]) {
			glDeleteTextures(1, &textures[i]);
		}
		texture_size[i].clear();
	}

	registry.clear();
	memset(textures, 0, sizeof(textures));
}

void Textures::unload(const std::string &name)
{
	iterator it = registry.find(name);
	if (it != registry.end()) {
		con_debug << "  unloading " << (*it).first << std::endl;
		size_t id = (*it).second;
		if (textures[id]) {
			glDeleteTextures(1, &textures[id]);
			textures[id] = 0;
			texture_size[id].clear();
		}
		registry.erase(it);
	}
}

void Textures::unload(const size_t id)
{
	// find in map
	for (iterator it = registry.begin(); it != registry.end(); it++) {
		if ((*it).second == id) {
			con_debug << "  unloading " << (*it).first << std::endl;
			size_t id = (*it).second;
			if (textures[id]) {
				glDeleteTextures(1, &textures[id]);
				textures[id] = 0;
			}
			registry.erase(it);
			break;
		}
	}
}

size_t Textures::load(const char *name, const bool filter)
{
	if (name)
		return load(std::string(name), filter);
	else
		return 0;
}

size_t Textures::load(const std::string &name, const bool filter)
{
	// check if it is already loaded
	iterator it = registry.find(name);
	if (it != registry.end())
		return (*it).second;

	// find first available texture
	size_t id = 0;
	while ((id < MAXTEXTURES) && (textures[id])) {
		id++;
	}
	if (id == MAXTEXTURES) {
		con_error << "Texture limit " << MAXTEXTURES << " exceeded!" << std::endl;
		registry[name] = 0;
		return 0;
	}

	std::string filename;
	Image *image = 0;

	if (!image) {
		// try the png version
		filename.assign(name);
		filename.append(".png");
		image = PNG::load(filename.c_str());
	}

	if (!image) {
		// try the tga version
		filename.assign(name);
		filename.append(".tga");
		image = TGA::load(filename.c_str());
	}

	if (!image) {
		// try the jpg version
		filename.assign(name);
		filename.append(".jpg");
		image = JPG::load(filename.c_str());
	}

	if (!image) {
		// add to the registry with id 0 (texture not found)
		con_warn << "Could not open texture " << name << std::endl;
		registry[name] = 0;
		return 0;
	}

	glGenTextures(1, &textures[id]);
	glBindTexture(GL_TEXTURE_2D, textures[id]);

	int texture_format;
	int texture_internalformat;

	if (filter) {
		// scaling functions
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		// 4 levels of mipmaps
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 4);

		if (r_mipmap->value()) {
			// hardware generated mipmaps (requires OpenGL 1.4)
			glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
		}

		// enable texture wrapping
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	} else {
		// scaling functions
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		// no mipmaps, base level only
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
		
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	}
	//glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

	if (image->channels() == 4) {
		texture_format = GL_RGBA;
		texture_internalformat = GL_RGBA8;
	} else {
		texture_format = GL_RGB;
		texture_internalformat = GL_RGB8;
	}

	if (filter && (r_mipmap->value() <= 0)) {
		gluBuild2DMipmaps(GL_TEXTURE_2D,
				  texture_internalformat, image->width(), image->height(),
				  texture_format, GL_UNSIGNED_BYTE, image->data());
	} else {
		glTexImage2D(GL_TEXTURE_2D, 0,
			     texture_internalformat, image->width(), image->height(), 0,
			     texture_format, GL_UNSIGNED_BYTE, image->data());
	}

	// add to the registry
	registry[name] = id;
	texture_size[id].assign((float) image->width(), (float) image->height());

	// delete image data
	delete image;

	return id;
}

size_t Textures::find(const std::string &name)
{
	size_t id = 0;
	iterator it = registry.find(name);
	if (it != registry.end())
		id = (*it).second;
	return id;
}

size_t Textures::bind(const char *name, const bool filter)
{
	if (name)
		return bind(std::string(name), filter);
	else
		return 0;
}

size_t Textures::bind(const std::string &name, const bool filter)
{
	size_t id = 0;
	iterator it = registry.find(name);
	if (it != registry.end()) {
		id = (*it).second;
		glBindTexture(GL_TEXTURE_2D, textures[id]);
	} else {
		id = load(name, filter);
	}

	return id;
}

size_t Textures::bind(const size_t texture, const bool filter)
{
	size_t id = texture;
	if (!textures[id])
		id = 0;

	glBindTexture(GL_TEXTURE_2D, textures[id]);
	return id;
}

void Textures::material_loader(model::Material *material)
{
	if ((material->flags() & model::Material::Texture) && (material->texture().size())) {
		size_t id = load(material->texture());
		material->set_texture_id(id);
		material->set_size(texture_size[id]);
	}
}

}