#include <boost/regex.hpp>

#include "array_callable.hpp"
#include "asserts.hpp"
#include "custom_object.hpp"
#include "foreach.hpp"
#include "formula.hpp"
#include "formula_profiler.hpp"
#include "graphics.hpp"
#include "json_parser.hpp"
#include "level.hpp"
#include "module.hpp"
#include "shaders.hpp"
#include "variant_utils.hpp"
#if defined(WIN32)
#include "win_profile_timer.hpp"
#endif

#define WRITE_LOG(_a,_b) if( !(_a) ) { std::ostringstream _s; _s << __FILE__ << ":" << __LINE__ << " ASSERTION FAILED: " << _b << "\n"; std::cerr << _s.str(); return; }

namespace gles2 {

namespace {
std::string current_error;
}

void shader::set_runtime_error(const std::string& msg)
{
	current_error = msg;
	if(msg == "") {
		current_error = "UNKNOWN SHADER ERROR";
	}
}

std::string shader::get_and_clear_runtime_error()
{
	const std::string result = current_error;
	current_error = "";
	return result;
}

shader::shader(GLenum type, const std::string& name, const std::string& code)
	: type_(type), shader_(0), name_(name)
{
	ASSERT_LOG(compile(code), "Error compiling shader for " << name_);
}

bool shader::compile(const std::string& code)
{
	GLint compiled;
	if(shader_) {
		glDeleteShader(shader_);
		shader_ = 0;
	}

	shader_ = glCreateShader(type_);
	if(shader_ == 0) {
		std::cerr << "Enable to create shader." << std::endl;
		return false;
	}
	const GLchar* shader_code = reinterpret_cast<const GLchar*>(code.c_str());
	glShaderSource(shader_, 1, &shader_code, NULL);
	glCompileShader(shader_);
	glGetShaderiv(shader_, GL_COMPILE_STATUS, &compiled);
	if(!compiled) {
		GLint info_len = 0;
		glGetShaderiv(shader_, GL_INFO_LOG_LENGTH, &info_len);
		if(info_len > 1) {
			std::vector<char> info_log;
			info_log.resize(info_len);
			glGetShaderInfoLog(shader_, info_log.capacity(), NULL, &info_log[0]);
			std::string s(info_log.begin(), info_log.end());
			std::cerr << "Error compiling shader: " << s << std::endl;
		}
		glDeleteShader(shader_);
		shader_ = 0;
		return false;
	}
	return true;
}

namespace {
	std::map<std::string, gles2::program_ptr> shader_programs;
}

program::program() 
	: object_(0)
{
	environ_ = this;
}


program::program(const std::string& name, const shader& vs, const shader& fs)
	: object_(0)
{
	environ_ = this;
	init(name, vs, fs);
}

void program::init(const std::string& name, const shader& vs, const shader& fs)
{
	name_ = name;
	vs_ = vs;
	fs_ = fs;
	ASSERT_LOG(link(), "Error linking program: " << name_);
}

bool program::link()
{
	if(object_) {
		glDeleteProgram(object_);
		object_ = 0;
	}
	object_ = glCreateProgram();
	ASSERT_LOG(object_ != 0, "Unable to create program object.");
	glAttachShader(object_, vs_.get());
	glAttachShader(object_, fs_.get());
	glLinkProgram(object_);
	GLint linked = 0;
	glGetProgramiv(object_, GL_LINK_STATUS, &linked);
	if(!linked) {
		GLint info_len = 0;
		glGetProgramiv(object_, GL_INFO_LOG_LENGTH, &info_len);
		if(info_len > 1) {
			std::vector<char> info_log;
			info_log.resize(info_len);
			glGetProgramInfoLog(object_, info_log.capacity(), NULL, &info_log[0]);
			std::string s(info_log.begin(), info_log.end());
			std::cerr << "Error linking object: " << s << std::endl;
		}
		glDeleteProgram(object_);
		object_ = 0;
		return false;
	}
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	return queryUniforms() && queryAttributes();
}

GLuint program::get_attribute(const std::string& attr) const
{
	std::map<std::string, actives>::const_iterator it = attribs_.find(attr);
	ASSERT_LOG(it != attribs_.end(), "Attribute \"" << attr << "\" not found in list.");
	return it->second.location;
}

GLuint program::get_uniform(const std::string& attr) const
{
	std::map<std::string, actives>::const_iterator it = uniforms_.find(attr);
	//ASSERT_LOG(it != uniforms_.end(), "Uniform \"" << attr << "\" not found in list.");
	if(it == uniforms_.end()) {
		return 0xffffffffUL;
	}
	return it->second.location;
}

bool program::queryAttributes()
{
	GLint active_attribs;
	glGetProgramiv(object_, GL_ACTIVE_ATTRIBUTES, &active_attribs);
	GLint attributes_max_len;
	glGetProgramiv(object_, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &attributes_max_len);
	std::vector<char> name;
	name.resize(attributes_max_len+1);
	for(int i = 0; i < active_attribs; i++) {
		actives a;
		GLsizei size;
		glGetActiveAttrib(object_, i, name.size(), &size, &a.num_elements, &a.type, &name[0]);
		a.name = std::string(&name[0], &name[size]);
		a.location = glGetAttribLocation(object_, a.name.c_str());
		ASSERT_LOG(a.location >= 0, "Unable to determine the location of the attribute: " << a.name);
		attribs_[a.name] = a;
	}
	return true;
}

bool program::queryUniforms()
{
	GLint active_uniforms;
	glGetProgramiv(object_, GL_ACTIVE_UNIFORMS, &active_uniforms);
	GLint uniform_max_len;
	glGetProgramiv(object_, GL_ACTIVE_UNIFORM_MAX_LENGTH, &uniform_max_len);
	std::vector<char> name;
	name.resize(uniform_max_len+1);
	for(int i = 0; i < active_uniforms; i++) {
		actives u;
		GLsizei size;
		glGetActiveUniform(object_, i, name.size(), &size, &u.num_elements, &u.type, &name[0]);
		u.name = std::string(&name[0], &name[size]);
		u.location = glGetUniformLocation(object_, u.name.c_str());
		ASSERT_LOG(u.location >= 0, "Unable to determine the location of the uniform: " << u.name);
		uniforms_[u.name] = u;
	}
	return true;
}

variant program::get_uniform_value(const std::string& key) const
{
	std::map<std::string, actives>::const_iterator it = uniforms_.find(key);
	ASSERT_LOG(it != uniforms_.end(), "No uniform found with name: " << key);
	return it->second.last_value;
}

void program::set_uniform(const std::map<std::string,actives>::iterator& it, const variant& value)
{
	const actives& u = it->second;
	switch(u.type) {
	case GL_FLOAT: {
		glUniform1f(u.location, GLfloat(value.as_decimal().as_float()));
		break;
	}
	case GL_FLOAT_VEC2: {
		WRITE_LOG(value.num_elements() == 2, "Must be four(2) elements in vector.");
		std::vector<GLfloat> f;
		for(size_t n = 0; n < value.num_elements(); ++n) {
			f.push_back(GLfloat(value[n].as_decimal().as_float()));
		}
		glUniform2fv(u.location, u.num_elements, &f[0]);
		break;
	}
	case GL_FLOAT_VEC3: {
		WRITE_LOG(value.num_elements() == 3, "Must be three(3) elements in vector.");
		std::vector<GLfloat> f;
		for(size_t n = 0; n < value.num_elements(); ++n) {
			f.push_back(GLfloat(value[n].as_decimal().as_float()));
		}
		glUniform3fv(u.location, u.num_elements, &f[0]);
		break;
	}
	case GL_FLOAT_VEC4: {
		WRITE_LOG(value.num_elements() == 4, "Must be four(4) elements in vector.");
		std::vector<GLfloat> f;
		for(size_t n = 0; n < value.num_elements(); ++n) {
			f.push_back(GLfloat(value[n].as_decimal().as_float()));
		}
		glUniform4fv(u.location, u.num_elements, &f[0]);
		break;
	}
	case GL_INT:		glUniform1i(u.location, value.as_int()); break;
	case GL_INT_VEC2:	
		WRITE_LOG(value.num_elements() == 2, "Must be two(2) elements in vec.");
		glUniform2i(u.location, value[0].as_int(), value[1].as_int()); 
		break;
	case GL_INT_VEC3:	
		WRITE_LOG(value.num_elements() == 3, "Must be three(3) elements in vec.");
		glUniform3iv(u.location, u.num_elements, &value.as_list_int()[0]); 
		break;
	case GL_INT_VEC4: 	
		WRITE_LOG(value.num_elements() == 4, "Must be four(4) elements in vec.");
		glUniform4iv(u.location, u.num_elements, &value.as_list_int()[0]); 
		break;
	case GL_BOOL:		glUniform1i(u.location, value.as_bool()); break;
	case GL_BOOL_VEC2:	
		WRITE_LOG(value.num_elements() == 2, "Must be two(2) elements in vec.");
		glUniform2i(u.location, value[0].as_bool(), value[1].as_bool()); 
		break;
	case GL_BOOL_VEC3:	
		WRITE_LOG(value.num_elements() == 3, "Must be three(3) elements in vec.");
		glUniform3i(u.location, value[0].as_bool(), value[1].as_bool(), value[2].as_bool()); 
		break;
	case GL_BOOL_VEC4:
		WRITE_LOG(value.num_elements() == 4, "Must be four(4) elements in vec.");
		glUniform4i(u.location, value[0].as_bool(), value[1].as_bool(), value[2].as_bool(), value[3].as_bool()); 
		break;
	case GL_FLOAT_MAT2:	{
		WRITE_LOG(value.num_elements() == 4, "Must be four(4) elements in matrix.");
		std::vector<GLfloat> f;
		for(size_t n = 0; n < value.num_elements(); ++n) {
			f.push_back(GLfloat(value[n].as_decimal().as_float()));
		}
		glUniformMatrix2fv(u.location, u.num_elements, GL_FALSE, &f[0]);
		break;
	}
	case GL_FLOAT_MAT3: {
		WRITE_LOG(value.num_elements() == 9, "Must be nine(9) elements in matrix.");
		std::vector<GLfloat> f;
		for(size_t n = 0; n < value.num_elements(); ++n) {
			f.push_back(GLfloat(value[n].as_decimal().as_float()));
		}
		glUniformMatrix3fv(u.location, u.num_elements, GL_FALSE, &f[0]);
		break;
	}
	case GL_FLOAT_MAT4: {
		WRITE_LOG(value.num_elements() == 16, "Must be four(16) elements in matrix.");
		std::vector<GLfloat> f;
		for(size_t n = 0; n < value.num_elements(); ++n) {
			f.push_back(GLfloat(value[n].as_decimal().as_float()));
		}
		glUniformMatrix4fv(u.location, u.num_elements, GL_FALSE, &f[0]);
		break;
	}

	case GL_SAMPLER_2D:		glUniform1i(u.location, value.as_int()); break;

	case GL_SAMPLER_CUBE:
	default:
		WRITE_LOG(false, "Unhandled uniform type: " << it->second.type);
	}
}

void program::set_uniform_or_defer(const std::string& key, const variant& value)
{
	std::map<std::string, actives>::iterator it = uniforms_.find(key);
	WRITE_LOG(it != uniforms_.end(), "No uniform found with name: " << key);
	it->second.last_value = value;

	GLint cur_prog;
	glGetIntegerv(GL_CURRENT_PROGRAM, &cur_prog);
	if(cur_prog != get()) {
		uniforms_to_update_.push_back(key);
		return;
	}
	set_uniform(it, value);
}

namespace {
	class uniforms_callable : public game_logic::formula_callable 
	{
		program_ptr program_;
		variant get_value(const std::string& key) const 
		{
			//GLfloat f[16];
			//glGetUniformfv(program_->get(), program_->get_uniform(key), f);
			//GLenum err = glGetError();
			//ASSERT_LOG(err == GL_NONE, "glGetUniformfv OpenGL error: 0x" << std::hex << err);
			// XXX fixme to check type of uniform for number of elements to return.
			//return variant(f[0]);
			return program_->get_uniform_value(key);
		}
		void set_value(const std::string& key, const variant& value) 
		{
			program_->set_uniform_or_defer(key, value);
		}
	public:
		explicit uniforms_callable(const program& p) 
			: program_(const_cast<program*>(&p))
		{}
	};

