/*
    TE4 - T-Engine 4
    Copyright (C) 2009, 2010, 2011, 2012, 2013 Nicolas Casalini

    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 3 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, see <http://www.gnu.org/licenses/>.

    Nicolas Casalini "DarkGod"
    darkgod@te4.org
*/
#include "display.h"
#include <math.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#include "auxiliar.h"
#include "types.h"
#include "map.h"
#include "main.h"
#include "script.h"
//#include "shaders.h"
#include "useshader.h"

#include "assert.h"

static const char IS_HEX_KEY = 'k';

/*
static int lua_set_is_hex(lua_State *L)
{
	int val = luaL_checknumber(L, 1);
	lua_pushlightuserdata(L, (void *)&IS_HEX_KEY); // push address as guaranteed unique key
	lua_pushnumber(L, val);
	lua_settable(L, LUA_REGISTRYINDEX);
	return 0;
}
*/

static int lua_is_hex(lua_State *L)
{
	lua_checkstack(L, 4);
	lua_pushlightuserdata(L, (void *)&IS_HEX_KEY); // push address as guaranteed unique key
	lua_gettable(L, LUA_REGISTRYINDEX);  /* retrieve value */
	if (lua_isnil(L, -1)) {
		lua_pop(L, 1); // remove nil
		lua_pushlightuserdata(L, (void *)&IS_HEX_KEY); // push address as guaranteed unique key
		lua_pushnumber(L, 0);
		lua_settable(L, LUA_REGISTRYINDEX);
		lua_pushnumber(L, 0);
	}
}

static int map_object_new(lua_State *L)
{
	long uid = luaL_checknumber(L, 1);
	int nb_textures = luaL_checknumber(L, 2);
	int i;

	map_object *obj = (map_object*)lua_newuserdata(L, sizeof(map_object));
	memset(obj, 0, sizeof(map_object));
	auxiliar_setclass(L, "core{mapobj}", -1);
	obj->textures = calloc(nb_textures, sizeof(GLuint));
	obj->tex_x = calloc(nb_textures, sizeof(GLfloat));
	obj->tex_y = calloc(nb_textures, sizeof(GLfloat));
	obj->tex_factorx = calloc(nb_textures, sizeof(GLfloat));
	obj->tex_factory = calloc(nb_textures, sizeof(GLfloat));
	obj->textures_ref = calloc(nb_textures, sizeof(int));
	obj->textures_is3d = calloc(nb_textures, sizeof(bool));
	obj->nb_textures = nb_textures;
	obj->uid = uid;

	obj->on_seen = lua_toboolean(L, 3);
	obj->on_remember = lua_toboolean(L, 4);
	obj->on_unknown = lua_toboolean(L, 5);

	obj->move_max = 0;
	obj->anim_max = 0;

	obj->cb_ref = LUA_NOREF;

	obj->mm_r = -1;
	obj->mm_g = -1;
	obj->mm_b = -1;

	obj->valid = TRUE;
	obj->dx = luaL_checknumber(L, 6);
	obj->dy = luaL_checknumber(L, 7);
	obj->dw = luaL_checknumber(L, 8);
	obj->dh = luaL_checknumber(L, 9);
	obj->scale = luaL_checknumber(L, 10);
	obj->shader = NULL;
	obj->tint_r = obj->tint_g = obj->tint_b = 1;
	for (i = 0; i < nb_textures; i++)
	{
		obj->textures[i] = 0;
		obj->textures_is3d[i] = FALSE;
		obj->textures_ref[i] = LUA_NOREF;
	}

	obj->next = NULL;

	return 1;
}

static int map_object_free(lua_State *L)
{
	map_object *obj = (map_object*)auxiliar_checkclass(L, "core{mapobj}", 1);
	int i;

	for (i = 0; i < obj->nb_textures; i++)
		if (obj->textures_ref[i] != LUA_NOREF)
			luaL_unref(L, LUA_REGISTRYINDEX, obj->textures_ref[i]);

	free(obj->textures);
	free(obj->tex_x);
	free(obj->tex_y);
	free(obj->tex_factorx);
	free(obj->tex_factory);
	free(obj->textures_ref);
	free(obj->textures_is3d);

	if (obj->next)
	{
		luaL_unref(L, LUA_REGISTRYINDEX, obj->next_ref);
		obj->next = NULL;
	}

	if (obj->cb_ref != LUA_NOREF)
	{
		luaL_unref(L, LUA_REGISTRYINDEX, obj->cb_ref);
		obj->cb_ref = LUA_NOREF;
	}

	lua_pushnumber(L, 1);
	return 1;
}

static int map_object_cb(lua_State *L)
{
	map_object *obj = (map_object*)auxiliar_checkclass(L, "core{mapobj}", 1);
	if (obj->cb_ref != LUA_NOREF) luaL_unref(L, LUA_REGISTRYINDEX, obj->cb_ref);
	if (lua_isfunction(L, 2)) obj->cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
	else obj->cb_ref = LUA_NOREF;
	return 0;
}

static int map_object_chain(lua_State *L)
{
	map_object *obj = (map_object*)auxiliar_checkclass(L, "core{mapobj}", 1);
	map_object *obj2 = (map_object*)auxiliar_checkclass(L, "core{mapobj}", 2);
	if (obj->next) return 0;
	obj->next = obj2;
	lua_pushvalue(L, 2);
	obj->next_ref = luaL_ref(L, LUA_REGISTRYINDEX);
	return 0;
}

static int map_object_on_seen(lua_State *L)
{
	map_object *obj = (map_object*)auxiliar_checkclass(L, "core{mapobj}", 1);
	if (lua_isboolean(L, 2))
	{
		obj->on_seen = lua_toboolean(L, 2);
	}
	lua_pushboolean(L, obj->on_seen);
	return 1;
}

static int map_object_texture(lua_State *L)
{
	map_object *obj = (map_object*)auxiliar_checkclass(L, "core{mapobj}", 1);
	int i = luaL_checknumber(L, 2);
	GLuint *t = (GLuint*)auxiliar_checkclass(L, "gl{texture}", 3);
	bool is3d = lua_toboolean(L, 4);
	if (i < 0 || i >= obj->nb_textures) return 0;

	if (obj->textures_ref[i] != LUA_NOREF) luaL_unref(L, LUA_REGISTRYINDEX, obj->textures_ref[i]);

	lua_pushvalue(L, 3); // Get the texture
	obj->textures_ref[i] = luaL_ref(L, LUA_REGISTRYINDEX); // Ref the texture
//	printf("C Map Object setting texture %d = %d (ref %x)\n", i, *t, obj->textures_ref[i]);
	obj->textures[i] = *t;
	obj->textures_is3d[i] = is3d;
	obj->tex_factorx[i] = lua_tonumber(L, 5);
	obj->tex_factory[i] = lua_tonumber(L, 6);
	if (lua_isnumber(L, 7))
	{
		obj->tex_x[i] = lua_tonumber(L, 7);
		obj->tex_y[i] = lua_tonumber(L, 8);
	}
	else
	{
		obj->tex_x[i] = 0;
		obj->tex_y[i] = 0;
	}
	return 0;
}

static int map_object_shader(lua_State *L)
{
	map_object *obj = (map_object*)auxiliar_checkclass(L, "core{mapobj}", 1);
	if (!lua_isnil(L, 2)) {
		shader_type *s = (shader_type*)auxiliar_checkclass(L, "gl{program}", 2);
		obj->shader = s;
	} else {
		obj->shader = NULL;
	}
	return 0;
}

static int map_object_tint(lua_State *L)
{
	map_object *obj = (map_object*)auxiliar_checkclass(L, "core{mapobj}", 1);
	float r = luaL_checknumber(L, 2);
	float g = luaL_checknumber(L, 3);
	float b = luaL_checknumber(L, 4);
	obj->tint_r = r;
	obj->tint_g = g;
	obj->tint_b = b;
	return 0;
}

static int map_object_minimap(lua_State *L)
{
	map_object *obj = (map_object*)auxiliar_checkclass(L, "core{mapobj}", 1);
	float r = luaL_checknumber(L, 2);
	float g = luaL_checknumber(L, 3);
	float b = luaL_checknumber(L, 4);
	obj->mm_r = r / 255;
	obj->mm_g = g / 255;
	obj->mm_b = b / 255;
	return 0;
}

