#include "gles.h"
#include "rend/TexCache.h"
#include "hw/pvr/pvr_mem.h"

/*
Textures

Textures are converted to native opengl textures
The mapping is done with tcw:tsp -> GL texture. That includes stuff like
filtering/ texture repeat

To save space native formats are used for 1555/565/4444 (only bit shuffling is done)
YUV is converted to 565 (some loss of quality on that)
PALs are decoded to their unpaletted format, 8888 is downcasted to 4444

Mipmaps
	not supported for now

Compresssion
	look into it, but afaik PVRC is not realtime doable
*/

u16 temp_tex_buffer[1024*1024];

typedef void TexConvFP(PixelBuffer* pb,u8* p_in,u32 Width,u32 Height);

struct PvrTexInfo
{
	const char* name;
	int bpp;				//4/8 for pal. 16 for uv, argb
	GLuint type;
	TexConvFP *PL;
	TexConvFP *TW;
	TexConvFP *VQ;
};

PvrTexInfo format[8]=
{
	{"1555", 16,GL_UNSIGNED_SHORT_5_5_5_1,		&tex1555_PL,&tex1555_TW,&tex1555_VQ},	//1555
	{"565", 16,GL_UNSIGNED_SHORT_5_6_5,		&tex565_PL,&tex565_TW,&tex565_VQ},		//565
	{"4444", 16,GL_UNSIGNED_SHORT_4_4_4_4,		&tex4444_PL,&tex4444_TW,&tex4444_VQ},	//4444
	{"yuv", 16,GL_UNSIGNED_SHORT_5_6_5,		&texYUV422_PL,&texYUV422_TW,&texYUV422_VQ},	//yuv
	{"ns/bump", 0},	//ns
	{"pal4", 4,0,0,texPAL4_TW,0},	//pal4
	{"pla8", 8,0,0,texPAL8_TW,0},	//pal8
	{"ns/1555", 0},	//ns, 1555
};

const u32 MipPoint[8] =
{
	0x00006,//8
	0x00016,//16
	0x00056,//32
	0x00156,//64
	0x00556,//128
	0x01556,//256
	0x05556,//512
	0x15556//1024
};

const GLuint PAL_TYPE[4]=
{GL_UNSIGNED_SHORT_5_5_5_1,GL_UNSIGNED_SHORT_5_6_5,GL_UNSIGNED_SHORT_4_4_4_4,GL_UNSIGNED_SHORT_4_4_4_4};


//Texture Cache :)
struct TextureCacheData
{
	TSP tsp;		//dreamcast texture parametters
	TCW tcw;

	GLuint texID;	//gl texture

	//decoded texture info
	u32 sa;			//data start address in vram
	u32 w,h;		//width & height of the texture
	u32 size;		//size, in bytes, in vram

	PvrTexInfo* tex;
	TexConvFP*	texconv;

	bool dirty;
	vram_block* lock_block;

	u32 Lookups;
	u32 Updates;

	//used for palette updates
	u32  pal_local_rev;			//local palette rev
	u32* pal_table_rev;			//table palette rev pointer
	u32  indirect_color_ptr;	//palette color table index for pal. tex
								//VQ quantinisers table for VQ tex
								//a texture can't be both VQ and PAL at the same time

	void PrintTextureName()
	{
		printf("Texture: %s ",tex?tex->name:"?format?");

		if (tcw.VQ_Comp)
			printf(" VQ");

		if (tcw.ScanOrder==0)
			printf(" TW");

		if (tcw.MipMapped)
			printf(" MM");

		if (tcw.StrideSel)
			printf(" Stride");

		printf(" %dx%d @ 0x%X",8<<tsp.TexU,8<<tsp.TexV,tcw.TexAddr<<3);
		printf("\n");
	}

	void SetRepeatMode(GLuint dir,u32 clamp,u32 mirror)
	{
		if (clamp)
			glTexParameteri (GL_TEXTURE_2D, dir, GL_CLAMP_TO_EDGE);
		else 
			glTexParameteri (GL_TEXTURE_2D, dir, mirror?GL_MIRRORED_REPEAT : GL_REPEAT);
	}

	//Create gl texture from tsp/tcw
	void Create()
	{
		//ask gl for tex id
		glGenTextures(1,&texID);

		//Reset state info ..
		Lookups=0;
		Updates=0;
		dirty=true;
		lock_block=0;

		//decode info from tsp/tcw into the texture struct
		tex=&format[tcw.PixelFmt==7?0:tcw.PixelFmt];		//texture format table entry

		sa=(tcw.TexAddr<<3) & VRAM_MASK;		//data start address
		w=8<<tsp.TexU;							//tex width
		h=8<<tsp.TexV;							//tex height

		//bind texture to set modes
		glBindTexture(GL_TEXTURE_2D,texID);

		//set texture repeat mode
		SetRepeatMode(GL_TEXTURE_WRAP_S,tsp.ClampU,tsp.FlipU);	// glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, (tsp.ClampU ? GL_CLAMP_TO_EDGE : (tsp.FlipU ? GL_MIRRORED_REPEAT : GL_REPEAT))) ;
		SetRepeatMode(GL_TEXTURE_WRAP_T,tsp.ClampV,tsp.FlipV);	// glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, (tsp.ClampV ? GL_CLAMP_TO_EDGE : (tsp.FlipV ? GL_MIRRORED_REPEAT : GL_REPEAT))) ;

		glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);