	class attributes_callable : public game_logic::formula_callable 
	{
		program_ptr program_;
		variant get_value(const std::string& key) const 
		{
			return program_->get_attributes_value(key);
		}
		void set_value(const std::string& key, const variant& value) 
		{
			program_->set_attributes(key, value);
		}
	public:
		explicit attributes_callable(const program& p) 
			: program_(const_cast<program*>(&p))
		{}
	};
}

variant program::get_value(const std::string& key) const
{
#if defined(USE_GLES2)
	if(key == "uniforms") {
		return variant(new uniforms_callable(*this));
	} else if(key == "attributes") {
		return variant(new attributes_callable(*this));
	} else if(key == "alpha") {
		return variant(get_alpha());
	} else if(key == "color") {
		std::vector<variant> v;
		for(int n = 0; n < 4; ++n) {
			v.push_back(variant(get_color()[n]));
		}
		return variant(&v);
	} else if(key == "point_size") {
		//return variant(get_point_size());
		GLfloat pt_size;
		glGetFloatv(GL_POINT_SIZE, &pt_size);
		return variant(pt_size);
	} else if(key == "mvp_matrix" ) {
		std::vector<variant> v;
		for(size_t n = 0; n < 16; n++) {
			v.push_back(variant(((GLfloat*)(&gles2::get_mvp_matrix().x.x))[n]));
		}
		return variant(&v);
	}
#endif
	return variant();
}

void program::set_value(const std::string& key, const variant& value)
{
}

namespace {
	GLenum convert_mode(const std::string& smode)
	{
		if(smode == "points") {
			return GL_POINTS;
		} else if(smode == "lines") {
			return GL_LINES;
		} else if(smode == "line_strips") {
			return GL_LINE_STRIP;
		} else if(smode == "line_loop") {
			return GL_LINE_LOOP;
		} else if(smode == "triangles") {
			return GL_TRIANGLES;
		} else if(smode == "triangle_strip") {
			return GL_TRIANGLE_STRIP;
		} else if(smode == "triangle_fan") {
			return GL_TRIANGLE_FAN;
		}
		ASSERT_LOG(false, "Unexpected mode type: " << smode);
		return GL_POINTS;
	}

