#include "GraphicsCommon.h"
#include "GraphicsAPI.h"
#include "Platform.h"
#include "Block.h"
#include "Event.h"
#include "Funcs.h"
#include "ExtMath.h"

UChar Gfx_ApiBuffer[7][String_BufferSize(STRING_SIZE)];
String Gfx_ApiInfo[7] = {
	{ Gfx_ApiBuffer[0], 0, STRING_SIZE }, { Gfx_ApiBuffer[1], 0, STRING_SIZE },
	{ Gfx_ApiBuffer[2], 0, STRING_SIZE }, { Gfx_ApiBuffer[3], 0, STRING_SIZE },
	{ Gfx_ApiBuffer[4], 0, STRING_SIZE }, { Gfx_ApiBuffer[5], 0, STRING_SIZE },
	{ Gfx_ApiBuffer[6], 0, STRING_SIZE },
};

void GfxCommon_Init(void) {
	GfxCommon_quadVb = Gfx_CreateDynamicVb(VERTEX_FORMAT_P3FC4B, 4);
	GfxCommon_texVb = Gfx_CreateDynamicVb(VERTEX_FORMAT_P3FT2FC4B, 4);

	UInt16 indices[GFX_MAX_INDICES];
	GfxCommon_MakeIndices(indices, GFX_MAX_INDICES);
	GfxCommon_defaultIb = Gfx_CreateIb(indices, GFX_MAX_INDICES);
}

void GfxCommon_Free(void) {
	Gfx_DeleteVb(&GfxCommon_quadVb);
	Gfx_DeleteVb(&GfxCommon_texVb);
	Gfx_DeleteIb(&GfxCommon_defaultIb);
}

void GfxCommon_LoseContext(const UChar* reason) {
	Gfx_LostContext = true;
	Platform_Log1("Lost graphics context: %c", reason);

	Event_RaiseVoid(&GfxEvents_ContextLost);
	GfxCommon_Free();
}

void GfxCommon_RecreateContext(void) {
	Gfx_LostContext = false;
	Platform_LogConst("Recreating graphics context");

	Event_RaiseVoid(&GfxEvents_ContextRecreated);
	GfxCommon_Init();
}


void GfxCommon_UpdateDynamicVb_Lines(GfxResourceID vb, void* vertices, Int32 vCount) {
	Gfx_SetDynamicVbData(vb, vertices, vCount);
	Gfx_DrawVb_Lines(vCount);
}

void GfxCommon_UpdateDynamicVb_IndexedTris(GfxResourceID vb, void* vertices, Int32 vCount) {
	Gfx_SetDynamicVbData(vb, vertices, vCount);
	Gfx_DrawVb_IndexedTris(vCount);
}

void GfxCommon_Draw2DFlat(Int32 x, Int32 y, Int32 width, Int32 height, PackedCol col) {
	VertexP3fC4b verts[4];
	VertexP3fC4b v; v.Z = 0.0f; v.Col = col;

	v.X = (Real32)x;           v.Y = (Real32)y;            verts[0] = v;
	v.X = (Real32)(x + width);                             verts[1] = v;
	                           v.Y = (Real32)(y + height); verts[2] = v;
	v.X = (Real32)x;                                       verts[3] = v;

	Gfx_SetBatchFormat(VERTEX_FORMAT_P3FC4B);
	GfxCommon_UpdateDynamicVb_IndexedTris(GfxCommon_quadVb, verts, 4);
}

void GfxCommon_Draw2DGradient(Int32 x, Int32 y, Int32 width, Int32 height, PackedCol top, PackedCol bottom) {
	VertexP3fC4b verts[4];
	VertexP3fC4b v; v.Z = 0.0f;

	v.X = (Real32)x;           v.Y = (Real32)y;            v.Col = top;    verts[0] = v;
	v.X = (Real32)(x + width);                                             verts[1] = v;
	                           v.Y = (Real32)(y + height); v.Col = bottom; verts[2] = v;
	v.X = (Real32)x;                                                       verts[3] = v;

	Gfx_SetBatchFormat(VERTEX_FORMAT_P3FC4B);
	GfxCommon_UpdateDynamicVb_IndexedTris(GfxCommon_quadVb, verts, 4);
}

