#include "SelectionBox.h"
#include "ExtMath.h"
#include "Graphics.h"
#include "Event.h"
#include "Funcs.h"
#include "Game.h"
#include "Camera.h"

#ifdef CC_BUILD_NETWORKING
/* Data for a selection box. */
struct SelectionBox {
	Vec3 p0, p1;
	PackedCol color;
	float minDist, maxDist;
};

#define X0 0
#define X1 1
#define Y0 0
#define Y1 2
#define Z0 0
#define Z1 4

#define SelectionBox_Y(y) X0|y |Z0, X0|y |Z1, X1|y |Z1, X1|y |Z0,
#define SelectionBox_Z(z) X0|Y0|z , X0|Y1|z , X1|Y1|z , X1|Y0|z ,
#define SelectionBox_X(x) x |Y0|Z0, x |Y1|Z0, x |Y1|Z1, x |Y0|Z1,

static void BuildFaces(struct SelectionBox* box, struct VertexColoured* v) {
	static const cc_uint8 faceIndices[24] = {
		SelectionBox_Y(Y0) SelectionBox_Y(Y1) /* YMin, YMax */
		SelectionBox_Z(Z0) SelectionBox_Z(Z1) /* ZMin, ZMax */
		SelectionBox_X(X0) SelectionBox_X(X1) /* XMin, XMax */
	};
	PackedCol color;
	int i, flags;

	float offset = box->minDist < 32.0f * 32.0f ? (1/32.0f) : (1/16.0f);
	Vec3 coords[2];
	Vec3_Add1(&coords[0], &box->p0, -offset);
	Vec3_Add1(&coords[1], &box->p1,  offset);

	color = box->color;
	for (i = 0; i < Array_Elems(faceIndices); i++, v++) {
		flags  = faceIndices[i];
		v->x   = coords[(flags     ) & 1].x;
		v->y   = coords[(flags >> 1) & 1].y;
		v->z   = coords[(flags >> 2)    ].z;
		v->Col = color;
	}
}

static void BuildEdges(struct SelectionBox* box, struct VertexColoured* v) {
	static const cc_uint8 edgeIndices[24] = {
		X0|Y0|Z0, X1|Y0|Z0,  X1|Y0|Z0, X1|Y0|Z1,  X1|Y0|Z1, X0|Y0|Z1,  X0|Y0|Z1, X0|Y0|Z0, /* YMin */
		X0|Y1|Z0, X1|Y1|Z0,  X1|Y1|Z0, X1|Y1|Z1,  X1|Y1|Z1, X0|Y1|Z1,  X0|Y1|Z1, X0|Y1|Z0, /* YMax */
		X0|Y0|Z0, X0|Y1|Z0,  X1|Y0|Z0, X1|Y1|Z0,  X1|Y0|Z1, X1|Y1|Z1,  X0|Y0|Z1, X0|Y1|Z1, /* X/Z  */
	};
	PackedCol color;
	int i, flags;

	float offset = box->minDist < 32.0f * 32.0f ? (1/32.0f) : (1/16.0f);
	Vec3 coords[2];
	Vec3_Add1(&coords[0], &box->p0, -offset);
	Vec3_Add1(&coords[1], &box->p1,  offset);

	color = box->color;
	/* invert R/G/B for surrounding line */
	color = (color & PACKEDCOL_A_MASK) | (~color & PACKEDCOL_RGB_MASK);

	for (i = 0; i < Array_Elems(edgeIndices); i++, v++) {
		flags  = edgeIndices[i];
		v->x   = coords[(flags     ) & 1].x;
		v->y   = coords[(flags >> 1) & 1].y;
		v->z   = coords[(flags >> 2)    ].z;
		v->Col = color;
	}
}

static int CompareDists(struct SelectionBox* a, struct SelectionBox* b) {
	float aDist, bDist;
	if (a->minDist == b->minDist) {
		aDist = a->maxDist; bDist = b->maxDist;
	} else {
		aDist = a->minDist; bDist = b->minDist;
	}

	/* Reversed comparison order result, because we need to render back to front for alpha blending */
	if (aDist < bDist) return 1;
	if (aDist > bDist) return -1;
	return 0;
}

static void CalcDists(struct SelectionBox* box, Vec3 P) {
	float dx0 = (P.x - box->p0.x) * (P.x - box->p0.x), dx1 = (P.x - box->p1.x) * (P.x - box->p1.x);
	float dy0 = (P.y - box->p0.y) * (P.y - box->p0.y), dy1 = (P.y - box->p1.y) * (P.y - box->p1.y);
	float dz0 = (P.z - box->p0.z) * (P.z - box->p0.z), dz1 = (P.z - box->p1.z) * (P.z - box->p1.z);

	/* Distance to closest and furthest of the eight box corners */
	box->minDist = min(dx0, dx1) + min(dy0, dy1) + min(dz0, dz1);
	box->maxDist = max(dx0, dx1) + max(dy0, dy1) + max(dz0, dz1);
}