	GLenum get_blend_mode(variant v)
	{
		if(v.is_string()) {
			const std::string s = v.as_string();
			if(s == "zero") {
				return GL_ZERO;
			} else if(s == "one") {
				return GL_ONE;
			} else if(s == "src_color") {
				return GL_SRC_COLOR;
			} else if(s == "one_minus_src_color") {
				return GL_ONE_MINUS_SRC_COLOR;
			} else if(s == "src_alpha") {
				return GL_SRC_ALPHA;
			} else if(s == "one_minus_src_alpha") {
				return GL_ONE_MINUS_SRC_ALPHA;
			} else if(s == "dst_alpha") {
				return GL_DST_ALPHA;
			} else if(s == "one_minus_dst_alpha") {
				return GL_ONE_MINUS_DST_ALPHA;
			} 
			ASSERT_LOG(false, "Unrecognised blend mode (maybe needs adding): " << s);
		} else if(v.is_int()) {
			return v.as_int();
		}
		ASSERT_LOG(false, "Expected blend mode to be a string or integer");
		return GL_ZERO;
	}

	class get_mvp_matrix_function : public game_logic::function_expression
	{
	public:
		explicit get_mvp_matrix_function(const args_list& args)
		 : function_expression("get_mvp_matrix", args, 0, 0)
		{}
	private:
		variant execute(const game_logic::formula_callable& variables) const {
			game_logic::formula::fail_if_static_context();
			std::vector<variant> v;
#if defined(USE_GLES2)
			for(size_t n = 0; n < 16; n++) {
				v.push_back(variant(((GLfloat*)(&gles2::get_mvp_matrix().x.x))[n]));
			}
#endif
			return variant(&v);
		}
	};

