//textures.cpp
//load textures into OpenGL

#include <OpenGL/gl.h>
#include <OpenGL/glext.h>
#include <OpenGL/glu.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

#include "fileio.h"
#include "gamemem.h"
#include "textures.h"
#include "texturesimport.h"
#include "config.h"
#include "gametime.h"
#include "error.h"
#include "screen.h"
#include "interfaceutil.h"
#include "writeout.h"
#include "S3Decompression.h"

#define gCanMultiTexture true

typedef struct{
	void *next;
	tFileRef tex;
} tTextureList;

typedef struct{
	GLuint ref;
	GLenum target;
} tGLTextureInfo;




tTextureList *gTextureList=NULL;
int gEnableTextureLoad=true;
int gNumTexturesRequested=0;
int gNumTexturesLoaded=0;
int gTexturesQualityModifier=0;

#define kTexturePriorityListFileName "texturepriorities.cfg"
//pass raw pixel data to OpenGL
void TexturesPassPixelsToGL(tFileRef tex,void *data,GLuint channels,GLuint pixType,GLuint pixSize,int xSize,int ySize,GLenum target,int quality)
{	
	int xQual=(quality+1)/3;
	int yQual=quality/3;
	if(xQual<0)xQual=0;
	if(yQual<0)yQual=0;
	if((xSize>=1024||ySize>=1024)&&(xQual==0||yQual==0))
		if(ScreenNoBigTextures())
			xQual=yQual=1;

	//are we using reduced texture quality?
	if(yQual)
	{
		//Calculate size of downsampled texture
		int newXSize=xSize>>xQual>0?xSize>>xQual:1;
		int newYSize=ySize>>yQual>0?ySize>>yQual:1;
		
		//allocate spce for downsampled texture
		void *buffer=MemoryAllocateBlock(newXSize*newYSize*4);
		
		//create downsample texture
		gluScaleImage(pixType,xSize,ySize,pixSize,data,newXSize,newYSize,pixSize,buffer);			
		gluBuild2DMipmaps(target,channels,newXSize,newYSize,pixType,pixSize,buffer);

		MemoryFreeBlock(buffer);
	}	
	else
		gluBuild2DMipmaps(target,channels,xSize,ySize,pixType,pixSize,data);
}

void TexturesLoadImport(tFileRef tex,GLuint target,int quality)
{
	int xSize,ySize;
	
	//Get Buffer
	void *imageBuffer=TexturesLoadImportBuffer(tex,&xSize,&ySize);
	
	//TexturesPassPixelsToGL(tex,imageBuffer,GL_COMPRESSED_RGBA_ARB,GL_RGBA,GL_UNSIGNED_BYTE,xSize,ySize,target,quality);
	TexturesPassPixelsToGL(tex,imageBuffer,GL_RGBA,GL_RGBA,GL_UNSIGNED_BYTE,xSize,ySize,target,quality);
			
	MemoryFreeBlock(imageBuffer);
}

void TexturesPassPixelsToGL3d(tFileRef tex,void *data,GLuint channels,GLuint pixType,GLuint pixSize,int xSize,int ySize,int zSize,GLenum target,int quality)
{
	quality/=3;
	if(quality<0)
		quality=0;
	
	//are we using reduced texture quality?
	if(quality)
	{
		//Calculate size of downsampled texture
		int newXSize=xSize>>quality>0?xSize>>quality:1;
		int newYSize=ySize>>quality>0?ySize>>quality:1;
		
		//allocate spce for downsampled texture
		void *buffer=MemoryAllocateBlock(newXSize*newYSize*zSize*4);
		
		//create downsample texture
		for(int z=0;z<zSize;z++)
			gluScaleImage(pixType,xSize,ySize,pixSize,(char*)data+xSize*ySize*z*4,newXSize,newYSize,pixSize,(char*)buffer+newXSize*newYSize*z*4);
		gluBuild3DMipmaps(target,channels,newXSize,newYSize,zSize,pixType,pixSize,buffer);
		
		MemoryFreeBlock(buffer);
	}	
	else
		gluBuild3DMipmaps(target,channels,xSize,ySize,zSize,pixType,pixSize,data);
}

