#include "EntityRenderers.h"
#include "Entity.h"
#include "Bitmap.h"
#include "Block.h"
#include "Event.h"
#include "ExtMath.h"
#include "Funcs.h"
#include "Game.h"
#include "Graphics.h"
#include "Model.h"
#include "World.h"
#include "Particle.h"
#include "Drawer2D.h"
#include "Server.h"

/*########################################################################################################################*
*------------------------------------------------------Entity Shadow------------------------------------------------------*
*#########################################################################################################################*/
static cc_bool shadows_boundTex;
static GfxResourceID shadows_VB;
static GfxResourceID shadows_tex;
static float shadow_radius, shadow_uvScale;
struct ShadowData { float y; BlockID block; cc_uint8 alpha; };

/* Circle shadows extend at most 4 blocks vertically */
#define SHADOW_MAX_RANGE 4 
/* Circle shadows on blocks underneath the top block can be chopped up into at most 4 pieces */
#define SHADOW_MAX_PER_SUB_BLOCK (4 * 4) 
/* Circle shadows use at most:
   - 4 vertices for top most block
   - MAX_PER_SUB_BLOCK for everyblock underneath the top block */
#define SHADOW_MAX_PER_COLUMN (4 + SHADOW_MAX_PER_SUB_BLOCK * (SHADOW_MAX_RANGE - 1))
/* Circle shadows may be split across (x,z), (x,z+1), (x+1,z), (x+1,z+1) */
#define SHADOW_MAX_VERTS 4 * SHADOW_MAX_PER_COLUMN

static cc_bool lequal(float a, float b) { return a < b || Math_AbsF(a - b) < 0.001f; }
static void EntityShadow_DrawCoords(struct VertexTextured** vertices, struct Entity* e, struct ShadowData* data, float x1, float z1, float x2, float z2) {
	PackedCol col;
	struct VertexTextured* v;
	Vec3 cen;
	float u1, v1, u2, v2;

	if (lequal(x2, x1) || lequal(z2, z1)) return;
	cen = e->Position;

	u1 = (x1 - cen.x) * shadow_uvScale + 0.5f;
	v1 = (z1 - cen.z) * shadow_uvScale + 0.5f;
	u2 = (x2 - cen.x) * shadow_uvScale + 0.5f;
	v2 = (z2 - cen.z) * shadow_uvScale + 0.5f;
	if (u2 <= 0.0f || v2 <= 0.0f || u1 >= 1.0f || v1 >= 1.0f) return;

	x1 = max(x1, cen.x - shadow_radius); u1 = u1 >= 0.0f ? u1 : 0.0f;
	z1 = max(z1, cen.z - shadow_radius); v1 = v1 >= 0.0f ? v1 : 0.0f;
	x2 = min(x2, cen.x + shadow_radius); u2 = u2 <= 1.0f ? u2 : 1.0f;
	z2 = min(z2, cen.z + shadow_radius); v2 = v2 <= 1.0f ? v2 : 1.0f;

	v   = *vertices;
	col = PackedCol_Make(255, 255, 255, data->alpha);

	v->x = x1; v->y = data->y; v->z = z1; v->Col = col; v->U = u1; v->V = v1; v++;
	v->x = x2; v->y = data->y; v->z = z1; v->Col = col; v->U = u2; v->V = v1; v++;
	v->x = x2; v->y = data->y; v->z = z2; v->Col = col; v->U = u2; v->V = v2; v++;
	v->x = x1; v->y = data->y; v->z = z2; v->Col = col; v->U = u1; v->V = v2; v++;

	*vertices = v;
}

static void EntityShadow_DrawSquareShadow(struct VertexTextured** vertices, float y, float x, float z) {
	PackedCol col = PackedCol_Make(255, 255, 255, 220);
	float     uv1 = 63/128.0f, uv2 = 64/128.0f;
	struct VertexTextured* v = *vertices;

	v->x = x;     v->y = y; v->z = z;     v->Col = col; v->U = uv1; v->V = uv1; v++;
	v->x = x + 1; v->y = y; v->z = z;     v->Col = col; v->U = uv2; v->V = uv1; v++;
	v->x = x + 1; v->y = y; v->z = z + 1; v->Col = col; v->U = uv2; v->V = uv2; v++;
	v->x = x;     v->y = y; v->z = z + 1; v->Col = col; v->U = uv1; v->V = uv2; v++;

	*vertices = v;
}