	class draw_arrays_command : public game_logic::command_callable
	{
	public:
		explicit draw_arrays_command(GLenum mode, GLint first, GLsizei count)
			: mode_(mode), first_(first), count_(count)
		{}
		virtual void execute(formula_callable& ob) const
		{
			glDrawArrays(mode_, first_, count_);
		}
	private:
		GLenum mode_;
		GLint first_;
		GLsizei count_;
	};

	class draw_arrays_function : public game_logic::function_expression 
	{
	public:
		explicit draw_arrays_function(const args_list& args)
		 : function_expression("draw_arrays", args, 3, 3)
		{}
	private:
		variant execute(const game_logic::formula_callable& variables) const 
		{
			game_logic::formula::fail_if_static_context();
			GLenum mode;
			variant vmode = args()[0]->evaluate(variables);
			if(vmode.is_string()) {
				mode = convert_mode(vmode.as_string());
			} else if(vmode.is_int()) {
				mode = vmode.as_int();
			} else {
				ASSERT_LOG(false, "Unexpected type for mode argument: " << vmode.type());
			}
			return variant(new draw_arrays_command(mode, 
				args()[1]->evaluate(variables).as_int(), 
				args()[2]->evaluate(variables).as_int()));
		}
	};

	class draw_elements_command : public game_logic::command_callable
	{
	public:
		explicit draw_elements_command(GLenum mode, std::vector<GLshort>* indicies)
			: mode_(mode)
		{
			indicies_.swap(*indicies);
		}
		virtual void execute(formula_callable& ob) const
		{
			glDrawElements(mode_, indicies_.size(), GL_SHORT, &indicies_[0]);
		}
	private:
		GLenum mode_;
		std::vector<GLshort> indicies_;
	};

	class draw_elements_function : public game_logic::function_expression 
	{
	public:
		explicit draw_elements_function(const args_list& args)
		 : function_expression("draw_elements", args, 2, 2)
		{}
	private:
		variant execute(const game_logic::formula_callable& variables) const 
		{
			game_logic::formula::fail_if_static_context();
			GLenum mode;
			variant vmode = args()[0]->evaluate(variables);
			if(vmode.is_string()) {
				mode = convert_mode(vmode.as_string());
			} else if(vmode.is_int()) {
				mode = vmode.as_int();
			} else {
				ASSERT_LOG(false, "Unexpected type for mode argument: " << vmode.type());
			}
			variant ndxs = args()[1]->evaluate(variables);
			std::vector<GLshort> indicies;
			for(size_t n = 0; n < ndxs.num_elements(); ++n) {
				indicies.push_back(GLshort(ndxs[n].as_int()));
			}
			return variant(new draw_elements_command(mode, &indicies));
		}
	};

	class bind_texture_command : public game_logic::command_callable
	{
	public:
		explicit bind_texture_command(GLuint tex_id, GLuint active = 0)
			: tex_id_(tex_id), active_(active)
		{}
		virtual void execute(formula_callable& ob) const
		{
			glActiveTexture(GL_TEXTURE0 + active_);
			glBindTexture(GL_TEXTURE_2D, tex_id_);
		}
	private:
		GLuint tex_id_;
		GLuint active_;
	};

	class bind_texture_function : public game_logic::function_expression 
	{
	public:
		explicit bind_texture_function(const args_list& args)
		 : function_expression("bind_texture", args, 1, 2)
		{}
	private:
		variant execute(const game_logic::formula_callable& variables) const 
		{
			GLuint active_tex = args().size() > 1 ? args()[1]->evaluate(variables).as_int() : 0;
			return variant(new bind_texture_command(GLuint(args()[0]->evaluate(variables).as_int()), active_tex));
		}
	};