static int map_object_print(lua_State *L)
{
	map_object *obj = (map_object*)auxiliar_checkclass(L, "core{mapobj}", 1);
	printf("Map object texture 0: %d\n", obj->textures[0]);
	return 0;
}

static int map_object_invalid(lua_State *L)
{
	map_object *obj = (map_object*)auxiliar_checkclass(L, "core{mapobj}", 1);
	obj->valid = FALSE;
	return 0;
}


static int map_object_set_anim(lua_State *L)
{
	map_object *obj = (map_object*)auxiliar_checkclass(L, "core{mapobj}", 1);

	obj->anim_step = luaL_checknumber(L, 2);
	obj->anim_max = luaL_checknumber(L, 3);
	obj->anim_speed = luaL_checknumber(L, 4);
	obj->anim_loop = luaL_checknumber(L, 5);
	return 0;
}

static int map_object_reset_move_anim(lua_State *L)
{
	map_object *obj = (map_object*)auxiliar_checkclass(L, "core{mapobj}", 1);
	obj->move_max = 0;
	obj->animdx = obj->animdy = 0;
	return 0;
}

static int map_object_set_move_anim(lua_State *L)
{
	map_object *obj = (map_object*)auxiliar_checkclass(L, "core{mapobj}", 1);

	lua_is_hex(L);
	int is_hex = luaL_checknumber(L, -1);

	// If at rest use starting point
	if (!obj->move_max)
	{
		int ox = luaL_checknumber(L, 2);
		int oy = luaL_checknumber(L, 3);
		obj->oldx = ox;
		obj->oldy = oy + 0.5f*(ox & is_hex);
	}
	// If already moving, compute starting point
	else
	{
		int ox = luaL_checknumber(L, 2);
		int oy = luaL_checknumber(L, 3);
		obj->oldx = obj->animdx + ox;
		obj->oldy = obj->animdy + oy + 0.5f*(ox & is_hex);
	}
	obj->move_step = 0;
	obj->move_max = luaL_checknumber(L, 6);
	obj->move_blur = lua_tonumber(L, 7); // defaults to 0
	obj->move_twitch_dir = lua_tonumber(L, 8); // defaults to 0 (which is equivalent to up or 8)
	obj->move_twitch = lua_tonumber(L, 9); // defaults to 0
	return 0;
}

static int map_object_get_move_anim(lua_State *L)
{
	map_object *obj = (map_object*)auxiliar_checkclass(L, "core{mapobj}", 1);
	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 2);
	int i = luaL_checknumber(L, 3);
	int j = luaL_checknumber(L, 4);

	float mapdx = 0, mapdy = 0;
	if (map->move_max)
	{
		float adx = (float)map->mx - map->oldmx;
		float ady = (float)map->my - map->oldmy;
		mapdx = -(adx * map->move_step / (float)map->move_max - adx);
		mapdy = -(ady * map->move_step / (float)map->move_max - ady);
	}

	if (!obj->move_max || obj->display_last == DL_NONE)
	{
//		printf("==== GET %f x %f\n", mapdx, mapdy);
		lua_pushnumber(L, mapdx);
		lua_pushnumber(L, mapdy);
	}
	else
	{
//		printf("==== GET %f x %f :: %f x %f\n", mapdx, mapdy,obj->animdx,obj->animdy);
		lua_pushnumber(L, mapdx + obj->animdx);
		lua_pushnumber(L, mapdy + obj->animdy);
	}
	return 2;
}

static int map_object_get_move_anim_raw(lua_State *L)
{
	map_object *obj = (map_object*)auxiliar_checkclass(L, "core{mapobj}", 1);
	lua_pushnumber(L, obj->animdx);
	lua_pushnumber(L, obj->animdy);
	return 2;
}

static int map_object_is_valid(lua_State *L)
{
	map_object *obj = (map_object*)auxiliar_checkclass(L, "core{mapobj}", 1);
	lua_pushboolean(L, obj->valid);
	return 1;
}

static bool _CheckGL_Error(const char* GLcall, const char* file, const int line)
{
	GLenum errCode;
	if((errCode = glGetError())!=GL_NO_ERROR)
	{
		printf("OPENGL ERROR #%i: (%s) in file %s on line %i\n",errCode,gluErrorString(errCode), file, line);
		printf("OPENGL Call: %s\n",GLcall);
		return FALSE;
	}
	return TRUE;
}