void GfxCommon_Draw2DTexture(struct Texture* tex, PackedCol col) {
	VertexP3fT2fC4b texVerts[4];
	VertexP3fT2fC4b* ptr = texVerts;
	GfxCommon_Make2DQuad(tex, col, &ptr);
	Gfx_SetBatchFormat(VERTEX_FORMAT_P3FT2FC4B);
	GfxCommon_UpdateDynamicVb_IndexedTris(GfxCommon_texVb, texVerts, 4);
}

void GfxCommon_Make2DQuad(struct Texture* tex, PackedCol col, VertexP3fT2fC4b** vertices) {
	Real32 x1 = (Real32)tex->X, x2 = (Real32)(tex->X + tex->Width);
	Real32 y1 = (Real32)tex->Y, y2 = (Real32)(tex->Y + tex->Height);
#if CC_BUILD_D3D9
	/* NOTE: see "https://msdn.microsoft.com/en-us/library/windows/desktop/bb219690(v=vs.85).aspx", */
	/* i.e. the msdn article called "Directly Mapping Texels to Pixels (Direct3D 9)" for why we have to do this. */
	x1 -= 0.5f; x2 -= 0.5f;
	y1 -= 0.5f; y2 -= 0.5f;
#endif

	VertexP3fT2fC4b* ptr = *vertices;
	VertexP3fT2fC4b v; v.Z = 0.0f; v.Col = col;
	v.X = x1; v.Y = y1; v.U = tex->U1; v.V = tex->V1; ptr[0] = v;
	v.X = x2;           v.U = tex->U2;                ptr[1] = v;
	v.Y = y2;                         v.V = tex->V2;  ptr[2] = v;
	v.X = x1;           v.U = tex->U1;                ptr[3] = v;
	*vertices += 4;
}

bool gfx_hadFog;
void GfxCommon_Mode2D(Int32 width, Int32 height) {
	Gfx_SetMatrixMode(MATRIX_TYPE_PROJECTION);
	struct Matrix ortho;
	Gfx_CalcOrthoMatrix((Real32)width, (Real32)height, &ortho);
	Gfx_LoadMatrix(&ortho);
	Gfx_SetMatrixMode(MATRIX_TYPE_VIEW);
	Gfx_LoadIdentityMatrix();

	Gfx_SetDepthTest(false);
	Gfx_SetAlphaBlending(true);
	gfx_hadFog = Gfx_GetFog();
	if (gfx_hadFog) Gfx_SetFog(false);
}

void GfxCommon_Mode3D(void) {
	Gfx_SetMatrixMode(MATRIX_TYPE_PROJECTION);
	Gfx_LoadMatrix(&Gfx_Projection);
	Gfx_SetMatrixMode(MATRIX_TYPE_VIEW);
	Gfx_LoadMatrix(&Gfx_View);

	Gfx_SetDepthTest(true);
	Gfx_SetAlphaBlending(false);
	if (gfx_hadFog) Gfx_SetFog(true);
}

void GfxCommon_MakeIndices(UInt16* indices, Int32 iCount) {
	Int32 element = 0, i;

	for (i = 0; i < iCount; i += 6) {
		indices[0] = (UInt16)(element + 0);
		indices[1] = (UInt16)(element + 1);
		indices[2] = (UInt16)(element + 2);

		indices[3] = (UInt16)(element + 2);
		indices[4] = (UInt16)(element + 3);
		indices[5] = (UInt16)(element + 0);

		indices += 6; element += 4;
	}
}

void GfxCommon_SetupAlphaState(UInt8 draw) {
	if (draw == DRAW_TRANSLUCENT)       Gfx_SetAlphaBlending(true);
	if (draw == DRAW_TRANSPARENT)       Gfx_SetAlphaTest(true);
	if (draw == DRAW_TRANSPARENT_THICK) Gfx_SetAlphaTest(true);
	if (draw == DRAW_SPRITE)            Gfx_SetAlphaTest(true);
}

void GfxCommon_RestoreAlphaState(UInt8 draw) {
	if (draw == DRAW_TRANSLUCENT)       Gfx_SetAlphaBlending(false);
	if (draw == DRAW_TRANSPARENT)       Gfx_SetAlphaTest(false);
	if (draw == DRAW_TRANSPARENT_THICK) Gfx_SetAlphaTest(false);
	if (draw == DRAW_SPRITE)            Gfx_SetAlphaTest(false);
}


#define alphaMask ((UInt32)0xFF000000UL)
/* Quoted from http://www.realtimerendering.com/blog/gpus-prefer-premultiplication/
   The short version: if you want your renderer to properly handle textures with alphas when using
   bilinear interpolation or mipmapping, you need to premultiply your PNG color data by their (unassociated) alphas. */