	class texture_callable : public game_logic::formula_callable
	{
	public:
		explicit texture_callable(const graphics::texture& tex): tex_(tex)
		{}
		variant get_value(const std::string& key) const
		{
			if(key == "id") {
				return variant(tex_.get_id());
			}
			return variant();
		}
	private:
		graphics::texture tex_;
	};

	class load_texture_function : public game_logic::function_expression 
	{
	public:
		explicit load_texture_function(const args_list& args)
		 : function_expression("load_texture", args, 1, 1)
		{}
	private:
		variant execute(const game_logic::formula_callable& variables) const 
		{
			const std::string filename = module::map_file(args()[0]->evaluate(variables).as_string());
			const graphics::texture tex = graphics::texture::get(filename);
			return variant(new texture_callable(tex));
		}
	};

	class blend_mode_command : public game_logic::command_callable
	{
	public:
		explicit blend_mode_command(GLenum src, GLenum dst)
			: src_(src), dst_(dst)
		{}
		virtual void execute(formula_callable& ob) const
		{
			glEnable(GL_BLEND);
			glBlendFunc(src_, dst_);
		}
	private:
		GLenum src_;
		GLenum dst_;
	};

	class blend_mode_function : public game_logic::function_expression 
	{
	public:
		explicit blend_mode_function(const args_list& args)
		 : function_expression("blend_mode", args, 2, 2)
		{}
	private:
		variant execute(const game_logic::formula_callable& variables) const 
		{
			const GLenum src = get_blend_mode(args()[0]->evaluate(variables));
			const GLenum dst = get_blend_mode(args()[1]->evaluate(variables));
			return variant(new blend_mode_command(src, dst));
		}
	};

	class shader_symbol_table : public game_logic::function_symbol_table
	{
	public:
		shader_symbol_table()
		{}

		game_logic::expression_ptr create_function(
			const std::string& fn,
			const std::vector<game_logic::expression_ptr>& args,
			const game_logic::formula_callable_definition* callable_def) const
		{
			if(fn == "get_mvp_matrix") {
				return game_logic::expression_ptr(new get_mvp_matrix_function(args));
			} else if(fn == "draw_arrays") {
				return game_logic::expression_ptr(new draw_arrays_function(args));
			} else if(fn == "draw_elements") {
				return game_logic::expression_ptr(new draw_elements_function(args));
			} else if(fn == "bind_texture") {
				return game_logic::expression_ptr(new bind_texture_function(args));
			} else if(fn == "load_texture") {
				return game_logic::expression_ptr(new load_texture_function(args));
			} else if(fn == "blend_mode") {
				return game_logic::expression_ptr(new blend_mode_function(args));
			}
			return function_symbol_table::create_function(fn, args, callable_def);
		}
	};