/* Shadow may extend down multiple blocks vertically */
/* If so, shadow on a block must be 'chopped up' to avoid a shadow underneath block above this one */
static void EntityShadow_DrawCircle(struct VertexTextured** vertices, struct Entity* e, struct ShadowData* data, float x, float z) {
	Vec3 min, max, nMin, nMax;
	int i;
	x = (float)Math_Floor(x); z = (float)Math_Floor(z);
	min = Blocks.MinBB[data[0].block]; max = Blocks.MaxBB[data[0].block];

	EntityShadow_DrawCoords(vertices, e, &data[0], x + min.x, z + min.z, x + max.x, z + max.z);
	for (i = 1; i < 4; i++) 
	{
		if (data[i].block == BLOCK_AIR) return;
		nMin = Blocks.MinBB[data[i].block]; nMax = Blocks.MaxBB[data[i].block];

		EntityShadow_DrawCoords(vertices, e, &data[i], x +  min.x, z + nMin.z, x +  max.x, z +  min.z);
		EntityShadow_DrawCoords(vertices, e, &data[i], x +  min.x, z +  max.z, x +  max.x, z + nMax.z);

		EntityShadow_DrawCoords(vertices, e, &data[i], x + nMin.x, z + nMin.z, x +  min.x, z + nMax.z);
		EntityShadow_DrawCoords(vertices, e, &data[i], x +  max.x, z + nMin.z, x + nMax.x, z + nMax.z);
		min = nMin; max = nMax;
	}
}

static void EntityShadow_CalcAlpha(float playerY, struct ShadowData* data) {
	float height = playerY - data->y;
	if (height <= 6.0f) {
		data->alpha = (cc_uint8)(160 - 160 * height / 6.0f);
		data->y     += 1.0f / 64.0f; 
		return;
	}

	data->alpha = 0;
	if (height <= 16.0f)      data->y += 1.0f / 64.0f;
	else if (height <= 32.0f) data->y += 1.0f / 16.0f;
	else if (height <= 96.0f) data->y += 1.0f / 8.0f;
	else data->y += 1.0f / 4.0f;
}

static cc_bool EntityShadow_GetBlocks(struct Entity* e, int x, int y, int z, struct ShadowData* data) {
	struct ShadowData zeroData = { 0 };
	struct ShadowData* cur;
	float posY, topY;
	cc_bool outside;
	BlockID block; cc_uint8 draw;
	int i;

	for (i = 0; i < 4; i++) { data[i] = zeroData; }
	cur     = data;
	posY    = e->Position.y;
	outside = !World_ContainsXZ(x, z);

	for (i = 0; y >= 0 && i < 4; y--) 
	{
		if (!outside) {
			block = World_GetBlock(x, y, z);
		} else if (y == Env.EdgeHeight - 1) {
			block = Blocks.Draw[Env.EdgeBlock] == DRAW_GAS  ? BLOCK_AIR : BLOCK_BEDROCK;
		} else if (y == Env_SidesHeight - 1) {
			block = Blocks.Draw[Env.SidesBlock] == DRAW_GAS ? BLOCK_AIR : BLOCK_BEDROCK;
		} else {
			block = BLOCK_AIR;
		}

		draw = Blocks.Draw[block];
		if (draw == DRAW_GAS || draw == DRAW_SPRITE || Blocks.IsLiquid[block]) continue;
		topY = y + Blocks.MaxBB[block].y;
		if (topY >= posY + 0.01f) continue;

		cur->block = block; cur->y = topY;
		EntityShadow_CalcAlpha(posY, cur);
		i++; cur++;

		/* Check if the casted shadow will continue on further down. */
		if (Blocks.MinBB[block].x == 0.0f && Blocks.MaxBB[block].x == 1.0f &&
			Blocks.MinBB[block].z == 0.0f && Blocks.MaxBB[block].z == 1.0f) return true;
	}

	if (i < 4) {
		cur->block = Env.EdgeBlock; cur->y = 0.0f;
		EntityShadow_CalcAlpha(posY, cur);
		i++; cur++;
	}
	return true;
}