static inline void ByteSwap(UInt16 *i)
{
	*i = ((*i << 8) | (*i >> 8));
}

void LoadCompressedTexture(tFileRef tex,int quality)
{
	quality/=3;
	if(quality<0)quality=0;
	tTextureCacheFile *cache=(tTextureCacheFile*)FileGetDataPtr(tex);
	tCachedTexture *t0=(tCachedTexture*)(((char*)cache)+EndianS32_BtoN(cache->offsets[0]));
	if((t0->width>=1024||t0->height>=1024)&&quality==0)
		if(ScreenNoBigTextures())
			quality=1;
	for(int i=quality;i<EndianS32_BtoN(cache->numEntries);i++)
	{
		tCachedTexture *t=(tCachedTexture*)(((char*)cache)+EndianS32_BtoN(cache->offsets[i]));
		if(EndianS32_BtoN(t->width)&&EndianS32_BtoN(t->height))
			if(EndianS32_BtoN(t->compressionSize))
				if(ScreenSupportsTextureCompression())
					glCompressedTexImage2DARB(
						EndianS32_BtoN(t->target),
						EndianS32_BtoN(t->level)-quality,
						EndianS32_BtoN(t->internalformat),
						EndianS32_BtoN(t->width),
						EndianS32_BtoN(t->height),
						EndianS32_BtoN(t->border),
						EndianS32_BtoN(t->compressionSize),
						t->data
					);
				else
				{
					UInt16* temp = (UInt16*)t->data;
#if TARGET_RT_BIG_ENDIAN
					for (int i=0;i<t->compressionSize/2;i++)
						ByteSwap(temp++);
#endif
					void* decompData=MemoryAllocateBlock(sizeof(GLuint)*EndianS32_BtoN(t->width)*EndianS32_BtoN(t->height));
					void* decompData2=MemoryAllocateBlock(sizeof(GLuint)*EndianS32_BtoN(t->width)*EndianS32_BtoN(t->height));
					DecompressDXT3(EndianS32_BtoN(t->width),EndianS32_BtoN(t->height),(UInt32*)t->data,(UInt32*)decompData);
#if TARGET_RT_BIG_ENDIAN
					for(int y=0;y<t->height;y++)
						for(int x=0;x<t->width;x++)
						{
							UInt32 d=((UInt32*)decompData)[x+(y&1?y-1:y+1)*t->width];
							UInt8 b=d>>24;
							UInt8 g=d>>16;
							UInt8 r=d>>8;
							UInt8 a=((UInt32*)decompData)[x+y*t->width];
							((UInt32*)decompData2)[x+y*t->width]=(r<<24)|(g<<16)|(b<<8)|a;
						}			
					glTexImage2D(EndianS32_BtoN(t->target)
						,EndianS32_BtoN(t->level)-quality
						,GL_RGBA
						,EndianS32_BtoN(t->width)
						,EndianS32_BtoN(t->height)
						,EndianS32_BtoN(t->border)
						,GL_RGBA,GL_UNSIGNED_BYTE,decompData2);
#else
					glTexImage2D(EndianS32_BtoN(t->target)
						,EndianS32_BtoN(t->level)-quality
						,GL_RGBA
						,EndianS32_BtoN(t->width)
						,EndianS32_BtoN(t->height)
						,EndianS32_BtoN(t->border)
						,GL_RGBA,GL_UNSIGNED_BYTE,decompData);
#endif
						
					MemoryFreeBlock(decompData);
					MemoryFreeBlock(decompData2);
				}
			else		
				glTexImage2D(
					EndianS32_BtoN(t->target),
					EndianS32_BtoN(t->level)-quality,
					EndianS32_BtoN(t->internalformat),
					EndianS32_BtoN(t->width),
					EndianS32_BtoN(t->height),
					EndianS32_BtoN(t->border),
					EndianS32_BtoN(t->format),
					EndianS32_BtoN(t->type),
					t->data
				);
	}
}