//#define _DEBUG
#ifdef _DEBUG
#define CHECKGL( GLcall )                               		\
    GLcall;                                             		\
    if(!_CheckGL_Error( #GLcall, __FILE__, __LINE__))     		\
    exit(-1);
#else
#define CHECKGL( GLcall)        \
    GLcall;
#endif
static int map_objects_toscreen(lua_State *L)
{
	int x = luaL_checknumber(L, 1);
	int y = luaL_checknumber(L, 2);
	int w = luaL_checknumber(L, 3);
	int h = luaL_checknumber(L, 4);
	float a = (lua_isnumber(L, 5) ? lua_tonumber(L, 5) : 1);
	bool allow_cb = TRUE;
	bool allow_shader = TRUE;
	if (lua_isboolean(L, 6)) allow_cb = lua_toboolean(L, 6);
	if (lua_isboolean(L, 7)) allow_shader = lua_toboolean(L, 7);

	GLfloat vertices[3*4];
	GLfloat texcoords[2*4] = {
		0, 0,
		1, 0,
		1, 1,
		0, 1,
	};
	GLfloat colors[4*4] = {
		1, 1, 1, a,
		1, 1, 1, a,
		1, 1, 1, a,
		1, 1, 1, a,
	};

	glTexCoordPointer(2, GL_FLOAT, 0, texcoords);
	glColorPointer(4, GL_FLOAT, 0, colors);
	glVertexPointer(3, GL_FLOAT, 0, vertices);

	/***************************************************
	 * Render
	 ***************************************************/
	int moid = 8;
	while (lua_isuserdata(L, moid))
	{
		map_object *m = (map_object*)auxiliar_checkclass(L, "core{mapobj}", moid);
		map_object *dm;

		int z;
		if (allow_shader && m->shader) useShader(m->shader, 1, 1, 1, 1, 1, 1, 1, 1);
		for (z = (!shaders_active) ? 0 : (m->nb_textures - 1); z >= 0; z--)
		{
			if (multitexture_active && shaders_active) tglActiveTexture(GL_TEXTURE0+z);
			tglBindTexture(m->textures_is3d[z] ? GL_TEXTURE_3D : GL_TEXTURE_2D, m->textures[z]);
		}

		int nb = 0;
		dm = m;
		while (dm)
		{
			tglBindTexture(GL_TEXTURE_2D, dm->textures[0]);

			int dx = x + dm->dx * w, dy = y + dm->dy * h;
			float dw = w * dm->dw;
			float dh = h * dm->dh;
			int dz = moid;

			texcoords[0] = dm->tex_x[0]; texcoords[1] = dm->tex_y[0];
			texcoords[2] = dm->tex_x[0] + dm->tex_factorx[0]; texcoords[3] = dm->tex_y[0];
			texcoords[4] = dm->tex_x[0] + dm->tex_factorx[0]; texcoords[5] = dm->tex_y[0] + dm->tex_factory[0];
			texcoords[6] = dm->tex_x[0]; texcoords[7] = dm->tex_y[0] + dm->tex_factory[0];

			vertices[0] = dx; vertices[1] = dy; vertices[2] = dz;
			vertices[3] = dw + dx; vertices[4] = dy; vertices[5] = dz;
			vertices[6] = dw + dx; vertices[7] = dh + dy; vertices[8] = dz;
			vertices[9] = dx; vertices[10] = dh + dy; vertices[11] = dz;
			glDrawArrays(GL_QUADS, 0, 4);

			if (allow_cb && (dm->cb_ref != LUA_NOREF))
			{
				if (allow_shader && m->shader) glUseProgramObjectARB(0);
				int dx = x + dm->dx * w, dy = y + dm->dy * h;
				float dw = w * dm->dw;
				float dh = h * dm->dh;
				lua_rawgeti(L, LUA_REGISTRYINDEX, dm->cb_ref);
				lua_pushnumber(L, dx);
				lua_pushnumber(L, dy);
				lua_pushnumber(L, dw);
				lua_pushnumber(L, dh);
				lua_pushnumber(L, 1);
				lua_pushboolean(L, FALSE);
				if (lua_pcall(L, 6, 1, 0))
				{
					printf("Display callback error: UID %ld: %s\n", dm->uid, lua_tostring(L, -1));
					lua_pop(L, 1);
				}
				if (lua_isboolean(L, -1)) {
					glTexCoordPointer(2, GL_FLOAT, 0, texcoords);
					glColorPointer(4, GL_FLOAT, 0, colors);
					glVertexPointer(3, GL_FLOAT, 0, vertices);
				}
				lua_pop(L, 1);

				if (allow_shader && m->shader) useShader(m->shader, 1, 1, 1, 1, 1, 1, 1, 1);
			}

			dm = dm->next;
			nb++;
		}

		if (allow_shader && m->shader) glUseProgramObjectARB(0);

		moid++;
	}
	/***************************************************
	 ***************************************************/

	return 0;
}

static int map_objects_display(lua_State *L)
{
	if (!fbo_active) return 0;

	int w = luaL_checknumber(L, 1);
	int h = luaL_checknumber(L, 2);

	// Setup our FBO
	// WARNING: this is a static, only one FBO is ever made, and never deleted, for some reasons
	// deleting it makes the game crash when doing a chain lightning spell under luajit1 ... (yeah I know .. weird)
	static GLuint fbo = 0;
	if (!fbo) CHECKGL(glGenFramebuffersEXT(1, &fbo));
	CHECKGL(glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo));

	// Now setup a texture to render to
	GLuint img;
	CHECKGL(glGenTextures(1, &img));
	tfglBindTexture(GL_TEXTURE_2D, img);
	CHECKGL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL));
	CHECKGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT));
	CHECKGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT));
	CHECKGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
	CHECKGL(glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, img, 0));

	GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
	if(status != GL_FRAMEBUFFER_COMPLETE_EXT) return 0;

	// Set the viewport and save the old one
	CHECKGL(glPushAttrib(GL_VIEWPORT_BIT));

	CHECKGL(glViewport(0, 0, w, h));
	glMatrixMode(GL_PROJECTION);
	CHECKGL(glPushMatrix());
	glLoadIdentity();
	glOrtho(0, w, 0, h, -101, 101);
	glMatrixMode( GL_MODELVIEW );
	CHECKGL(glPushMatrix());
	/* Reset The View */
	glLoadIdentity();


	tglClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
	CHECKGL(glClear(GL_COLOR_BUFFER_BIT));
	//CHECKGL(glLoadIdentity());

	GLfloat vertices[3*4];
	GLfloat texcoords[2*4] = {
		0, 0,
		1, 0,
		1, 1,
		0, 1,
	};
	GLfloat colors[4*4] = {
		1, 1, 1, 1,
		1, 1, 1, 1,
		1, 1, 1, 1,
		1, 1, 1, 1,
	};

	glTexCoordPointer(2, GL_FLOAT, 0, texcoords);
	glColorPointer(4, GL_FLOAT, 0, colors);
	glVertexPointer(3, GL_FLOAT, 0, vertices);

	/***************************************************
	 * Render to buffer
	 ***************************************************/
	int moid = 3;
	int dz = moid;
	while (lua_isuserdata(L, moid))
	{
		map_object *m = (map_object*)auxiliar_checkclass(L, "core{mapobj}", moid);
		map_object *dm;

		int z;
		if (m->shader) useShader(m->shader, 1, 1, 1, 1, 1, 1, 1, 1);
		for (z = (!shaders_active) ? 0 : (m->nb_textures - 1); z >= 0; z--)
		{
			if (multitexture_active && shaders_active) tglActiveTexture(GL_TEXTURE0+z);
			tglBindTexture(m->textures_is3d[z] ? GL_TEXTURE_3D : GL_TEXTURE_2D, m->textures[z]);
		}

		dm = m;
		while (dm)
		{
			tglBindTexture(GL_TEXTURE_2D, dm->textures[0]);

			int dx = 0, dy = 0;

			texcoords[0] = m->tex_x[0]; texcoords[1] = m->tex_y[0];
			texcoords[2] = m->tex_x[0] + m->tex_factorx[0]; texcoords[3] = m->tex_y[0];
			texcoords[4] = m->tex_x[0] + m->tex_factorx[0]; texcoords[5] = m->tex_y[0] + m->tex_factory[0];
			texcoords[6] = m->tex_x[0]; texcoords[7] = m->tex_y[0] + m->tex_factory[0];

			vertices[0] = dx; vertices[1] = dy; vertices[2] = dz;
			vertices[3] = w + dx; vertices[4] = dy; vertices[5] = dz;
			vertices[6] = w + dx; vertices[7] = h + dy; vertices[8] = dz;
			vertices[9] = dx; vertices[10] = h + dy; vertices[11] = dz;
			glDrawArrays(GL_QUADS, 0, 4);

			dm = dm->next;
			dz++;
		}

		if (m->shader) glUseProgramObjectARB(0);

		moid++;
	}
	/***************************************************
	 ***************************************************/

	// Unbind texture from FBO and then unbind FBO
	CHECKGL(glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, 0, 0));
	CHECKGL(glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, gl_c_fbo));
	// Restore viewport
	CHECKGL(glPopAttrib());

	// Cleanup
	// No, dot not it's a static, see upwards
//	CHECKGL(glDeleteFramebuffersEXT(1, &fbo));

	CHECKGL(glPopMatrix());
	glMatrixMode(GL_PROJECTION);
	CHECKGL(glPopMatrix());
	glMatrixMode( GL_MODELVIEW );

	tglClearColor( 0.0f, 0.0f, 0.0f, 1.0f );


	// Now register the texture to lua
	GLuint *t = (GLuint*)lua_newuserdata(L, sizeof(GLuint));
	auxiliar_setclass(L, "gl{texture}", -1);
	*t = img;

	return 1;
}