	game_logic::function_symbol_table& get_shader_symbol_table()
	{
		static shader_symbol_table table;
		return table;
	}
}

game_logic::formula_ptr program::create_formula(const variant& v)
{
	return game_logic::formula_ptr(new game_logic::formula(v, &get_shader_symbol_table()));
}

bool program::execute_command(const variant& var)
{
	bool result = true;
	if(var.is_null()) {
		return result;
	}

	if(var.is_list()) {
		const int num_elements = var.num_elements();
		for(int n = 0; n != num_elements; ++n) {
			if(var[n].is_null() == false) {
				result = execute_command(var[n]) && result;
			}
		}
	} else {
		game_logic::command_callable* cmd = var.try_convert<game_logic::command_callable>();
		if(cmd != NULL) {
			cmd->execute(*this);
		}
	}
	return result;
}

variant program::get_attributes_value(const std::string& key) const
{
	std::map<std::string, actives>::const_iterator it = attribs_.find(key);
	ASSERT_LOG(it != attribs_.end(), "No attribute found with name: " << key);
	return it->second.last_value;	
}

void program::set_attributes(const std::string& key, const variant& value)
{
	std::map<std::string, actives>::iterator it = attribs_.find(key);
	ASSERT_LOG(it != attribs_.end(), "No attribute found with name: " << key << ", prog: " << get());
	const actives& a = it->second;
	WRITE_LOG(a.type == GL_FLOAT || a.type == GL_FLOAT_VEC2 || a.type == GL_FLOAT_VEC3 || a.type == GL_FLOAT_VEC4, 
		"Attribute type must be float not: " << a.type);
	it->second.last_value = value;

	if(value.is_callable()) {
		boost::intrusive_ptr<game_logic::float_array_callable> f = value.try_convert<game_logic::float_array_callable>();
		boost::intrusive_ptr<game_logic::short_array_callable> s = value.try_convert<game_logic::short_array_callable>();
		if(f != NULL) {
			::glVertexAttribPointer(a.location, f->num_elements(), GL_FLOAT, GL_FALSE, 0, &(f->floats()[0]));
			//GLenum err = glGetError();
			//GLint prog, max_va;
			//glGetIntegerv(GL_CURRENT_PROGRAM, &prog);
			//glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &max_va);
			//ASSERT_LOG(err == GL_NONE, "Error in OpenGL: 0x" 
			//		<< std::hex << err << std::dec << " : " << key << " : " << a.location << " : " << a.type << " : " << f->num_elements()
			//		<< ", progs " << prog << " : " << get() << ", max vertex attribs: " << max_va);
		} else if(s != NULL) {
			::glVertexAttribPointer(a.location, s->num_elements(), GL_SHORT, GL_FALSE, 0, &(s->shorts()[0]));
			//GLenum err = glGetError();
			//ASSERT_LOG(err == GL_NONE, "Error in OpenGL: 0x" << std::hex << err << std::dec << " : " << key << " : " << a.location << " : " << a.type);
		} else {
			ASSERT_LOG(false, "Couldn't convert to float_array or short_array type: " << a.name);
		}
		::glEnableVertexAttribArray(a.location);
		//GLenum err = glGetError();
		//ASSERT_LOG(err == GL_NONE, "Error in OpenGL: 0x" << std::hex << err << std::dec << " : " << key << " : " << a.location << " : " << a.type);
		active_attributes_.push_back(a.location);
	} else {//if(a.num_elements == value.num_elements()) {
		// Probably just a constant. not an attrib array.
		if(value.num_elements() == 1) {
			ASSERT_LOG(value.is_decimal(), "Value not floating point number");
			glVertexAttrib1f(a.location, GLfloat(value.as_decimal().as_float()));
		} else if(value.num_elements() == 2) {
			ASSERT_LOG(value.is_list(), "Value not list");
			glVertexAttrib2f(a.location, 
				GLfloat(value[0].as_decimal().as_float()),
				GLfloat(value[1].as_decimal().as_float()));
		} else if(value.num_elements() == 3) {
			ASSERT_LOG(value.is_list(), "Value not list");
			glVertexAttrib3f(a.location, 
				GLfloat(value[0].as_decimal().as_float()),
				GLfloat(value[1].as_decimal().as_float()),
				GLfloat(value[2].as_decimal().as_float()));
		} else if(value.num_elements() == 4) {
			ASSERT_LOG(value.is_list(), "Value not list");
			//std::cerr << "set attribute \"" << key << "\" to " << value << std::endl;
			glVertexAttrib4f(a.location, 
				GLfloat(value[0].as_decimal().as_float()),
				GLfloat(value[1].as_decimal().as_float()),
				GLfloat(value[2].as_decimal().as_float()),
				GLfloat(value[3].as_decimal().as_float()));
			glDisableVertexAttribArray(a.location);
		} else {
			ASSERT_LOG(false, "Unrecognised attribute type: " << value.type() << " : " << a.name << " : " << a.num_elements << "," << value.num_elements());
		}
	//} else {
	//	ASSERT_LOG(false, "Unrecognised attribute type: " << value.type() << " : " << a.name << " : " << a.num_elements << "," << value.num_elements());
	}
}

void program::disable_vertex_attrib(GLint)
{
	for(size_t n = 0; n < active_attributes_.size(); ++n) {
		::glDisableVertexAttribArray(active_attributes_[n]);
	}
	active_attributes_.clear();
}

variant program::write()
{
	variant_builder res;
	res.add("program", name());
	res.add("vertex", vs_.name());
	res.add("fragment", fs_.name());
	if(stored_attributes_.is_null() == false) {
		res.add("attributes", stored_attributes_);
	}
	return res.build();
}

void program::vertex_attrib_array(GLint ndx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
{
	::glVertexAttribPointer(ndx, size, type, normalized, stride, ptr);
	::glEnableVertexAttribArray(ndx);
	active_attributes_.push_back(ndx);
}

void program::vertex_array(GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
{
	if(stored_attributes_.has_key("vertex")) {
		const variant& v = stored_attributes_["vertex"];
		if(v.is_string()) {
			vertex_attrib_array(get_attribute(v.as_string()), size, type, normalized, stride, ptr);
		} else {
			ASSERT_LOG(false, "Expected vertex attribute to be string.");
		}
	} else {
		ASSERT_LOG(false, "No attribute mapping found for: 'vertex', program: " << name());
	}
}

void program::texture_array(GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
{
	if(stored_attributes_.has_key("texcoord")) {
		const variant& v = stored_attributes_["texcoord"];
		if(v.is_string()) {
			vertex_attrib_array(get_attribute(v.as_string()), size, type, normalized, stride, ptr);
		} else {
			ASSERT_LOG(false, "Expected texcoord attribute to be string.");
		}
	} else {
		ASSERT_LOG(false, "No attribute mapping found for: 'texcoord', program: " << name());
	}
}

void program::color_array(GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
{
	if(stored_attributes_.has_key("color")) {
		const variant& v = stored_attributes_["color"];
		if(v.is_string()) {
			vertex_attrib_array(get_attribute(v.as_string()), size, type, normalized, stride, ptr);
		} else {
			ASSERT_LOG(false, "Expected color attribute to be string.");
		}
	} else {
		ASSERT_LOG(false, "No attribute mapping found for: 'color', program: " << name());
	}
}

void program::set_fixed_attributes(const variant& node)
{
	stored_attributes_ = node;
}

void program::load_shaders(const std::string& shader_data)
{
	variant node = json::parse(shader_data);
	//std::cerr << "load_shaders: " << node << std::endl;
	ASSERT_LOG(node.is_map() && node.has_key("shaders") && node.has_key("programs"),
		"shaders.cfg must be a map with \"shaders\" and \"programs\" attributes.");
	for(size_t n = 0; n < node["programs"].num_elements(); ++n) {
		const variant& prog = node["programs"][n];
		ASSERT_LOG(prog.has_key("vertex") 
			&& prog.has_key("fragment") 
			&& prog.has_key("name"),
			"Program's must contain \"vertex\", \"fragment\" and \"name\" attributes.");
		const std::string vs_name = prog["vertex"].as_string();
		const std::string fs_name = prog["fragment"].as_string();

		ASSERT_LOG(node["shaders"].has_key("vertex") 
			&& node["shaders"]["vertex"].has_key(vs_name),
			"No key \"" << vs_name << "\" found under \"vertex\" attribute.");
		ASSERT_LOG(node["shaders"].has_key("fragment") 
			&& node["shaders"]["fragment"].has_key(fs_name),
			"No key \"" << vs_name << "\" found under \"fragment\" attribute.");
		std::string vert_data = node["shaders"]["vertex"][vs_name].as_string();
		std::string frag_data = node["shaders"]["fragment"][fs_name].as_string();

		// Simple test to differntiate shaders as strings, compared to shaders in files.
		// i.e. shaders as strings will have "void main" stanzas, it would be kind of
		// pathological to create a file containing "void main" as part of the filename.
		const boost::regex re("void\\s+main");
		if(boost::regex_search(vert_data, re) == false) {
			// Try loading as file.
			vert_data = sys::read_file(module::map_file("data/" + vert_data));
		}
		if(boost::regex_search(frag_data, re) == false) {
			// Try loading as file.
			frag_data = sys::read_file(module::map_file("data/" + frag_data));
		}

		gles2::shader v_shader(GL_VERTEX_SHADER, vs_name, vert_data);
		gles2::shader f_shader(GL_FRAGMENT_SHADER, fs_name, frag_data);
		const std::string& program_name = prog["name"].as_string();
		add_shader(program_name, v_shader, f_shader, prog["attributes"]);

		std::map<std::string, gles2::program_ptr>::iterator it = shader_programs.find(program_name);
		ASSERT_LOG(it != shader_programs.end(), "Error! Something bad happened adding the shader.");
		std::cerr << "Loaded shader program: \"" << program_name << "\"(" << it->second->get() << ") from file. (" 
			<< vs_name << ", " << fs_name << ")." << std::endl;
	}
}

void program::add_shader(const std::string& program_name, 
		const shader& v_shader, 
		const shader& f_shader,
		const variant& prog)
{
	std::map<std::string, gles2::program_ptr>::iterator it = shader_programs.find(program_name);
	if(it == shader_programs.end()) {
		shader_programs[program_name] = program_ptr(new program(program_name, v_shader, f_shader));
	} else {
		it->second->init(program_name, v_shader, f_shader);
	}
	if(prog.is_null() == false) {
		shader_programs[program_name]->set_fixed_attributes(prog);
	}
}

program_ptr program::find_program(const std::string& prog_name)
{
	std::map<std::string, gles2::program_ptr>::const_iterator it = shader_programs.find(prog_name);
	ASSERT_LOG(it != shader_programs.end(), "Shader program \"" << prog_name << "\" not found.");
	return it->second;
}

std::map<std::string, gles2::program_ptr>& program::get_shaders()
{
	return shader_programs;
}

void program::clear_shaders()
{
	shader_programs.clear();
}

void program::set_deferred_uniforms()
{
	foreach(const std::string& key, uniforms_to_update_) {
		std::map<std::string, actives>::iterator it = uniforms_.find(key);
		ASSERT_LOG(it != uniforms_.end(), "No uniform found with name: " << key);
		set_uniform(it, it->second.last_value);
	}
	uniforms_to_update_.clear();
}


///////////////////////////////////////////////////////////////////////////
// shader_program

shader_program::shader_program()
	: vars_(new game_logic::formula_variable_storage()), parent_(NULL), zorder_(-1)
{
}

shader_program::shader_program(const variant& node, entity* obj)
	: vars_(new game_logic::formula_variable_storage()), parent_(obj), zorder_(-1)
{
	configure(node, obj);
}

shader_program::shader_program(const std::string& program_name)
	: vars_(new game_logic::formula_variable_storage()), zorder_(-1)
{
	name_ = program_name;
	program_object_ = program::find_program(name_);
}

void shader_program::configure(const variant& node, entity* obj)
{
	ASSERT_LOG(node.is_map(), "shader attribute must be a map.");
	name_ = node["program"].as_string();
	program_object_ = program::find_program(name_);
	game_logic::formula_callable* e = this;
	ASSERT_LOG(e != NULL, "Environment was not set.");

	zorder_ = node["zorder"].as_int(-1);
	if(node.has_key("create")) {
		const variant& c = node["create"];
		if(c.is_list()) {
			for(size_t n = 0; n < c.num_elements(); ++n) {
				std::string cmd = c[n].as_string();
				create_commands_.push_back(cmd);
				ASSERT_LOG(node.has_key(cmd) == true, "No attribute found with name: " << cmd);
				create_formulas_.push_back(e->create_formula(node[cmd]));
			}
		} else if(c.is_string()) {
			// single formula stored
			create_formulas_.push_back(e->create_formula(variant(c.as_string())));
		} else {
			ASSERT_LOG(false, "create must be string or list");
		}
	}
	if(node.has_key("draw")) {
		const variant& d = node["draw"];
		if(d.is_list()) {
			for(size_t n = 0; n < d.num_elements(); ++n) {
				std::string cmd = d[n].as_string();
				draw_commands_.push_back(cmd);
				ASSERT_LOG(node.has_key(cmd) == true, "No attribute found with name: " << cmd);
				draw_formulas_.push_back(e->create_formula(node[cmd]));
			}
		} else if(d.is_string()) {
			draw_formulas_.push_back(e->create_formula(variant(d.as_string())));
		} else {
			ASSERT_LOG(false, "draw must be string or list");
		}
	}

	vars_->read(node["vars"]);

	//if(obj) {
		init(obj);
	//}
}

void shader_program::init(entity* obj)
{
	ASSERT_LOG(name_.empty() != true, "Configure not run, before calling init");
	game_logic::formula_callable* e = this;
	parent_ = obj;
	GLint current_program;
	glGetIntegerv(GL_CURRENT_PROGRAM, &current_program);
	glUseProgram(program_object_->get());
	for(size_t n = 0; n < create_formulas_.size(); ++n) {
		e->execute_command(create_formulas_[n]->execute(*e));
	}
	glUseProgram(current_program);
}

variant shader_program::write()
{
	variant_builder res;
	res.add("program", name());

	if(draw_commands_.size() == 0 && draw_formulas_.size() == 1) {
		// write a single formula as a string in "draw" attribute.
		res.add("draw", draw_formulas_[0]->str());
	} else {
		ASSERT_LOG(draw_commands_.size() == draw_formulas_.size(), "commands and formulas not same size");
		for(size_t n = 0; n < draw_commands_.size(); ++n) {
			res.add("draw", draw_commands_[n]);
			res.add(draw_commands_[n], draw_formulas_[n]->str());
		}
	}

	if(create_commands_.size() == 0 && create_formulas_.size() == 1) {
		// write a single formula as a string in "draw" attribute.
		res.add("create", create_formulas_[0]->str());
	} else {
		ASSERT_LOG(create_commands_.size() == create_formulas_.size(), "commands and formulas not same size");
		for(size_t n = 0; n < create_commands_.size(); ++n) {
			res.add("create", create_commands_[n]);
			res.add(create_commands_[n], create_formulas_[n]->str());
		}
	}

	if(vars_ != NULL) {
		res.add("vars", vars_->write());
	}
	if(zorder_ != -1) {
		res.add("zorder", zorder_);
	}
	return res.build();
}

void shader_program::prepare_draw()
{
//#if defined(WIN32)
//	profile::manager manager;
//#endif
	glUseProgram(program_object_->get());
	program_object_->set_deferred_uniforms();
	game_logic::formula_callable* e = this;
	for(size_t n = 0; n < draw_formulas_.size(); ++n) {
		e->execute_command(draw_formulas_[n]->execute(*e));
	}
}

variant shader_program::get_value(const std::string& key) const
{
	if(key == "vars") {
		return variant(vars_.get());
	} else if(key == "parent" || key == "object") {
		ASSERT_LOG(parent_ != NULL, "Tried to request parent, when value is null");
		return variant(parent_);
	}
	return program_object_->get_value(key);
}

void shader_program::set_value(const std::string& key, const variant& value)
{
	program_object_->set_value(key, value);
}

program_ptr shader_program::shader() const 
{ 
	ASSERT_LOG(program_object_ != NULL, "null shader program");
	return program_object_;
}

void shader_program::clear()
{
	program_object_.reset();
	name_.clear();
	create_commands_.clear();
	draw_commands_.clear();
	create_formulas_.clear();
	draw_formulas_.clear();
}

bool shader_program::execute_command(const variant& var)
{
	bool result = true;
	if(var.is_null()) {
		return result;
	}

	if(var.is_list()) {
		const int num_elements = var.num_elements();
		for(int n = 0; n != num_elements; ++n) {
			if(var[n].is_null() == false) {
				result = execute_command(var[n]) && result;
			}
		}
	} else {
		game_logic::command_callable* cmd = var.try_convert<game_logic::command_callable>();
		if(cmd != NULL) {
			cmd->execute(*this);
		}
	}
	return result;
}

game_logic::formula_ptr shader_program::create_formula(const variant& v)
{
	return game_logic::formula_ptr(new game_logic::formula(v, &get_shader_symbol_table()));
}

}