static void EntityShadow_Draw(struct Entity* e) {
	struct VertexTextured vertices[128]; /* TODO this is less than maxVertes */
	struct VertexTextured* ptr;
	struct ShadowData data[4];
	Vec3 pos;
	float radius;
	int y, count;
	int x1, z1, x2, z2;

	pos = e->Position;
	if (pos.y < 0.0f) return;
	y = min((int)pos.y, World.MaxY);

	radius = 7.0f * min(e->ModelScale.y, 1.0f) * e->Model->shadowScale;
	shadow_radius  = radius / 16.0f;
	shadow_uvScale = 16.0f / (radius * 2.0f);

	ptr = vertices;
	if (Entities.ShadowsMode == SHADOW_MODE_SNAP_TO_BLOCK) {
		x1 = Math_Floor(pos.x); z1 = Math_Floor(pos.z);
		if (!EntityShadow_GetBlocks(e, x1, y, z1, data)) return;

		EntityShadow_DrawSquareShadow(&ptr, data[0].y, x1, z1);
	} else {
		x1 = Math_Floor(pos.x - shadow_radius); z1 = Math_Floor(pos.z - shadow_radius);
		x2 = Math_Floor(pos.x + shadow_radius); z2 = Math_Floor(pos.z + shadow_radius);

		if (EntityShadow_GetBlocks(e, x1, y, z1, data) && data[0].alpha > 0) {
			EntityShadow_DrawCircle(&ptr, e, data, (float)x1, (float)z1);
		}
		if (x1 != x2 && EntityShadow_GetBlocks(e, x2, y, z1, data) && data[0].alpha > 0) {
			EntityShadow_DrawCircle(&ptr, e, data, (float)x2, (float)z1);
		}
		if (z1 != z2 && EntityShadow_GetBlocks(e, x1, y, z2, data) && data[0].alpha > 0) {
			EntityShadow_DrawCircle(&ptr, e, data, (float)x1, (float)z2);
		}
		if (x1 != x2 && z1 != z2 && EntityShadow_GetBlocks(e, x2, y, z2, data) && data[0].alpha > 0) {
			EntityShadow_DrawCircle(&ptr, e, data, (float)x2, (float)z2);
		}
	}

	if (ptr == vertices) return;

	if (!shadows_boundTex) {
		Gfx_BindTexture(shadows_tex);
		shadows_boundTex = true;
	}

	count = (int)(ptr - vertices);
	Gfx_SetDynamicVbData(shadows_VB, vertices, count);
	Gfx_DrawVb_IndexedTris(count);
}


/*########################################################################################################################*
*-----------------------------------------------------Entity Shadows------------------------------------------------------*
*#########################################################################################################################*/
#define sh_size 128
#define sh_half (sh_size / 2)

static void EntityShadows_MakeTexture(void) {
	BitmapCol pixels[sh_size * sh_size];
	BitmapCol color = BitmapCol_Make(0, 0, 0, 200);
	struct Bitmap bmp;
	cc_uint32 x, y;

	Bitmap_Init(bmp, sh_size, sh_size, pixels);
	for (y = 0; y < sh_size; y++) {
		BitmapCol* row = Bitmap_GetRow(&bmp, y);

		for (x = 0; x < sh_size; x++) {
			float dist =
				(sh_half - (x + 0.5f)) * (sh_half - (x + 0.5f)) +
				(sh_half - (y + 0.5f)) * (sh_half - (y + 0.5f));
			row[x] = dist < sh_half * sh_half ? color : 0;
		}
	}
	shadows_tex = Gfx_CreateTexture(&bmp, 0, false);
}

void EntityShadows_Render(void) {
	struct Entity* e;
	int i;
	if (Entities.ShadowsMode == SHADOW_MODE_NONE) return;

	shadows_boundTex = false;
	if (!shadows_tex) 
		EntityShadows_MakeTexture();
	if (!shadows_VB)
		shadows_VB = Gfx_CreateDynamicVb(VERTEX_FORMAT_TEXTURED, SHADOW_MAX_VERTS);

	Gfx_SetAlphaArgBlend(true);
	Gfx_SetDepthWrite(false);
	Gfx_SetAlphaBlending(true);

	Gfx_SetVertexFormat(VERTEX_FORMAT_TEXTURED);
	EntityShadow_Draw(&Entities.CurPlayer->Base);

	if (Entities.ShadowsMode == SHADOW_MODE_CIRCLE_ALL) {	
		for (i = 0; i < ENTITIES_MAX_COUNT; i++) 
		{
			e = Entities.List[i];
			if (!e || !e->ShouldRender || e == &Entities.CurPlayer->Base) continue;
			EntityShadow_Draw(e);
		}
	}

	Gfx_SetAlphaArgBlend(false);
	Gfx_SetDepthWrite(true);
	Gfx_SetAlphaBlending(false);
}