#include "error.h"
//load a texture from the file referenced by tex
void TexturesLoadFromFile(tFileRef tex,GLenum target)
{
	int quality=gConfig->textureQuality+gTexturesQualityModifier;
	
	//if the file type is a raw pixel format, just pass the data to OpenGL
	char *fileExtension=FileGetExtension(tex);
	if(!_stricmp(fileExtension,kFileTypeCompressedTexture)){ //precompressed texture
		LoadCompressedTexture(tex,quality);
	}else if(!_stricmp(fileExtension,kFileTypeRAW)){ //RAW RGB image
		if(void *textureData=FileGetDataPtr(tex)){
			int size=sqrt(FileGetSize(tex)/3);
			TexturesPassPixelsToGL(tex,textureData,GL_RGB,GL_RGB,GL_UNSIGNED_BYTE,size,size,target,quality);
		}
	}else if(!_stricmp(fileExtension,kFileTypeRAW3d)){ //RAW 3D RGBA image
		if(void *textureData=FileGetDataPtr(tex)){
			int size=sqrt(FileGetSize(tex)/8);
			if(ScreenSupports3DTextures())
				TexturesPassPixelsToGL3d(tex,textureData,GL_RGBA,GL_RGBA,GL_UNSIGNED_BYTE,size,size,2,target,quality);
			else
				TexturesPassPixelsToGL(tex,textureData,GL_RGBA,GL_RGBA,GL_UNSIGNED_BYTE,size,size,target,quality);
		}
	}else if(!_stricmp(fileExtension,kFileTypeGrayscaleRAW)){ //RAW grayscale image
		if(void *textureData=FileGetDataPtr(tex)){
			int size=sqrt(FileGetSize(tex));
			TexturesPassPixelsToGL(tex,textureData,1,GL_LUMINANCE,GL_UNSIGNED_BYTE,size,size,target,quality);
		}
	}else if(!_stricmp(fileExtension,".RGBA")){ //RAW RGBA image
		if(void *textureData=FileGetDataPtr(tex)){
			int size=sqrt(FileGetSize(tex)/4);
			TexturesPassPixelsToGL(tex,textureData,GL_RGBA,GL_RGBA,GL_UNSIGNED_BYTE,size,size,target,quality);
		}
	}
	//otherwise decompress image data
	else TexturesLoadImport(tex,target,quality); //other image formats
}

int TexturesSelectTextureUnit(int unit)
{
	GLint numTextureUnits;
	glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB,&numTextureUnits);
	if(unit<numTextureUnits)
	{
		glActiveTextureARB(GL_TEXTURE0_ARB+unit);
		return true;
	}
	else
		return false;
}

//fades out the alpha values of mip map LOD textures 
//(used to avoid subpixel artifacts for the blended skid marks)
void TexturesAlphaFadeMipMaps() 
{
	int n=1,err;
	GLint width,height;
	unsigned char *buffer;
	glGetError(); //Clear Error info
	glGetTexLevelParameteriv(GL_TEXTURE_2D,0,GL_TEXTURE_WIDTH,&width);
	glGetTexLevelParameteriv(GL_TEXTURE_2D,0,GL_TEXTURE_HEIGHT,&height);
	if(!glGetError())
	{
		buffer=(unsigned char*)MemoryAllocateBlock(width*height*4);
		do
		{
			glGetTexImage(GL_TEXTURE_2D,n,GL_RGBA,GL_UNSIGNED_BYTE,buffer);
			if(!(err=glGetError())){
				glGetTexLevelParameteriv(GL_TEXTURE_2D,n,GL_TEXTURE_WIDTH,&width);
				glGetTexLevelParameteriv(GL_TEXTURE_2D,n,GL_TEXTURE_HEIGHT,&height);
				for(int pix=0;pix<width*height*4;pix++)
					buffer[pix+3]/=n;
				glTexImage2D(GL_TEXTURE_2D,n,4,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE,buffer);
				n++;
			}
		}while(!err);
	}
	MemoryFreeBlock(buffer);
}