		//set texture filter mode
		if ( tsp.FilterMode == 0 )
		{
			//disable filtering, mipmaps
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
		}
		else
		{
			//bilinear filtering
			//PowerVR supports also trilinear via two passes, but we ignore that for now
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, (tcw.MipMapped && settings.rend.UseMipmaps)?GL_LINEAR_MIPMAP_NEAREST:GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
		}

		//PAL texture
		if (tex->bpp==4)
		{
			pal_table_rev=&pal_rev_16[tcw.PalSelect];
			indirect_color_ptr=tcw.PalSelect<<4;
		}
		else if (tex->bpp==8)
		{
			pal_table_rev=&pal_rev_256[tcw.PalSelect>>4];
			indirect_color_ptr=(tcw.PalSelect>>4)<<8;
		}
		else
		{
			pal_table_rev=0;
		}

		//VQ table (if VQ tex)
		if (tcw.VQ_Comp)
			indirect_color_ptr=sa;

			//Convert a pvr texture into OpenGL
		switch (tcw.PixelFmt)
		{

		case 0:	//0     1555 value: 1 bit; RGB values: 5 bits each
		case 7:	//7     Reserved        Regarded as 1555
		case 1:	//1     565      R value: 5 bits; G value: 6 bits; B value: 5 bits
		case 2:	//2     4444 value: 4 bits; RGB values: 4 bits each
		case 3:	//3     YUV422 32 bits per 2 pixels; YUYV values: 8 bits each
		case 5:	//5     4 BPP Palette   Palette texture with 4 bits/pixel
		case 6:	//6     8 BPP Palette   Palette texture with 8 bits/pixel
			if (tcw.ScanOrder && tex->PL)
			{
				//Texture is stored 'planar' in memory, no deswizzle is needed
				verify(tcw.VQ_Comp==0);
				//Planar textures support stride selection, mostly used for non power of 2 textures (videos)
				int stride=w;
				if (tcw.StrideSel) 
					stride=(TEXT_CONTROL&31)*32;
				//Call the format specific convertion code
				texconv=tex->PL;
				//calculate the size, in bytes, for the locking
				size=w*h*tex->bpp/8;
			}
			else
			{
				verify(w==h || !tcw.MipMapped); // are non square mipmaps supported ? i can't recall right now *WARN*

				if (tcw.VQ_Comp)
				{
					verify(tex->VQ!=0);
					indirect_color_ptr=sa;
					if (tcw.MipMapped)
						sa+=MipPoint[tsp.TexU];
					texconv=tex->VQ;
					size=w*h/8;
				}
				else
				{
					verify(tex->TW!=0)
					if (tcw.MipMapped)
						sa+=MipPoint[tsp.TexU]*tex->bpp/2;
					texconv=tex->TW;
					size=w*h*tex->bpp/8;
				}
			}
			break;

			//4     Bump Map        16 bits/pixel; S value: 8 bits; R value: 8 bits
			//Bump maps are not supported for now
		default:
			printf("Unhandled texture %d\n",tcw.PixelFmt);
			size=w*h*2;
			memset(temp_tex_buffer,0xFFFFFFFF,size);
			texconv=0;
		}
	}

	void Update()
	{
		//texture state tracking stuff
		Updates++;
		dirty=false;

		GLuint textype=tex->type;

		if (pal_table_rev) 
		{
			textype=PAL_TYPE[PAL_RAM_CTRL&3];
			pal_local_rev=*pal_table_rev;		//make sure to update the local rev, so it won't have to redo the tex
		}

		palette_index=indirect_color_ptr;	//might be used if pal. tex
		vq_codebook=(u8*)&vram[indirect_color_ptr];  //might be used if VQ tex

		//texture conversion work
		PixelBuffer pbt;
		pbt.p_buffer_start=pbt.p_current_line=temp_tex_buffer;
		pbt.pixels_per_line=w;

		u32 stride=w;

		if (tcw.StrideSel && tcw.ScanOrder && tex->PL) 
			stride=(TEXT_CONTROL&31)*32;	//i think this needs +1 ?

		if(texconv!=0)
		{
			texconv(&pbt,(u8*)&vram[sa],stride,h);
		}
		else
		{
			//fill it in with a temp color
			printf("UNHANDLED TEXTURE\n");
			memset(temp_tex_buffer,0xF88F8F7F,w*h*2);
		}

		//PrintTextureName();

		//lock the texture to detect changes in it
		lock_block = libCore_vramlock_Lock(sa,sa+size-1,this);

		//upload to opengl !
		glBindTexture(GL_TEXTURE_2D, texID);
		GLuint comps=textype==GL_UNSIGNED_SHORT_5_6_5?GL_RGB:GL_RGBA;
		glTexImage2D(GL_TEXTURE_2D, 0,comps , w, h, 0, comps, textype, temp_tex_buffer);

		if (tcw.MipMapped && settings.rend.UseMipmaps)
			glGenerateMipmap(GL_TEXTURE_2D);
	}

	//true if : dirty or paletted texture and revs don't match
	bool NeedsUpdate() { return (dirty) || (pal_table_rev!=0 && *pal_table_rev!=pal_local_rev); }
	
	void Delete()
	{
		glDeleteTextures(1,&texID);
		if (lock_block)
			libCore_vramlock_Unlock_block(lock_block);
		lock_block=0;
	}
};