/*########################################################################################################################*
*-----------------------------------------------------Entity nametag------------------------------------------------------*
*#########################################################################################################################*/
static GfxResourceID names_VB;
#define NAME_IS_EMPTY -30000
#define NAME_OFFSET 3 /* offset of back layer of name above an entity */

static void MakeNameTexture(struct Entity* e) {
	cc_string colorlessName; char colorlessBuffer[STRING_SIZE];
	BitmapCol shadowColor = BitmapCol_Make(80, 80, 80, 255);
	BitmapCol origWhiteColor;

	struct DrawTextArgs args;
	struct FontDesc font;
	struct Context2D ctx;
	int width, height;
	cc_string name;

	/* Names are always drawn using default.png font */
	Font_MakeBitmapped(&font, 24, FONT_FLAGS_NONE);
	/* Don't want DPI scaling or padding */
	font.size = 24; font.height = 24;

	name = String_FromRawArray(e->NameRaw);
	DrawTextArgs_Make(&args, &name, &font, false);
	width = Drawer2D_TextWidth(&args);

	if (!width) {
		e->NameTex.ID = 0;
		e->NameTex.x  = NAME_IS_EMPTY;
	} else {
		String_InitArray(colorlessName, colorlessBuffer);
		width  += NAME_OFFSET; 
		height = Drawer2D_TextHeight(&args) + NAME_OFFSET;

		Context2D_Alloc(&ctx, width, height);
		{
			origWhiteColor = Drawer2D.Colors['f'];

			Drawer2D.Colors['f'] = shadowColor;
			Drawer2D_WithoutColors(&colorlessName, &name);
			args.text = colorlessName;
			Context2D_DrawText(&ctx, &args, NAME_OFFSET, NAME_OFFSET);

			Drawer2D.Colors['f'] = origWhiteColor;
			args.text = name;
			Context2D_DrawText(&ctx, &args, 0, 0);
		}
		Context2D_MakeTexture(&e->NameTex, &ctx);
		Context2D_Free(&ctx);
	}
}

static void DrawName(struct Entity* e) {
	struct VertexTextured* vertices;
	struct Model* model;
	struct Matrix mat, transform;
	Vec3 pos;
	float scale;
	Vec2 size;

	if (!e->VTABLE->ShouldRenderName(e)) return;
	if (e->NameTex.x == NAME_IS_EMPTY)   return;
	if (!e->NameTex.ID) MakeNameTexture(e);
	Gfx_BindTexture(e->NameTex.ID);

	if (!names_VB)
		names_VB = Gfx_CreateDynamicVb(VERTEX_FORMAT_TEXTURED, 4);

	model = e->Model;
	Model_GetEntityTransform(model, e, &transform);
	Vec3_TransformY(&pos, model->GetNameY(e), &transform);

	scale  = e->ModelScale.y;
	scale  = scale > 1.0f ? (1.0f/70.0f) : (scale/70.0f);
	size.x = e->NameTex.width * scale; size.y = e->NameTex.height * scale;

	if (Entities.NamesMode == NAME_MODE_ALL_UNSCALED && Entities.CurPlayer->Hacks.CanSeeAllNames) {
		Matrix_Mul(&mat, &Gfx.View, &Gfx.Projection); /* TODO: This mul is slow, avoid it */
		/* Get W component of transformed position */
		scale = pos.x * mat.row1.w + pos.y * mat.row2.w + pos.z * mat.row3.w + mat.row4.w;
		size.x *= scale * 0.2f; size.y *= scale * 0.2f;
	}

	Gfx_SetVertexFormat(VERTEX_FORMAT_TEXTURED);

	vertices = (struct VertexTextured*)Gfx_LockDynamicVb(names_VB, VERTEX_FORMAT_TEXTURED, 4);
	Particle_DoRender(&size, &pos, &e->NameTex.uv, PACKEDCOL_WHITE, vertices);
	Gfx_UnlockDynamicVb(names_VB);

	Gfx_DrawVb_IndexedTris(4);
}