static void setup_seens_texture(map_type *map)
{
	if (map->seens_texture) glDeleteTextures(1, &(map->seens_texture));
	if (map->seens_map) free(map->seens_map);

	int f = (map->is_hex & 1);
	int realw=1;
	while (realw < f + (1+f)*(map->w+10)) realw *= 2;
	int realh=1;
	while (realh < f + (1+f)*(map->h+10)) realh *= 2;
	map->seens_map_w = realw;
	map->seens_map_h = realh;

	glGenTextures(1, &(map->seens_texture));
	printf("C Map seens texture: %d (%dx%d)\n", map->seens_texture, map->w+10, map->h+10);
	tglBindTexture(GL_TEXTURE_2D, map->seens_texture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexImage2D(GL_TEXTURE_2D, 0, 4, map->seens_map_w, map->seens_map_h, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
	map->seens_map = calloc((map->seens_map_w)*(map->seens_map_h)*4, sizeof(GLubyte));
	map->seen_changed = TRUE;

	// Black it all
	int i;
	for (i = 0; i < map->seens_map_w * map->seens_map_h; i++)
	{
		map->seens_map[(i*4)] = 0;
		map->seens_map[(i*4)+1] = 0;
		map->seens_map[(i*4)+2] = 0;
		map->seens_map[(i*4)+3] = 255;
	}
}

#define QUADS_PER_BATCH 500

static int map_new(lua_State *L)
{
	int w = luaL_checknumber(L, 1);
	int h = luaL_checknumber(L, 2);
	int mx = luaL_checknumber(L, 3);
	int my = luaL_checknumber(L, 4);
	int mwidth = luaL_checknumber(L, 5);
	int mheight = luaL_checknumber(L, 6);
	int tile_w = luaL_checknumber(L, 7);
	int tile_h = luaL_checknumber(L, 8);
	int zdepth = luaL_checknumber(L, 9);
	int is_hex = luaL_checknumber(L, 10);
	int i, j;

	map_type *map = (map_type*)lua_newuserdata(L, sizeof(map_type));
	auxiliar_setclass(L, "core{map}", -1);

	map->obscure_r = map->obscure_g = map->obscure_b = 0.6f;
	map->obscure_a = 1;
	map->shown_r = map->shown_g = map->shown_b = 1;
	map->shown_a = 1;

	map->minimap = NULL;
	map->mm_texture = 0;
	map->mm_w = map->mm_h = 0;

	map->minimap_gridsize = 4;

	map->is_hex = (is_hex > 0);
	lua_pushlightuserdata(L, (void *)&IS_HEX_KEY); // push address as guaranteed unique key
	lua_pushnumber(L, map->is_hex);
	lua_settable(L, LUA_REGISTRYINDEX);

	map->vertices = calloc(2*4*QUADS_PER_BATCH, sizeof(GLfloat)); // 2 coords, 4 vertices per particles
	map->colors = calloc(4*4*QUADS_PER_BATCH, sizeof(GLfloat)); // 4 color data, 4 vertices per particles
	map->texcoords = calloc(2*4*QUADS_PER_BATCH, sizeof(GLfloat));

	map->w = w;
	map->h = h;
	map->zdepth = zdepth;
	map->tile_w = tile_w;
	map->tile_h = tile_h;
	map->move_max = 0;

	// Make up the map objects list, thus we can iterate them later
	lua_newtable(L);
	map->mo_list_ref = luaL_ref(L, LUA_REGISTRYINDEX); // Ref the table

	// In case we can't support NPOT textures round up to nearest POT
	for (i = 1; i <= 3; i++)
	{
		int tw = tile_w * i;
		int realw=1;
		while (realw < tw) realw *= 2;
		map->tex_tile_w[i-1] = (GLfloat)tw / realw;

		int th = tile_h * i;
		int realh=1;
		while (realh < th) realh *= 2;
		map->tex_tile_h[i-1] = (GLfloat)th / realh;
	}

	map->mx = mx;
	map->my = my;
	map->mwidth = mwidth;
	map->mheight = mheight;
	map->grids = calloc(w, sizeof(map_object***));
	map->grids_ref = calloc(w, sizeof(int**));
	map->grids_seens = calloc(w * h, sizeof(float));
	map->grids_remembers = calloc(w, sizeof(bool*));
	map->grids_lites = calloc(w, sizeof(bool*));
	map->grids_important = calloc(w, sizeof(bool*));
	printf("C Map size %d:%d :: %d\n", mwidth, mheight,mwidth * mheight);

	map->seens_texture = 0;
	map->seens_map = NULL;
	setup_seens_texture(map);

	for (i = 0; i < w; i++)
	{
		map->grids[i] = calloc(h, sizeof(map_object**));
		map->grids_ref[i] = calloc(h, sizeof(int*));
//		map->grids_seens[i] = calloc(h, sizeof(float));
		map->grids_remembers[i] = calloc(h, sizeof(bool));
		map->grids_lites[i] = calloc(h, sizeof(bool));
		map->grids_important[i] = calloc(h, sizeof(bool));
		for (j = 0; j < h; j++)
		{
			map->grids[i][j] = calloc(zdepth, sizeof(map_object*));
			map->grids_ref[i][j] = calloc(zdepth, sizeof(int));
			map->grids_important[i][j] = FALSE;
		}
	}

	return 1;
}

static int map_free(lua_State *L)
{
	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
	int i, j;

	for (i = 0; i < map->w; i++)
	{
		for (j = 0; j < map->h; j++)
		{
			free(map->grids[i][j]);
			free(map->grids_ref[i][j]);
		}
		free(map->grids[i]);
		free(map->grids_ref[i]);
//		free(map->grids_seens[i]);
		free(map->grids_remembers[i]);
		free(map->grids_lites[i]);
		free(map->grids_important[i]);
	}
	free(map->grids);
	free(map->grids_ref);
	free(map->grids_seens);
	free(map->grids_remembers);
	free(map->grids_lites);
	free(map->grids_important);

	free(map->colors);
	free(map->texcoords);
	free(map->vertices);

	luaL_unref(L, LUA_REGISTRYINDEX, map->mo_list_ref);

	glDeleteTextures(1, &map->seens_texture);
	free(map->seens_map);

	if (map->minimap) free(map->minimap);
	if (map->mm_texture) glDeleteTextures(1, &map->mm_texture);

	lua_pushnumber(L, 1);
	return 1;
}

static int map_set_zoom(lua_State *L)
{
	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
	int tile_w = luaL_checknumber(L, 2);
	int tile_h = luaL_checknumber(L, 3);
	int mwidth = luaL_checknumber(L, 4);
	int mheight = luaL_checknumber(L, 5);
	map->tile_w = tile_w;
	map->tile_h = tile_h;
	map->mwidth = mwidth;
	map->mheight = mheight;
	map->seen_changed = TRUE;
	setup_seens_texture(map);
	return 0;
}

static int map_set_obscure(lua_State *L)
{
	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
	float r = luaL_checknumber(L, 2);
	float g = luaL_checknumber(L, 3);
	float b = luaL_checknumber(L, 4);
	float a = luaL_checknumber(L, 5);
	map->obscure_r = r;
	map->obscure_g = g;
	map->obscure_b = b;
	map->obscure_a = a;
	map->seen_changed = TRUE;
	return 0;
}

static int map_set_shown(lua_State *L)
{
	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
	float r = luaL_checknumber(L, 2);
	float g = luaL_checknumber(L, 3);
	float b = luaL_checknumber(L, 4);
	float a = luaL_checknumber(L, 5);
	map->shown_r = r;
	map->shown_g = g;
	map->shown_b = b;
	map->shown_a = a;
	map->seen_changed = TRUE;
	return 0;
}

static int map_set_minimap_gridsize(lua_State *L)
{
	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
	float s = luaL_checknumber(L, 2);
	map->minimap_gridsize = s;
	return 0;
}

static int map_set_grid(lua_State *L)
{
	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
	int x = luaL_checknumber(L, 2);
	int y = luaL_checknumber(L, 3);
	if (x < 0 || y < 0 || x >= map->w || y >= map->h) return 0;

	// Get the mo list
	lua_rawgeti(L, LUA_REGISTRYINDEX, map->mo_list_ref);

	int i;
	for (i = 0; i < map->zdepth; i++)
	{
		// Remove the old object if any from the mo list
		// We use the pointer value directly as an index
		if (map->grids[x][y][i])
		{
#if defined(__PTRDIFF_TYPE__)
			if(sizeof(__PTRDIFF_TYPE__) == sizeof(long int))
				lua_pushnumber(L, (unsigned long int)map->grids[x][y][i]);
			else if(sizeof(__PTRDIFF_TYPE__) == sizeof(long long))
				lua_pushnumber(L, (long long)map->grids[x][y][i]);
			else
				lua_pushnumber(L, (int)map->grids[x][y][i]);
#else
			lua_pushnumber(L, (long long)map->grids[x][y][i]);
#endif
			lua_pushnil(L);
			lua_settable(L, 5); // Access the list of all mos for the map

			luaL_unref(L, LUA_REGISTRYINDEX, map->grids_ref[x][y][i]);
		}

		lua_pushnumber(L, i + 1);
		lua_gettable(L, 4); // Access the table of mos for this spot
		map->grids[x][y][i] = lua_isnoneornil(L, -1) ? NULL : (map_object*)auxiliar_checkclass(L, "core{mapobj}", -1);
		if (map->grids[x][y][i])
		{
			map->grids[x][y][i]->cur_x = x;
			map->grids[x][y][i]->cur_y = y;
			lua_pushvalue(L, -1);
			map->grids_ref[x][y][i] = luaL_ref(L, LUA_REGISTRYINDEX);
		}

		// Set the object in the mo list
		// We use the pointer value directly as an index
		lua_pushnumber(L, (long)map->grids[x][y][i]);
		lua_pushvalue(L, -2);
		lua_settable(L, 5); // Access the list of all mos for the map

		// Remove the mo and get the next
		lua_pop(L, 1);
	}

	// Pop the mo list
	lua_pop(L, 1);
	return 0;
}

static int map_set_seen(lua_State *L)
{
	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
	int x = luaL_checknumber(L, 2);
	int y = luaL_checknumber(L, 3);
	float v = lua_tonumber(L, 4);

	if (x < 0 || y < 0 || x >= map->w || y >= map->h) return 0;
	map->grids_seens[y*map->w+x] = v;
	map->seen_changed = TRUE;
	return 0;
}

static int map_set_remember(lua_State *L)
{
	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
	int x = luaL_checknumber(L, 2);
	int y = luaL_checknumber(L, 3);
	bool v = lua_toboolean(L, 4);

	if (x < 0 || y < 0 || x >= map->w || y >= map->h) return 0;
	map->grids_remembers[x][y] = v;
	map->seen_changed = TRUE;
	return 0;
}

static int map_set_lite(lua_State *L)
{
	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
	int x = luaL_checknumber(L, 2);
	int y = luaL_checknumber(L, 3);
	bool v = lua_toboolean(L, 4);

	if (x < 0 || y < 0 || x >= map->w || y >= map->h) return 0;
	map->grids_lites[x][y] = v;
	map->seen_changed = TRUE;
	return 0;
}

static int map_set_important(lua_State *L)
{
	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
	int x = luaL_checknumber(L, 2);
	int y = luaL_checknumber(L, 3);
	bool v = lua_toboolean(L, 4);

	if (x < 0 || y < 0 || x >= map->w || y >= map->h) return 0;
	map->grids_important[x][y] = v;
	map->seen_changed = TRUE;
	return 0;
}

static int map_clean_seen(lua_State *L)
{
	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
	int i, j;

	for (i = 0; i < map->w; i++)
		for (j = 0; j < map->h; j++)
			map->grids_seens[j*map->w+i] = 0;
	map->seen_changed = TRUE;
	return 0;
}

static int map_clean_remember(lua_State *L)
{
	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
	int i, j;

	for (i = 0; i < map->w; i++)
		for (j = 0; j < map->h; j++)
			map->grids_remembers[i][j] = FALSE;
	map->seen_changed = TRUE;
	return 0;
}

static int map_clean_lite(lua_State *L)
{
	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
	int i, j;

	for (i = 0; i < map->w; i++)
		for (j = 0; j < map->h; j++)
			map->grids_lites[i][j] = FALSE;
	map->seen_changed = TRUE;
	return 0;
}

static int map_get_seensinfo(lua_State *L)
{
	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
	lua_pushnumber(L, map->tile_w);
	lua_pushnumber(L, map->tile_h);
	lua_pushnumber(L, map->seensinfo_w);
	lua_pushnumber(L, map->seensinfo_h);
	return 4;
}

static void map_update_seen_texture(map_type *map)
{
	glBindTexture(GL_TEXTURE_2D, map->seens_texture);
	gl_c_texture = -1;

	int mx = map->used_mx;
	int my = map->used_my;
	GLubyte *seens = map->seens_map;
	int ptr = 0;
	int f = (map->is_hex & 1);
	int ii, jj;
	map->seensinfo_w = map->w+10;
	map->seensinfo_h = map->h+10;

	for (jj = 0; jj < map->h+10; jj++)
	{
		for (ii = 0; ii < map->w+10; ii++)
		{
			int i = ii, j = jj;
			int ri = i-5, rj = j-5;
			ptr = (((1+f)*j + (ri & f)) * map->seens_map_w + (1+f)*i) * 4;
			ri = (ri < 0) ? 0 : (ri >= map->w) ? map->w-1 : ri;
			rj = (rj < 0) ? 0 : (rj >= map->h) ? map->h-1 : rj;
			if ((i < 0) || (j < 0) || (i >= map->w+10) || (j >= map->h+10))
			{
				seens[ptr] = 0;
				seens[ptr+1] = 0;
				seens[ptr+2] = 0;
				seens[ptr+3] = 255;
				if (f) {
					ptr += 4;
					seens[ptr] = 0;
					seens[ptr+1] = 0;
					seens[ptr+2] = 0;
					seens[ptr+3] = 255;
					ptr += 4 * map->seens_map_w - 4;
					seens[ptr] = 0;
					seens[ptr+1] = 0;
					seens[ptr+2] = 0;
					seens[ptr+3] = 255;
					ptr += 4;
					seens[ptr] = 0;
					seens[ptr+1] = 0;
					seens[ptr+2] = 0;
					seens[ptr+3] = 255;
				}
				//ptr += 4;
				continue;
			}
			float v = map->grids_seens[rj*map->w+ri] * 255;
			if (v)
			{
				if (v > 255) v = 255;
				if (v < 0) v = 0;
				seens[ptr] = (GLubyte)0;
				seens[ptr+1] = (GLubyte)0;
				seens[ptr+2] = (GLubyte)0;
				seens[ptr+3] = (GLubyte)255-v;
				if (f) {
					ptr += 4;
					seens[ptr] = (GLubyte)0;
					seens[ptr+1] = (GLubyte)0;
					seens[ptr+2] = (GLubyte)0;
					seens[ptr+3] = (GLubyte)255-v;
					ptr += 4 * map->seens_map_w - 4;
					seens[ptr] = (GLubyte)0;
					seens[ptr+1] = (GLubyte)0;
					seens[ptr+2] = (GLubyte)0;
					seens[ptr+3] = (GLubyte)255-v;
					ptr += 4;
					seens[ptr] = (GLubyte)0;
					seens[ptr+1] = (GLubyte)0;
					seens[ptr+2] = (GLubyte)0;
					seens[ptr+3] = (GLubyte)255-v;
				}
			}
			else if (map->grids_remembers[ri][rj])
			{
				seens[ptr] = 0;
				seens[ptr+1] = 0;
				seens[ptr+2] = 0;
				seens[ptr+3] = 255 - map->obscure_a * 255;
				if (f) {
					ptr += 4;
					seens[ptr] = 0;
					seens[ptr+1] = 0;
					seens[ptr+2] = 0;
					seens[ptr+3] = 255 - map->obscure_a * 255;
					ptr += 4 * map->seens_map_w - 4;
					seens[ptr] = 0;
					seens[ptr+1] = 0;
					seens[ptr+2] = 0;
					seens[ptr+3] = 255 - map->obscure_a * 255;
					ptr += 4;
					seens[ptr] = 0;
					seens[ptr+1] = 0;
					seens[ptr+2] = 0;
					seens[ptr+3] = 255 - map->obscure_a * 255;
				}
			}
			else
			{
				seens[ptr] = 0;
				seens[ptr+1] = 0;
				seens[ptr+2] = 0;
				seens[ptr+3] = 255;
				if (f) {
					ptr += 4;
					seens[ptr] = 0;
					seens[ptr+1] = 0;
					seens[ptr+2] = 0;
					seens[ptr+3] = 255;
					ptr += 4 * map->seens_map_w - 4;
					seens[ptr] = 0;
					seens[ptr+1] = 0;
					seens[ptr+2] = 0;
					seens[ptr+3] = 255;
					ptr += 4;
					seens[ptr] = 0;
					seens[ptr+1] = 0;
					seens[ptr+2] = 0;
					seens[ptr+3] = 255;
				}
			}
			//ptr += 4;
		}
		// Skip the rest of the texture, silly GPUs not supporting NPOT textures!
		//ptr += (map->seens_map_w - map->w) * 4;
	}
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, map->seens_map_w, map->seens_map_h, GL_BGRA, GL_UNSIGNED_BYTE, seens);
}

static int map_update_seen_texture_lua(lua_State *L)
{
	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
	map_update_seen_texture(map);
	return 0;
}

static int map_draw_seen_texture(lua_State *L)
{
	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
	int x = lua_tonumber(L, 2);
	int y = lua_tonumber(L, 3);
	int nb_keyframes = 0;
	x += -map->tile_w * 5;
	y += -map->tile_h * 5;
	int w = (map->seens_map_w) * map->tile_w;
	int h = (map->seens_map_h) * map->tile_h;

	int mx = map->mx;
	int my = map->my;
//	x -= map->tile_w * (map->used_animdx + map->used_mx);
//	y -= map->tile_h * (map->used_animdy + map->used_my);
	x -= map->tile_w * (map->used_animdx + map->oldmx);
	y -= map->tile_h * (map->used_animdy + map->oldmy);


	tglBindTexture(GL_TEXTURE_2D, map->seens_texture);

	int f = 1 + (map->is_hex & 1);
	GLfloat texcoords[2*4] = {
		0, 0,
		0, f,
		f, f,
		f, 0,
	};
	GLfloat colors[4*4] = {
		1,1,1,1,
		1,1,1,1,
		1,1,1,1,
		1,1,1,1,
	};
	glColorPointer(4, GL_FLOAT, 0, colors);
	glTexCoordPointer(2, GL_FLOAT, 0, texcoords);

	GLfloat vertices[2*4] = {
		x, y,
		x, y + h,
		x + w, y + h,
		x + w, y,
	};
	glVertexPointer(2, GL_FLOAT, 0, vertices);

	glDrawArrays(GL_QUADS, 0, 4);
	return 0;
}

static int map_bind_seen_texture(lua_State *L)
{
	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
	int unit = luaL_checknumber(L, 2);
	if (unit > 0 && !multitexture_active) return 0;

	if (unit > 0) tglActiveTexture(GL_TEXTURE0+unit);
	glBindTexture(GL_TEXTURE_2D, map->seens_texture);
	if (unit > 0) tglActiveTexture(GL_TEXTURE0);

	return 0;
}

static int map_set_scroll(lua_State *L)
{
	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
	int x = luaL_checknumber(L, 2);
	int y = luaL_checknumber(L, 3);
	int smooth = luaL_checknumber(L, 4);

	if (smooth)
	{
		// Not moving, use starting point
		if (!map->move_max)
		{
			map->oldmx = map->mx;
			map->oldmy = map->my;
		}
		// Already moving, compute starting point
		else
		{
			map->oldmx = map->oldmx + map->used_animdx;
			map->oldmy = map->oldmy + map->used_animdy;
		}
	} else {
		map->oldmx = x;
		map->oldmy = y;
	}

	map->move_step = 0;
	map->move_max = smooth;
	map->used_animdx = 0;
	map->used_animdy = 0;
	map->mx = x;
	map->my = y;
	map->seen_changed = TRUE;
	return 0;
}

static int map_get_scroll(lua_State *L)
{
	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
	lua_pushnumber(L, -map->tile_w*(map->used_animdx + map->oldmx - map->mx));
	lua_pushnumber(L, -map->tile_h*(map->used_animdy + map->oldmy - map->my));
	return 2;
}

void do_quad(lua_State *L, const map_object *m, const map_object *dm, const map_type *map,
		float *vertices, float *texcoords, float *colors, int *vert_idx, int
		*col_idx, float anim, float dx, float dy, float tldx, float tldy, float
		dw, float dh, float r, float g, float b, float a, int force, int i, int j)
{
	int idx = 0, row;

	idx = *vert_idx;

	vertices[idx + 0] = dx;                                vertices[idx + 1] = dy;
	vertices[idx + 2] = map->tile_w * dw * dm->scale + dx; vertices[idx + 3] = dy;
	vertices[idx + 4] = vertices[idx + 2];                 vertices[idx + 5] = map->tile_h * dh * dm->scale + dy;
	vertices[idx + 6] = dx;                                vertices[idx + 7] = vertices[idx + 5];

	texcoords[idx + 0] = dm->tex_x[0] + anim;                      texcoords[idx + 1] = dm->tex_y[0];
	texcoords[idx + 2] = dm->tex_x[0] + anim + dm->tex_factorx[0]; texcoords[idx + 3] = dm->tex_y[0];
	texcoords[idx + 4] = dm->tex_x[0] + anim + dm->tex_factorx[0]; texcoords[idx + 5] = dm->tex_y[0] + dm->tex_factory[0];
	texcoords[idx + 6] = dm->tex_x[0] + anim;                      texcoords[idx + 7] = dm->tex_y[0] + dm->tex_factory[0];

	idx = *col_idx;

	for (row = 0; row < 4; row++) {
		colors[idx + (4 * row + 0)] = r;
		colors[idx + (4 * row + 1)] = g;
		colors[idx + (4 * row + 2)] = b;
		colors[idx + (4 * row + 3)] = a;
	}

	(*vert_idx) += 8;
	(*col_idx) += 16;
	if ((*vert_idx) >= 8*QUADS_PER_BATCH || force || dm->cb_ref != LUA_NOREF) {\
		glDrawArrays(GL_QUADS, 0, (*vert_idx) / 2);
		(*vert_idx) = 0;
		(*col_idx) = 0;
	}
	if (dm->cb_ref != LUA_NOREF)
	{
		if (m->shader) glUseProgramObjectARB(0);
		lua_rawgeti(L, LUA_REGISTRYINDEX, dm->cb_ref);
		lua_checkstack(L, 8);
		lua_pushnumber(L, dx);
		lua_pushnumber(L, dy);
		lua_pushnumber(L, map->tile_w * (dw) * (dm->scale));
		lua_pushnumber(L, map->tile_h * (dh) * (dm->scale));
		lua_pushnumber(L, (dm->scale));
		lua_pushboolean(L, TRUE);
		lua_pushnumber(L, tldx);
		lua_pushnumber(L, tldy);
		if (lua_pcall(L, 8, 1, 0))
		{
			printf("Display callback error: UID %ld: %s\n", dm->uid, lua_tostring(L, -1));
			lua_pop(L, 1);
		}
		if (lua_isboolean(L, -1)) {
			glTexCoordPointer(2, GL_FLOAT, 0, texcoords);
			glVertexPointer(2, GL_FLOAT, 0, vertices);
			glColorPointer(4, GL_FLOAT, 0, colors);
		}
		lua_pop(L, 1);
		if (m->shader) useShader(m->shader, dx, dy, map->tile_w, map->tile_h, r, g, b, a);
	}
}

inline void display_map_quad(lua_State *L, GLuint *cur_tex, int *vert_idx, int *col_idx, map_type *map, int dx, int dy, float dz, map_object *m, int i, int j, float a, float seen, int nb_keyframes, bool always_show) ALWAYS_INLINE;
void display_map_quad(lua_State *L, GLuint *cur_tex, int *vert_idx, int *col_idx, map_type *map, int dx, int dy, float dz, map_object *m, int i, int j, float a, float seen, int nb_keyframes, bool always_show)
{
	map_object *dm;
	float r, g, b;
	GLfloat *vertices = map->vertices;
	GLfloat *colors = map->colors;
	GLfloat *texcoords = map->texcoords;
	bool up_important = FALSE;
	float anim;
	int anim_step;

	/********************************************************
	 ** Select the color to use
	 ********************************************************/
	if (always_show)
	{
		if (m->tint_r < 1 || m->tint_g < 1 || m->tint_b < 1)
		{
			r = (map->shown_r + m->tint_r)/2; g = (map->shown_g + m->tint_g)/2; b = (map->shown_b + m->tint_b)/2;
		}
		else
		{
			r = map->shown_r; g = map->shown_g; b = map->shown_b;
		}
		a = 1;
	}
	else if (seen)
	{
		if (m->tint_r < 1 || m->tint_g < 1 || m->tint_b < 1)
		{
			r = (map->shown_r + m->tint_r)/2; g = (map->shown_g + m->tint_g)/2; b = (map->shown_b + m->tint_b)/2;
		}
		else
		{
			r = map->shown_r; g = map->shown_g; b = map->shown_b;
		}
		r *= seen;
		g *= seen;
		b *= seen;
		a = seen;
	}
	else
	{
		if (m->tint_r < 1 || m->tint_g < 1 || m->tint_b < 1)
		{
			r = (map->obscure_r + m->tint_r)/2; g = (map->obscure_g + m->tint_g)/2; b = (map->obscure_b + m->tint_b)/2;
		}
		else
		{
			r = map->obscure_r; g = map->obscure_g; b = map->obscure_b;
		}
		a = map->obscure_r;
	}

	/* Reset vertices&all buffers, we are changing texture/shader */
	if ((*cur_tex != m->textures[0]) || m->shader || (m->nb_textures > 1))
	{
		/* Draw remaining ones */
		if (vert_idx) glDrawArrays(GL_QUADS, 0, (*vert_idx) / 2);
		/* Reset */
		(*vert_idx) = 0;
		(*col_idx) = 0;
	}
	if (*cur_tex != m->textures[0])
	{
		glBindTexture(GL_TEXTURE_2D, m->textures[0]);
		*cur_tex = m->textures[0];
	}

	/********************************************************
	 ** Setup all textures we need
	 ********************************************************/
	a = (a > 1) ? 1 : ((a < 0) ? 0 : a);
	int z;
	if (m->shader) useShader(m->shader, dx, dy, map->tile_w, map->tile_h, r, g, b, a);
	for (z = (!shaders_active) ? 0 : (m->nb_textures - 1); z > 0; z--)
	{
		if (multitexture_active && shaders_active) tglActiveTexture(GL_TEXTURE0+z);
		tglBindTexture(m->textures_is3d[z] ? GL_TEXTURE_3D : GL_TEXTURE_2D, m->textures[z]);
	}
	if (m->nb_textures && multitexture_active && shaders_active) tglActiveTexture(GL_TEXTURE0); // Switch back to default texture unit

	/********************************************************
	 ** Compute/display movement and motion blur
	 ********************************************************/
	float animdx = 0, animdy = 0;
	float tlanimdx = 0, tlanimdy = 0;
	if (m->display_last == DL_NONE) m->move_max = 0;
	lua_is_hex(L);
	int is_hex = luaL_checknumber(L, -1);
	if (m->move_max)
	{
		m->move_step += nb_keyframes;
		if (m->move_step >= m->move_max) m->move_max = 0; // Reset once in place
		if (m->display_last == DL_NONE) m->display_last = DL_TRUE;

		if (m->move_max)
		{
			float adx = (float)i - m->oldx;
			float ady = (float)j - m->oldy + 0.5f*(i & is_hex);

			// Motion bluuuurr!
			if (m->move_blur)
			{
				int step;
				for (z = 1; z <= m->move_blur; z++)
				{
					step = m->move_step - z;
					if (step >= 0)
					{
						animdx = tlanimdx = map->tile_w * (adx * step / (float)m->move_max - adx);
						animdy = tlanimdy = map->tile_h * (ady * step / (float)m->move_max - ady);
						dm = m;
						while (dm)
						{
							tglBindTexture(GL_TEXTURE_2D, dm->textures[0]);
							do_quad(L, m, dm, map, vertices, texcoords, colors,
								vert_idx,
								col_idx,
								0,
								dx + dm->dx * map->tile_w + animdx,
								dy + dm->dy * map->tile_h + animdy,
								dx + dm->dx * map->tile_w + tlanimdx,
								dy + dm->dy * map->tile_h + tlanimdy,
								dm->dw,
								dm->dh,
								r, g, b, a,
								(m->next) ? 1 : 0,
								i, j);
							dm = dm->next;
						}
					}
				}
			}

			// Final step
			animdx = tlanimdx = adx * m->move_step / (float)m->move_max - adx;
			animdy = tlanimdy = ady * m->move_step / (float)m->move_max - ady;

			if (m->move_twitch) {
				float where = (0.5 - fabsf(m->move_step / (float)m->move_max - 0.5)) * 2;
				if (m->move_twitch_dir == 4) animdx -= m->move_twitch * where;
				else if (m->move_twitch_dir == 6) animdx += m->move_twitch * where;
				else if (m->move_twitch_dir == 2) animdy += m->move_twitch * where;
				else if (m->move_twitch_dir == 1) { animdx -= m->move_twitch * where; animdy += m->move_twitch * where; }
				else if (m->move_twitch_dir == 3) { animdx += m->move_twitch * where; animdy += m->move_twitch * where; }
				else if (m->move_twitch_dir == 7) { animdx -= m->move_twitch * where; animdy -= m->move_twitch * where; }
				else if (m->move_twitch_dir == 9) { animdx += m->move_twitch * where; animdy -= m->move_twitch * where; }
				else animdy -= m->move_twitch * where;
			}

//			printf("==computing %f x %f : %f x %f // %d/%d\n", animdx, animdy, adx, ady, m->move_step, m->move_max);
		}
	}

//	if ((j - 1 >= 0) && map->grids_important[i][j - 1] && map->grids[i][j-1][9] && !map->grids[i][j-1][9]->move_max) up_important = TRUE;

	/********************************************************
	 ** Display the entity
	 ********************************************************/
	dm = m;
	while (dm)
	{
		tglBindTexture(GL_TEXTURE_2D, dm->textures[0]);
		if (!dm->anim_max) anim = 0;
		else {
			dm->anim_step += (dm->anim_speed * nb_keyframes);
			anim_step = dm->anim_step;
			if (dm->anim_step >= dm->anim_max) {
				dm->anim_step = 0;
				if (dm->anim_loop == 0) dm->anim_max = 0;
				else if (dm->anim_loop > 0) dm->anim_loop--;
			}
			anim = (float)anim_step / dm->anim_max;
		}
		do_quad(L, m, dm, map, vertices, texcoords, colors,
			vert_idx,
			col_idx,
			anim,
			dx + (dm->dx + animdx) * map->tile_w,
			dy + (dm->dy + animdy) * map->tile_h,
			dx + (dm->dx + tlanimdx) * map->tile_w,
			dy + (dm->dy + tlanimdy) * map->tile_h,
			dm->dw,
			dm->dh,
			r, g, b, ((dm->dy < 0) && up_important) ? a / 3 : a,
			m->next ? 1 : 0,
			i, j);
		dm->animdx = animdx;
		dm->animdy = animdy;
		dm = dm->next;
	}

	/********************************************************
	 ** Cleanup
	 ********************************************************/
	if (m->shader || m->nb_textures || m->next)
	{
		/* Draw remaining ones */
		if (vert_idx) glDrawArrays(GL_QUADS, 0, (*vert_idx) / 2);
		/* Reset */
		(*vert_idx) = 0;
		(*col_idx) = 0;
		*cur_tex = 0;
	}
	if (m->shader) glUseProgramObjectARB(0);
	m->display_last = DL_TRUE;
}

#define MIN(a,b) ((a < b) ? a : b)

static int map_to_screen(lua_State *L)
{
	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
	int x = luaL_checknumber(L, 2);
	int y = luaL_checknumber(L, 3);
	int nb_keyframes = luaL_checknumber(L, 4);
	bool always_show = lua_toboolean(L, 5);
	bool changed = lua_toboolean(L, 6);
	int i = 0, j = 0, z = 0;
	int vert_idx = 0;
	int col_idx = 0;
	GLuint cur_tex = 0;
	int mx = map->mx;
	int my = map->my;

	/* Enables Depth Testing */
	//glEnable(GL_DEPTH_TEST);

	GLfloat *vertices = map->vertices;
	GLfloat *colors = map->colors;
	GLfloat *texcoords = map->texcoords;
	glTexCoordPointer(2, GL_FLOAT, 0, texcoords);
	glVertexPointer(2, GL_FLOAT, 0, vertices);
	glColorPointer(4, GL_FLOAT, 0, colors);

	// Smooth scrolling
	// If we use shaders for FOV display it means we must uses fbos for smooth scroll too
	float animdx = 0, animdy = 0;
	if (map->move_max)
	{
		map->move_step += nb_keyframes;
		if (map->move_step >= map->move_max)
		{
			map->move_max = 0;
			map->oldmx = map->mx;
			map->oldmy = map->my;
		}

		if (map->move_max)
		{
			float adx = (float)map->mx - map->oldmx;
			float ady = (float)map->my - map->oldmy;
			animdx = adx * map->move_step / (float)map->move_max;
			animdy = ady * map->move_step / (float)map->move_max;
			mx = (int)(map->oldmx + animdx);
			my = (int)(map->oldmy + animdy);
		}
		changed = TRUE;
	}
	x -= map->tile_w * (animdx + map->oldmx);
	y -= map->tile_h * (animdy + map->oldmy);
	map->used_animdx = animdx;
	map->used_animdy = animdy;

	map->used_mx = mx;
	map->used_my = my;

	int mini = mx - 1, maxi = mx + map->mwidth + 2, minj =  my - 1, maxj = my + map->mheight + 2;

	if(mini < 0)
		mini = 0;
	if(minj < 0)
		minj = 0;
	if(maxi > map->w)
		maxi = map->w;
	if(maxj > map->h)
		maxj = map->h;

	// Always display some more of the map to make sure we always see it all
	for (z = 0; z < map->zdepth; z++)
	{
		for (j = minj; j < maxj; j++)
		{
			for (i = mini; i < maxi; i++)
			{
				int dx = x + i * map->tile_w;
				int dy = y + j * map->tile_h + (i & map->is_hex) * map->tile_h / 2;
				map_object *mo = map->grids[i][j][z];
				if (!mo) continue;

				if ((mo->on_seen && map->grids_seens[j*map->w+i]) || (mo->on_remember && (always_show || map->grids_remembers[i][j])) || mo->on_unknown)
				{
					if (map->grids_seens[j*map->w+i])
					{
						display_map_quad(L, &cur_tex, &vert_idx, &col_idx, map, dx, dy, z, mo, i, j, 1, map->grids_seens[j*map->w+i], nb_keyframes, always_show);
					}
					else
					{
						display_map_quad(L, &cur_tex, &vert_idx, &col_idx, map, dx, dy, z, mo, i, j, 1, 0, nb_keyframes, always_show);
					}
				}
			}
		}
	}

	/* Display any leftovers */
	if (vert_idx) glDrawArrays(GL_QUADS, 0, vert_idx / 2);

	// "Decay" displayed status for all mos
	lua_rawgeti(L, LUA_REGISTRYINDEX, map->mo_list_ref);
	lua_pushnil(L);
	while (lua_next(L, -2) != 0)
	{
		map_object *mo = (map_object*)auxiliar_checkclass(L, "core{mapobj}", -1);
		if (mo->display_last == DL_TRUE) mo->display_last = DL_TRUE_LAST;
		else if (mo->display_last == DL_TRUE_LAST) mo->display_last = DL_NONE;
		lua_pop(L, 1); // Remove value, keep key for next iteration
	}

	/* Disables Depth Testing, we do not need it for the rest of the display */
	//glDisable(GL_DEPTH_TEST);

	if (always_show && changed)
	{
		lua_getglobal(L, "game");
		lua_pushliteral(L, "updateFOV");
		lua_gettable(L, -2);
		if (lua_isfunction(L, -1)) {
			lua_pushvalue(L, -2);
			lua_call(L, 1, 0);
			lua_pop(L, 1);
		}
		else lua_pop(L, 2);
		map_update_seen_texture(map);
		map->seen_changed = FALSE;
	}

	return 0;
}

static int minimap_to_screen(lua_State *L)
{
	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
	int x = luaL_checknumber(L, 2);
	int y = luaL_checknumber(L, 3);
	int mdx = luaL_checknumber(L, 4);
	int mdy = luaL_checknumber(L, 5);
	int mdw = luaL_checknumber(L, 6);
	int mdh = luaL_checknumber(L, 7);
	float transp = luaL_checknumber(L, 8);
	int z = 0, i = 0, j = 0;
	int vert_idx = 0;
	int col_idx = 0;
	GLfloat r, g, b, a;

	int f = (map->is_hex & 1);
	// Create/recreate the minimap data if needed
	if (map->mm_w != mdw || map->mm_h != mdh)
	{
		if (map->mm_texture) glDeleteTextures(1, &(map->mm_texture));
		if (map->minimap) free(map->minimap);

		// In case we can't support NPOT textures round up to nearest POT
		int realw=1;
		int realh=1;
		while (realw < mdw) realw *= 2;
		while (realh < f + (1+f)*mdh) realh *= 2;

		glGenTextures(1, &(map->mm_texture));
		map->mm_w = mdw;
		map->mm_h = mdh;
		map->mm_rw = realw;
		map->mm_rh = realh;
		printf("C Map minimap texture: %d (%dx%d; %dx%d)\n", map->mm_texture, mdw, mdh, realw, realh);
		tglBindTexture(GL_TEXTURE_2D, map->mm_texture);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexImage2D(GL_TEXTURE_2D, 0, 4, realw, realh, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
		map->minimap = calloc(realw*realh*4, sizeof(GLubyte));
	}

	tglBindTexture(GL_TEXTURE_2D, map->mm_texture);

	int ptr;
	GLubyte *mm = map->minimap;
	memset(mm, 0, map->mm_rh * map->mm_rw * 4 * sizeof(GLubyte));

	int mini = mdx, maxi = mdx + mdw, minj = mdy, maxj = mdy + mdh;

	if(mini < 0)
		mini = 0;
	if(minj < 0)
		minj = 0;
	if(maxi > map->w)
		maxi = map->w;
	if(maxj > map->h)
		maxj = map->h;

	for (z = 0; z < map->zdepth; z++)
	{
		for (j = minj; j < maxj; j++)
		{
			for (i = mini; i < maxi; i++)
			{
				map_object *mo = map->grids[i][j][z];
				if (!mo || mo->mm_r < 0) continue;
				ptr = (((1+f)*(j-mdy) + (i & f)) * map->mm_rw + (i-mdx)) * 4;

				if ((mo->on_seen && map->grids_seens[j*map->w+i]) || (mo->on_remember && map->grids_remembers[i][j]) || mo->on_unknown)
				{
					if (map->grids_seens[j*map->w+i])
					{
						r = mo->mm_r; g = mo->mm_g; b = mo->mm_b; a = transp;
					}
					else
					{
						r = mo->mm_r * 0.6; g = mo->mm_g * 0.6; b = mo->mm_b * 0.6; a = transp * 0.6;
					}
					mm[ptr] = b * 255;
					mm[ptr+1] = g * 255;
					mm[ptr+2] = r * 255;
					mm[ptr+3] = a * 255;
					if (f) {
						ptr += 4 * map->mm_rw;
						mm[ptr] = b * 255;
						mm[ptr+1] = g * 255;
						mm[ptr+2] = r * 255;
						mm[ptr+3] = a * 255;
					}
				}
			}
		}
	}
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, map->mm_rw, map->mm_rh, GL_BGRA, GL_UNSIGNED_BYTE, mm);

	// Display it
	GLfloat texcoords[2*4] = {
		0, 0,
		0, (float)((1+f)*mdh)/(float)map->mm_rh,
		(float)mdw/(float)map->mm_rw, (float)((1+f)*mdh)/(float)map->mm_rh,
		(float)mdw/(float)map->mm_rw, 0,
	};
	GLfloat colors[4*4] = {
		1,1,1,1,
		1,1,1,1,
		1,1,1,1,
		1,1,1,1,
	};
	glColorPointer(4, GL_FLOAT, 0, colors);
	glTexCoordPointer(2, GL_FLOAT, 0, texcoords);

	GLfloat vertices[2*4] = {
		x, y,
		x, y + mdh * map->minimap_gridsize,
		x + mdw * map->minimap_gridsize, y + mdh * map->minimap_gridsize,
		x + mdw * map->minimap_gridsize, y,
	};
	glVertexPointer(2, GL_FLOAT, 0, vertices);
	glDrawArrays(GL_QUADS, 0, 4);
//	printf("display mm %dx%d :: %dx%d\n",x,y,mdw,mdh);
	return 0;
}

static const struct luaL_Reg maplib[] =
{
	{"newMap", map_new},
	{"newObject", map_object_new},
	{"mapObjectsToTexture", map_objects_display},
	{"mapObjectsToScreen", map_objects_toscreen},
	{NULL, NULL},
};

static const struct luaL_Reg map_reg[] =
{
	{"__gc", map_free},
	{"close", map_free},
	{"updateSeensTexture", map_update_seen_texture_lua},
	{"bindSeensTexture", map_bind_seen_texture},
	{"drawSeensTexture", map_draw_seen_texture},
	{"setZoom", map_set_zoom},
	{"setShown", map_set_shown},
	{"setObscure", map_set_obscure},
	{"setGrid", map_set_grid},
	{"cleanSeen", map_clean_seen},
	{"cleanRemember", map_clean_remember},
	{"cleanLite", map_clean_lite},
	{"setSeen", map_set_seen},
	{"setRemember", map_set_remember},
	{"setLite", map_set_lite},
	{"setImportant", map_set_important},
	{"getSeensInfo", map_get_seensinfo},
	{"setScroll", map_set_scroll},
	{"getScroll", map_get_scroll},
	{"toScreen", map_to_screen},
	{"toScreenMiniMap", minimap_to_screen},
	{"setupMiniMapGridSize", map_set_minimap_gridsize},
	{NULL, NULL},
};

static const struct luaL_Reg map_object_reg[] =
{
	{"__gc", map_object_free},
	{"texture", map_object_texture},
	{"displayCallback", map_object_cb},
	{"chain", map_object_chain},
	{"tint", map_object_tint},
	{"shader", map_object_shader},
	{"print", map_object_print},
	{"invalidate", map_object_invalid},
	{"isValid", map_object_is_valid},
	{"onSeen", map_object_on_seen},
	{"minimap", map_object_minimap},
	{"resetMoveAnim", map_object_reset_move_anim},
	{"setMoveAnim", map_object_set_move_anim},
	{"getMoveAnim", map_object_get_move_anim},
	{"getMoveAnimRaw", map_object_get_move_anim_raw},
	{"setAnim", map_object_set_anim},
	{NULL, NULL},
};

int luaopen_map(lua_State *L)
{
	auxiliar_newclass(L, "core{map}", map_reg);
	auxiliar_newclass(L, "core{mapobj}", map_object_reg);
	luaL_openlib(L, "core.map", maplib, 0);
	lua_pop(L, 1);
	return 1;
}