//create a new texture reference, and load the texture file referenced to by tex
void TexturesLoadTex(tFileRef tex)
{
	if(!gEnableTextureLoad)
	{
		if(!gFileTable[tex].tagged)
		{
			gFileTable[tex].tagged=-1+gTexturesQualityModifier;
			gNumTexturesRequested++;
		}
		return;
	}
	tGLTextureInfo *info=(tGLTextureInfo*)MemoryAllocateBlock(sizeof(tGLTextureInfo));

	info->target=GL_TEXTURE_2D;
	char *fileExtension=FileGetExtension(tex);
	if(fileExtension)
		if(!_stricmp(fileExtension,kFileTypeRAW3d))
			if(ScreenSupports3DTextures())
				info->target=GL_TEXTURE_3D;
		
	glGenTextures(1,&info->ref);
	glBindTexture(info->target,info->ref);
	//glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE,GL_TRUE); 
	//glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_STORAGE_HINT_APPLE , GL_STORAGE_SHARED_APPLE);
	TexturesLoadFromFile(tex,info->target);
	glTexParameteri(info->target,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(info->target,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexParameteri(info->target,GL_TEXTURE_MIN_FILTER,gConfig->textureFilter?GL_LINEAR_MIPMAP_LINEAR:GL_LINEAR_MIPMAP_NEAREST);
	if(gConfig->textureFilter==2)
		glTexParameterf(info->target,GL_TEXTURE_MAX_ANISOTROPY_EXT,10);

	
	tTextureList *textureList=gTextureList;
	gTextureList=(tTextureList*)MemoryAllocateBlock(sizeof(tTextureList));
	gTextureList->next=(void*)textureList;
	gTextureList->tex=tex;
	gNumTexturesLoaded++;

	gFileTable[tex].parsedData=(void*)info;
	gFileTable[tex].parsed=true;
}

void TexturesUnloadAll()
{
	while(gTextureList!=NULL)
	{
		tTextureList *next=(tTextureList*)gTextureList->next;
		glDeleteTextures(1,&((tGLTextureInfo*)(gFileTable[gTextureList->tex].parsedData))->ref);
		MemoryFreeBlock(gFileTable[gTextureList->tex].parsedData);
		gFileTable[gTextureList->tex].parsed=false;
		FileReleaseData(gTextureList->tex);
		MemoryFreeBlock(gTextureList);
		gTextureList=next;
	}
}

//Selects the texture in the file referenced to by tex
void TexturesSelectTex(tFileRef tex)
{
	if(tex==kFileErr)
		tex=FileGetReference("null.raw");

	//is the texture loaded?
	if(!gFileTable[tex].parsed)
		TexturesLoadTex(tex);
	if(gFileTable[tex].parsed){
		if(((tGLTextureInfo*)gFileTable[tex].parsedData)->target==GL_TEXTURE_3D)
		{
			glDisable(GL_TEXTURE_2D);
			glEnable(GL_TEXTURE_3D);
		}
		else{
			glEnable(GL_TEXTURE_2D);
			glDisable(GL_TEXTURE_3D);			
		}
	//	GLboolean b;
	//	if(!glAreTexturesResident(1,(GLuint*)gFileTable[tex].parsedData,&b))
	//		SysBeep(0);
		glBindTexture(((tGLTextureInfo*)gFileTable[tex].parsedData)->target,((tGLTextureInfo*)gFileTable[tex].parsedData)->ref);
	}	
}