#define SELECTIONS_MAX 256
#define SELECTIONS_VERTICES 24
#define SELECTIONS_MAX_VERTICES SELECTIONS_MAX * SELECTIONS_VERTICES

static int selections_count;
static struct SelectionBox selections_list[SELECTIONS_MAX];
static cc_uint8 selections_ids[SELECTIONS_MAX];
static GfxResourceID selections_VB, selections_LineVB;

void Selections_Add(cc_uint8 id, const IVec3* p1, const IVec3* p2, PackedCol color) {
	struct SelectionBox sel;
	IVec3_ToVec3(&sel.p0, p1);
	IVec3_ToVec3(&sel.p1, p2);
	sel.color = color;

	Selections_Remove(id);
	selections_list[selections_count] = sel;
	selections_ids[selections_count]  = id;
	selections_count++;
}

void Selections_Remove(cc_uint8 id) {
	int i;
	for (i = 0; i < selections_count; i++) {
		if (selections_ids[i] != id) continue;

		for (; i < selections_count - 1; i++) {
			selections_list[i] = selections_list[i + 1];
			selections_ids[i]  = selections_ids[i + 1];
		}

		selections_count--;
		return;
	}
}

static void Selections_ContextLost(void* obj) {
	Gfx_DeleteDynamicVb(&selections_VB);
	Gfx_DeleteDynamicVb(&selections_LineVB);
}

static void AllocateVertexBuffers(void) {
	selections_VB     = Gfx_CreateDynamicVb(VERTEX_FORMAT_COLOURED, SELECTIONS_MAX_VERTICES);
	selections_LineVB = Gfx_CreateDynamicVb(VERTEX_FORMAT_COLOURED, SELECTIONS_MAX_VERTICES);
}

static void Selections_QuickSort(int left, int right) {
	cc_uint8* values = selections_ids; cc_uint8 value;
	struct SelectionBox* keys = selections_list; struct SelectionBox key;

	while (left < right) {
		int i = left, j = right;
		struct SelectionBox* pivot = &keys[(i + j) >> 1];

		/* partition the list */
		while (i <= j) {
			while (CompareDists(pivot, &keys[i]) > 0) i++;
			while (CompareDists(pivot, &keys[j]) < 0) j--;
			QuickSort_Swap_KV_Maybe();
		}
		/* recurse into the smaller subset */
		QuickSort_Recurse(Selections_QuickSort)
	}
}

void Selections_Render(void) {
	struct VertexColoured* data;
	Vec3 cameraPos;
	int i, count;
	if (!selections_count) return;

	/* TODO: Proper selection box sorting. But this is very difficult because
	   we can have boxes within boxes, intersecting boxes, etc. Probably not worth it. */
	cameraPos = Camera.CurrentPos;
	for (i = 0; i < selections_count; i++) {
		CalcDists(&selections_list[i], cameraPos);
	}
	Selections_QuickSort(0, selections_count - 1);

	/* lazy init as most servers don't use this */
	if (!selections_VB) AllocateVertexBuffers();

	count = selections_count * SELECTIONS_VERTICES;
	Gfx_SetVertexFormat(VERTEX_FORMAT_COLOURED);

	data = (struct VertexColoured*)Gfx_LockDynamicVb(selections_LineVB, 
										VERTEX_FORMAT_COLOURED, count);
	for (i = 0; i < selections_count; i++, data += SELECTIONS_VERTICES) {
		BuildEdges(&selections_list[i], data);
	}
	Gfx_UnlockDynamicVb(selections_LineVB);
	Gfx_DrawVb_Lines(count);

	data = (struct VertexColoured*)Gfx_LockDynamicVb(selections_VB, 
										VERTEX_FORMAT_COLOURED, count);
	for (i = 0; i < selections_count; i++, data += SELECTIONS_VERTICES) {
		BuildFaces(&selections_list[i], data);
	}
	Gfx_UnlockDynamicVb(selections_VB);

	Gfx_SetDepthWrite(false);
	Gfx_SetAlphaBlending(true);
	Gfx_DrawVb_IndexedTris(count);
	Gfx_SetDepthWrite(true);
	Gfx_SetAlphaBlending(false);
}
#else
static int selections_count;
void Selections_Render(void) { }
static void Selections_ContextLost(void* obj) { }
#endif


/*########################################################################################################################*
*--------------------------------------------------Selections component---------------------------------------------------*
*#########################################################################################################################*/
static void OnInit(void) {
	Event_Register_(&GfxEvents.ContextLost, NULL, Selections_ContextLost);
}

static void OnReset(void) { selections_count = 0; }

static void OnFree(void) { Selections_ContextLost(NULL); }

struct IGameComponent Selections_Component = {
	OnInit,  /* Init  */
	OnFree,  /* Free  */
	OnReset, /* Reset */
	OnReset  /* OnNewMap */
};
