/*
   model/material.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>

#include "auxiliary/functions.h"
#include "filesystem/filestream.h"
#include "math/functions.h"
#include "model/material.h"
#include "sys/sys.h"

namespace model
{

Material::LoaderFuncPtr 	Material::material_loaderfunc = 0;
Material::Registry 		Material::material_registry;

Material::Material(const std::string &name) :
		material_name(name),
		material_color(1.0f) ,
		material_size(64.0f, 64.0f)
{
	aux::to_lowercase(material_name);
	material_flags = 0;
	material_texture_id = 0;

}

Material::~Material()
{
}

void Material::set_color(const math::Color &color)
{
	material_color.assign(color);
	material_color.a = 1.0f;
}

void Material::set_texture(const std::string &texture)
{
	if (texture.size()) {
		set_flags(Texture);
		material_texture.assign(texture);
		if (material_loaderfunc) {
			material_loaderfunc(this);
		}
	} else {
		unset_flags(Texture);
		material_texture.clear();
		material_texture_id = 0;
	}
}

void Material::set_texture_id(const size_t texture_id)
{
	material_texture_id = texture_id;
}

void Material::set_size(const float width, const float height)
{
	material_size.assign(width, height);
}

void Material::set_size(const math::Vector2f &size)
{
	material_size.assign(size);
}

void Material::init()
{
	con_print << "^BInitializing materials..." << std::endl;

	filesystem::IFileStream shaderlist("materials/shaderlist.txt");
	if (!shaderlist.is_open()) {
		con_warn << "Could not open " << shaderlist.name() << std::endl;
		return;
	}

	con_debug << "  " << shaderlist.name() << std::endl;

	char line[1024];
	while (shaderlist.getline(line, 1023)) {
		if ((line[0] == 0) || (line[0] == '#') || (line[0] == ';')) {
			continue;
			if ((line[0] == '/') && (line[1] == '/'))
				continue;
		} else {
			std::string s(line);
			aux::trim(s);
			load_shader(s);
		}
	}

	shaderlist.close();
}

void Material::add(Material *material)
{
	if (!find(material->name())) {
		material_registry[material->name()] = material;
	}
}

void Material::load_shader(const std::string &shadername)
{
	std::string shaderfilename("materials/");
	shaderfilename.append(shadername);
	shaderfilename.append(".shader");

	filesystem::IFileStream shaderfile(shaderfilename);
	if (!shaderfile.is_open()) {
		con_warn << "Could not open " << shaderfile.name() << std::endl;
		return;
	}

	int parselevel = 0;
	unsigned int linenumber = 0;
	char line[1024];
	unsigned int count = 0;
	float r, g, b;
	Material *material = 0;

	while (shaderfile.getline(line, 1023)) {
		linenumber++;

		// read materials
		std::string s(line);
		aux::trim(s);

		// skip empty lines
		if (!s.size())
			continue;

		// skip comments
		if ((s[0] == '#') || (s[0] == ';') || ((s[0] == '/') && (s[1] == '/')))
			continue;

		std::istringstream linestream(s);
		std::string firstword;

		if (linestream >> firstword) {
			if (firstword.compare("//") == 0) {
				continue;

			} else if (firstword.compare("}") == 0) {
				parselevel--;

			} else if (firstword.compare("{") == 0) {
				parselevel++;

			} else if ((firstword.size()) && (parselevel == 0)) {

				material = find(firstword);
				if (material) {
					con_warn << "Duplicate material '" << firstword << "'" << std::endl;
				} else {
					material = new Material(firstword);
					add(material);
					count++;
					//con_debug << "  " << firstword << std::endl;
				}

			} else if ((parselevel == 1) && (material)) {
				aux::to_lowercase(firstword);
				if (firstword.compare("color") == 0) {
					if (linestream >> r >> g >> b) {
						if (math::max(r, math::max(g, b)) > 1.0f) {
							r /= 255.0f;
							g /= 255.0f;
							b /= 255.0f;
						}
						material->set_color(math::Color(r, g, b, 1.0f));
					}
				} else if (firstword.compare("engine") == 0) {
					material->set_flags(Engine);
				} else if (firstword.compare("bright") == 0) {
					material->set_flags(Bright);
				} else if (firstword.compare("environment") == 0) {
					material->set_flags(Environment);
				} else if (firstword.compare("entity") == 0) {
					material->set_flags(Primary);
				} else if (firstword.compare("entitysecond") == 0) {
					material->set_flags(Secondary);
				} else if (firstword.compare("entitythird") == 0) {
					material->set_flags(Tertiary);
				} else if (firstword.compare("ignore") == 0) {
					material->set_flags(Ignore);
				} else if (firstword.compare("qer_editorimage") == 0) {
					continue;
				} else if (firstword.compare("qer_trans") == 0) {
					continue;
				} else if (firstword.compare("texture") == 0) {

					// texture name should not contain spaces
					if (linestream >> firstword) {

						// remove extension
						if (firstword[firstword.size()-4] == '.') {
							firstword.erase(firstword.size() - 4);
						}
						material->set_texture(firstword);
					} else {
						con_warn << shaderfile.name() << " texture key without filename at line " << linenumber << std::endl;
					}

				} else {
					con_warn << shaderfile.name() << " unknown key '" << firstword
					<< "' at line " << linenumber << std::endl;
				}
			}
		}
	}

	con_debug << "  " << shaderfile.name() << " " << count << " materials " << std::endl;

	shaderfile.close();
}

void Material::shutdown()
{
	clear();
}

void Material::clear()
{
	con_print << "^BClearing materials..." << std::endl;

	for (Registry::iterator i = material_registry.begin(); i != material_registry.end(); ++i) {
		delete(*i).second;
	}

	material_registry.clear();
}

void Material::list()
{
	for (Registry::iterator i = material_registry.begin(); i != material_registry.end(); ++i) {
		con_print << "  " << (*i).second->name() << std::endl;
	}
	con_print << material_registry.size() << " registered materials" << std::endl;
}

Material *Material::find(const std::string &name)
{
	for (Registry::iterator i = material_registry.begin(); i != material_registry.end(); ++i) {
		if ((*i).first.compare(name) == 0)
			return (*i).second;
	}
	return 0;
}

void Material::set_loader_func(LoaderFuncPtr func)
{
	material_loaderfunc = func;
}

}