#include <map>
map<u64,TextureCacheData> TexCache;
typedef map<u64,TextureCacheData>::iterator TexCacheIter;

//TexCacheList<TextureCacheData> TexCache;

struct FBT
{
	u32 TexAddr;
	GLuint depthb,stencilb;
	GLuint tex;
	GLuint fbo;
};

FBT fb_rtt;

void BindRTT(u32 addy, u32 fbw, u32 fbh, u32 channels, u32 fmt)
{
	FBT& rv=fb_rtt;

	if (rv.fbo) glDeleteFramebuffers(1,&rv.fbo);
	if (rv.tex) glDeleteTextures(1,&rv.tex);
	if (rv.depthb) glDeleteRenderbuffers(1,&rv.depthb);
	if (rv.stencilb) glDeleteRenderbuffers(1,&rv.stencilb);

	rv.TexAddr=addy>>3;

	// Find the largest square power of two texture that fits into the viewport

	// Get the currently bound frame buffer object. On most platforms this just gives 0.
	//glGetIntegerv(GL_FRAMEBUFFER_BINDING, &m_i32OriginalFbo);

	// Generate and bind a render buffer which will become a depth buffer shared between our two FBOs
	glGenRenderbuffers(1, &rv.depthb);
	glBindRenderbuffer(GL_RENDERBUFFER, rv.depthb);

	/*
		Currently it is unknown to GL that we want our new render buffer to be a depth buffer.
		glRenderbufferStorage will fix this and in this case will allocate a depth buffer
		m_i32TexSize by m_i32TexSize.
	*/

	glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24_OES, fbw, fbh);

	glGenRenderbuffers(1, &rv.stencilb);
	glBindRenderbuffer(GL_RENDERBUFFER, rv.stencilb);
	glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, fbw, fbh);

	// Create a texture for rendering to
	glGenTextures(1, &rv.tex);
	glBindTexture(GL_TEXTURE_2D, rv.tex);

	glTexImage2D(GL_TEXTURE_2D, 0, channels, fbw, fbh, 0, channels, fmt, 0);

	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	// Create the object that will allow us to render to the aforementioned texture
	glGenFramebuffers(1, &rv.fbo);
	glBindFramebuffer(GL_FRAMEBUFFER, rv.fbo);

	// Attach the texture to the FBO
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rv.tex, 0);

	// Attach the depth buffer we created earlier to our FBO.
	glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rv.depthb);

	// Check that our FBO creation was successful
	GLuint uStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);

	verify(uStatus == GL_FRAMEBUFFER_COMPLETE);
}

GLuint GetTexture(TSP tsp,TCW tcw)
{       
	if (tcw.TexAddr==fb_rtt.TexAddr && fb_rtt.tex)
	{
		return fb_rtt.tex;
	}

	//lookup texture
	TextureCacheData* tf;
	//= TexCache.Find(tcw.full,tsp.full);
	u64 key=((u64)tcw.full<<32) | tsp.full;

	TexCacheIter tx=TexCache.find(key);

	if (tx!=TexCache.end())
	{
		tf=&tx->second;
	}
	else //create if not existing
	{
		TextureCacheData tfc={0};
		TexCache[key]=tfc;

		tx=TexCache.find(key);
		tf=&tx->second;

		tf->tsp=tsp;
		tf->tcw=tcw;
		tf->Create();
	}

	//update if needed
	if (tf->NeedsUpdate())
		tf->Update();

	//update state for opts/stuff
	tf->Lookups++;

	//return gl texture
	return tf->texID;
}

void killtex()
{
	for (TexCacheIter i=TexCache.begin();i!=TexCache.end();i++)
	{
		i->second.Delete();
	}

	TexCache.clear();
}

void rend_text_invl(vram_block* bl)
{
	TextureCacheData* tcd = (TextureCacheData*)bl->userdata;
	tcd->dirty=true;
	tcd->lock_block=0;

	libCore_vramlock_Unlock_block_wb(bl);
}