static UInt32 GfxCommon_Average(UInt32 p1, UInt32 p2) {
	UInt32 a1 = ((p1 & alphaMask) >> 24) & 0xFF;
	UInt32 a2 = ((p2 & alphaMask) >> 24) & 0xFF;
	UInt32 aSum = (a1 + a2);
	aSum = aSum > 0 ? aSum : 1; /* avoid divide by 0 below */

	/* Convert RGB to pre-multiplied form */
	UInt32 r1 = ((p1 >> 16) & 0xFF) * a1, g1 = ((p1 >> 8) & 0xFF) * a1, b1 = (p1 & 0xFF) * a1;
	UInt32 r2 = ((p2 >> 16) & 0xFF) * a2, g2 = ((p2 >> 8) & 0xFF) * a2, b2 = (p2 & 0xFF) * a2;

	/* https://stackoverflow.com/a/347376
	   We need to convert RGB back from the pre-multiplied average into normal form
	   ((r1 + r2) / 2) / ((a1 + a2) / 2)
	   but we just cancel out the / 2*/
	UInt32 aAve = aSum >> 1;
	UInt32 rAve = (r1 + r2) / aSum;
	UInt32 gAve = (g1 + g2) / aSum;
	UInt32 bAve = (b1 + b2) / aSum;

	return (aAve << 24) | (rAve << 16) | (gAve << 8) | bAve;
}

void GfxCommon_GenMipmaps(Int32 width, Int32 height, UInt8* lvlScan0, UInt8* scan0) {
	UInt32* baseSrc = (UInt32*)scan0;
	UInt32* baseDst = (UInt32*)lvlScan0;
	Int32 srcWidth = width << 1;

	Int32 x, y;
	for (y = 0; y < height; y++) {
		Int32 srcY = (y << 1);
		UInt32* src0 = baseSrc + srcY * srcWidth;
		UInt32* src1 = src0 + srcWidth;
		UInt32* dst = baseDst + y * width;

		for (x = 0; x < width; x++) {
			Int32 srcX = (x << 1);
			UInt32 src00 = src0[srcX], src01 = src0[srcX + 1];
			UInt32 src10 = src1[srcX], src11 = src1[srcX + 1];

			/* bilinear filter this mipmap */
			UInt32 ave0 = GfxCommon_Average(src00, src01);
			UInt32 ave1 = GfxCommon_Average(src10, src11);
			dst[x] = GfxCommon_Average(ave0, ave1);
		}
	}
}

Int32 GfxCommon_MipmapsLevels(Int32 width, Int32 height) {
	Int32 lvlsWidth = Math_Log2(width), lvlsHeight = Math_Log2(height);
	if (Gfx_CustomMipmapsLevels) {
		Int32 lvls = min(lvlsWidth, lvlsHeight);
		return min(lvls, 4);
	} else {
		return max(lvlsWidth, lvlsHeight);
	}
}


void Texture_FromOrigin(struct Texture* tex, GfxResourceID id, Int32 x, Int32 y, Int32 width, Int32 height,
	Real32 u2, Real32 v2) {
	Texture_From(tex, id, x, y, width, height, 0, u2, 0, v2);
}

void Texture_FromRec(struct Texture* tex, GfxResourceID id, Int32 x, Int32 y, Int32 width, Int32 height, struct TextureRec rec) {
	Texture_From(tex, id, x, y, width, height, rec.U1, rec.U2, rec.V1, rec.V2);
}

void Texture_From(struct Texture* tex, GfxResourceID id, Int32 x, Int32 y, Int32 width, Int32 height,
	Real32 u1, Real32 u2, Real32 v1, Real32 v2) {
	tex->ID = id;
	tex->X = x; tex->Y = y;
	tex->Width = width; tex->Height = height;

	tex->U1 = u1; tex->V1 = v1;
	tex->U2 = u2; tex->V2 = v2;
}

void Texture_MakeInvalid(struct Texture* tex) {
	struct Texture empty = { 0 }; *tex = empty;
}

void Texture_Render(struct Texture* tex) {
	Gfx_BindTexture(tex->ID);
	PackedCol white = PACKEDCOL_WHITE;
	GfxCommon_Draw2DTexture(tex, white);
}

void Texture_RenderShaded(struct Texture* tex, PackedCol shadeCol) {
	Gfx_BindTexture(tex->ID);
	GfxCommon_Draw2DTexture(tex, shadeCol);
}