void EntityNames_Delete(struct Entity* e) {
	Gfx_DeleteTexture(&e->NameTex.ID);
	e->NameTex.x = 0; /* X is used as an 'empty name' flag */
}


/*########################################################################################################################*
*-----------------------------------------------------Names rendering-----------------------------------------------------*
*#########################################################################################################################*/
static int closestEntityId;

void EntityNames_Render(void) {
	struct LocalPlayer* p = Entities.CurPlayer;
	cc_bool hadFog;
	int i;

	if (Entities.NamesMode == NAME_MODE_NONE) return;
	if (Server.IsSinglePlayer && Game_NumStates == 1) return;

	closestEntityId = Entities_GetClosest(&p->Base);
	if (!p->Hacks.CanSeeAllNames || Entities.NamesMode != NAME_MODE_ALL) return;

	Gfx_SetAlphaTest(true);
	hadFog = Gfx_GetFog();
	if (hadFog) Gfx_SetFog(false);

	for (i = 0; i < ENTITIES_MAX_COUNT; i++) 
	{
		if (!Entities.List[i]) continue;
		if (i != closestEntityId) DrawName(Entities.List[i]);
	}

	Gfx_SetAlphaTest(false);
	if (hadFog) Gfx_SetFog(true);
}

void EntityNames_RenderHovered(void) {
	struct LocalPlayer* p = Entities.CurPlayer;
	struct Entity* e;
	cc_bool allNames, hadFog;
	cc_bool setupState = false;
	int i;

	if (Entities.NamesMode == NAME_MODE_NONE) return;
	if (Server.IsSinglePlayer && Game_NumStates == 1) return;

	allNames = !(Entities.NamesMode == NAME_MODE_HOVERED || Entities.NamesMode == NAME_MODE_ALL) 
		&& p->Hacks.CanSeeAllNames;

	for (i = 0; i < ENTITIES_MAX_COUNT; i++) 
	{
		e = Entities.List[i];
		if (!e || e == &p->Base) continue;
		if (!allNames && i != closestEntityId) continue;

		/* Only alter the GPU state when actually necessary */
		if (!setupState) {
			Gfx_SetAlphaTest(true);
			Gfx_SetDepthTest(false);
			Gfx_SetDepthWrite(false);

			setupState = true;
			hadFog = Gfx_GetFog();
			if (hadFog) Gfx_SetFog(false);
		}
		DrawName(e);
	}

	if (!setupState) return;
	Gfx_SetAlphaTest(false);
	Gfx_SetDepthTest(true);
	Gfx_SetDepthWrite(true);
	if (hadFog) Gfx_SetFog(true);
}

static void DeleteAllNameTextures(void) {
	int i;
	for (i = 0; i < ENTITIES_MAX_COUNT; i++) 
	{
		if (!Entities.List[i]) continue;
		EntityNames_Delete(Entities.List[i]);
	}
}

static void EntityNames_ChatFontChanged(void* obj) {
	DeleteAllNameTextures();
}


/*########################################################################################################################*
*-----------------------------------------------Entity renderers component------------------------------------------------*
*#########################################################################################################################*/
static void EntityRenderers_ContextLost(void* obj) {
	Gfx_DeleteTexture(&shadows_tex);
	Gfx_DeleteDynamicVb(&shadows_VB);
	
	Gfx_DeleteDynamicVb(&names_VB);
	DeleteAllNameTextures();
}

static void EntityRenderers_Init(void) {
	Event_Register_(&GfxEvents.ContextLost,  NULL, EntityRenderers_ContextLost);
	Event_Register_(&ChatEvents.FontChanged, NULL, EntityNames_ChatFontChanged);
}

static void EntityRenderers_Free(void) {
	EntityRenderers_ContextLost(NULL);
}

struct IGameComponent EntityRenderers_Component = {
	EntityRenderers_Init,  /* Init  */
	EntityRenderers_Free   /* Free  */
};
