//
// Asura_GX_TextureManagement.cpp
//
// Created by KOR - 10 October 2006
//
// Copyright (C) 2006 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

// This file's header.
#include "Rendering/Asura_GX_TextureManagement.h"

// Asura headers
#include "FileAccess/Asura_FileAccess.h"
#include "Rendering/Asura_Render.h"
#include "Debugging/Asura_Screenshot_System.h"

// Platform headers
#include "Rendering/Asura_GX.h"

// Library headers
#include <revolution/gx.h>

GXTexObj*			Asura_GX_Texture_Management::s_apxTextureStorage[ASURA_MAX_NO_OF_TEXTURES] = { NULL };
Asura_GX_AlphaMap	Asura_GX_Texture_Management::s_axAlphaStorage[ASURA_MAX_NO_OF_TEXTURES];
TPLPalettePtr		Asura_GX_Texture_Management::s_apxTexturePaletteStorage[ASURA_MAX_NO_OF_TEXTURES] = { NULL };
u_char				Asura_GX_Texture_Management::s_aucTextureChannelsLoaded[MAX_TEXTURES_PER_POLYGON] = {0};
bool				Asura_GX_Texture_Management::s_abAlphaTextureDifferentRes[MAX_TEXTURES_PER_POLYGON] = { false };

bool Asura_GX_Texture_Management::InitialiseAlphaMapping(Asura_Handle fileHandle)
{
	u_int uNumAlphaMappings;
	Asura_FileAccess::ReadData(fileHandle, &uNumAlphaMappings, sizeof(u_int));
	for(u_int u = 0; u < uNumAlphaMappings; ++u)
	{
		bool bWasNew;
		const char* szSourceFilename = Asura_FileAccess::ReadString(fileHandle);
		int iSourceIndex = Asura_Texture_Management::AddTexture(szSourceFilename + 9, &bWasNew);
		Asura_Assert(!bWasNew, "Should have already loaded source texture");
//		OSReport("Mapping %s ", szSourceFilename);
		const char* szAlphaFilename = Asura_FileAccess::ReadString(fileHandle);
//		OSReport("to alpha texture %s,\n", szAlphaFilename);
		int iAlphaIndex = Asura_Texture_Management::AddTexture(szAlphaFilename + 9, &bWasNew);
		Asura_Assert(!bWasNew, "Should have already loaded alpha texture");
		

		Asura_GX_AlphaMap* pxAlphaMap = &Asura_GX_Texture_Management::s_axAlphaStorage[iSourceIndex];
		if (pxAlphaMap->m_iIndex == -1) // this can happen when the same texture is in multiple files (such as gui files)
		{
			Asura_Assert((pxAlphaMap->m_iIndex == -1) && !pxAlphaMap->m_pxTextureObject, "Alpha texture already exists for this texture.");
			pxAlphaMap->m_iIndex = iAlphaIndex;
			Asura_FileAccess::ReadData(fileHandle, &pxAlphaMap->m_eChannel, sizeof(Asura_RGB_Channel));
		}
		else
		{
			Asura_RGB_Channel uTmp;
			Asura_FileAccess::ReadData(fileHandle, &uTmp, sizeof(Asura_RGB_Channel));
		}
	}	
	return true;
}

void Asura_GX_Texture_Management::SetTextureIndex(int iTextureIndex, int iLoadedIndex)
{
	s_aucTextureChannelsLoaded[iTextureIndex] = 0;
	if (iLoadedIndex == ASURA_TEXTURE_NONE)
	{
		return;
	}

	Asura_Assert(0 <= iLoadedIndex && iLoadedIndex < ASURA_MAX_NO_OF_TEXTURES, "Loaded texture index out of range");
	Asura_Assert(0 <= iTextureIndex && iTextureIndex < GX_MAX_TEXMAP, "Texture index out of range");

	GXTexObj* pxTexObj = Asura_GX_Texture_Management::s_apxTextureStorage[iLoadedIndex];
	if (pxTexObj)
	{
		GXLoadTexObj(pxTexObj, static_cast<GXTexMapID>(GX_TEXMAP0 + iTextureIndex));
		++s_aucTextureChannelsLoaded[iTextureIndex];
		LoadAlphaMap(iTextureIndex, iLoadedIndex);
	}
}

DEBUGVAR float dbg_fLODBias = 0.f;
DEBUGVAR bool dbg_bDisableMipmaps = false;

// Copied and modified from TPLGetGXTexObjFromPaletteCI
bool Asura_GX_Texture_Management::GetTLUTTexObjFromTPL( TPLPalettePtr pxTPL, GXTexObj *pxTexObj, GXTlutObj *pxTlutObj, GXTlut xTlut, u_int uIndex )
{
	if(!pxTPL) { return false; }

	const TPLDescriptorPtr pxTPLDesc = TPLGet(pxTPL, uIndex);

	if(!pxTPLDesc) { return false; }
	//initTexObj with texture values
	GXInitTexObjCI(	pxTexObj, 
		pxTPLDesc->textureHeader->data, 
		pxTPLDesc->textureHeader->width, 
		pxTPLDesc->textureHeader->height, 
		//(GXCITexFmt)pxTPLDesc->textureHeader->format,
		GX_TF_C8,
		pxTPLDesc->textureHeader->wrapS, 
		pxTPLDesc->textureHeader->wrapT, 
		(pxTPLDesc->textureHeader->minLOD == pxTPLDesc->textureHeader->maxLOD || dbg_bDisableMipmaps) ? GX_FALSE : GX_TRUE,
		xTlut );

	GXInitTexObjLOD(pxTexObj, 
		pxTPLDesc->textureHeader->minFilter, 
		pxTPLDesc->textureHeader->magFilter, 
		pxTPLDesc->textureHeader->minLOD, 
		(dbg_bDisableMipmaps || Asura_ScreenShot_System::IsTakingHiResScreenshot()) ? pxTPLDesc->textureHeader->minLOD : pxTPLDesc->textureHeader->maxLOD, 
		dbg_fLODBias + pxTPLDesc->textureHeader->LODBias,
		GX_DISABLE,
		pxTPLDesc->textureHeader->edgeLODEnable,
		GX_ANISO_1);  

	return true;
}

void Asura_GX_Texture_Management::SetTextureIndexTLUT(int iTextureIndex, int iLoadedIndex, GXTlutObj& xTlut)
{
	s_aucTextureChannelsLoaded[iTextureIndex] = 0;
	if (iLoadedIndex == ASURA_TEXTURE_NONE)
	{
		return;
	}

	Asura_Assert(0 <= iLoadedIndex && iLoadedIndex < ASURA_MAX_NO_OF_TEXTURES, "Loaded texture index out of range");
	Asura_Assert(0 <= iTextureIndex && iTextureIndex < GX_MAX_TEXMAP, "Texture index out of range");

	// Make render states think there's nothing loaded, so it doesn't get confused. Much.
	Asura_Render::CurrentStates.RequestTextureIndex(iTextureIndex, ASURA_TEXTURE_NONE);

	GXTexObj xTexObj;
	if(!GetTLUTTexObjFromTPL(s_apxTexturePaletteStorage[iLoadedIndex], &xTexObj, &xTlut, GX_TLUT0, 0))
	{
		return;
	}

	GXLoadTexObj(&xTexObj, static_cast<GXTexMapID>(GX_TEXMAP0 + iTextureIndex));
	GXLoadTlut(&xTlut, GX_TLUT0);

	++s_aucTextureChannelsLoaded[iTextureIndex];

	LoadAlphaMap(iTextureIndex, iLoadedIndex);
}


bool Asura_GX_Texture_Management::GetUnfilteredTexObjFromTPL( TPLPalettePtr pxTPL, GXTexObj *pxTexObj, u_int uIndex )
{
	if(!pxTPL) { return false; }

	const TPLDescriptorPtr pxTPLDesc = TPLGet(pxTPL, uIndex);

	if(!pxTPLDesc) { return false; }

	//initTexObj with texture values
	GXInitTexObj(pxTexObj, 
		pxTPLDesc->textureHeader->data, 
		pxTPLDesc->textureHeader->width, 
		pxTPLDesc->textureHeader->height, 
		(GXTexFmt)pxTPLDesc->textureHeader->format,
		pxTPLDesc->textureHeader->wrapS, 
		pxTPLDesc->textureHeader->wrapT, 
		GX_FALSE); 
		//(pxTPLDesc->textureHeader->minLOD == pxTPLDesc->textureHeader->maxLOD) ? GX_FALSE : GX_TRUE); 


	GXInitTexObjLOD(pxTexObj,
		GX_NEAR_MIP_NEAR,//pxTPLDesc->textureHeader->minFilter, 
		GX_NEAR,//pxTPLDesc->textureHeader->magFilter, 
		pxTPLDesc->textureHeader->minLOD, 
		(dbg_bDisableMipmaps || Asura_ScreenShot_System::IsTakingHiResScreenshot()) ? pxTPLDesc->textureHeader->minLOD : pxTPLDesc->textureHeader->maxLOD, 
		dbg_fLODBias + pxTPLDesc->textureHeader->LODBias, //pxTPLDesc->textureHeader->LODBias,
		GX_DISABLE,//GX_DISABLE,
		pxTPLDesc->textureHeader->edgeLODEnable,
		GX_ANISO_1);  

	return true;
}

void Asura_GX_Texture_Management::SetTextureIndexUnfiltered(int iTextureIndex, int iLoadedIndex)
{
	s_aucTextureChannelsLoaded[iTextureIndex] = 0;
	if (iLoadedIndex == ASURA_TEXTURE_NONE)
	{
		return;
	}

	Asura_Assert(0 <= iLoadedIndex && iLoadedIndex < ASURA_MAX_NO_OF_TEXTURES, "Loaded texture index out of range");
	Asura_Assert(0 <= iTextureIndex && iTextureIndex < GX_MAX_TEXMAP, "Texture index out of range");

	// Make render states think there's nothing loaded, so it doesn't get confused. Much.
	Asura_Render::CurrentStates.RequestTextureIndex(iTextureIndex, ASURA_TEXTURE_NONE);

	GXTexObj xTexObj;
	if(!GetUnfilteredTexObjFromTPL(s_apxTexturePaletteStorage[iLoadedIndex], &xTexObj, 0))
	{
		return;
	}

	GXLoadTexObj(&xTexObj, static_cast<GXTexMapID>(GX_TEXMAP0 + iTextureIndex));

	++s_aucTextureChannelsLoaded[iTextureIndex];

	LoadAlphaMap(iTextureIndex, iLoadedIndex);
}

static const GXTevColorChan g_aeColourChannels[3] = 
{
	GX_CH_BLUE,
	GX_CH_GREEN,
	GX_CH_RED,
};

void Asura_GX_Texture_Management::LoadAlphaMap(int iTextureIndex, int iLoadedIndex)
{
	const Asura_GX_AlphaMap* pxAlphaMap = &Asura_GX_Texture_Management::s_axAlphaStorage[iLoadedIndex];
	GXSetTevSwapModeTable(GX_TEV_SWAP1, GX_CH_RED, GX_CH_RED, GX_CH_RED, GX_CH_ALPHA);
	const GXTexObj* pxAlphaTexObj = pxAlphaMap->m_pxTextureObject;
	if(pxAlphaTexObj)
	{
		const GXTexObj* pxColourTexObj = Asura_GX_Texture_Management::s_apxTextureStorage[iLoadedIndex];
		const u_short uColourWidth = GXGetTexObjWidth(pxColourTexObj);
		const u_short uColourHeight = GXGetTexObjHeight(pxColourTexObj);
		const u_short uAlphaWidth = GXGetTexObjWidth(pxAlphaTexObj);
		const u_short uAlphaHeight = GXGetTexObjHeight(pxAlphaTexObj);
		s_abAlphaTextureDifferentRes[iTextureIndex] = (uColourWidth != uAlphaWidth) || (uColourHeight != uAlphaHeight);
		GXLoadTexObj(pxAlphaTexObj, GetAlphaTexMapID(iTextureIndex));
		++s_aucTextureChannelsLoaded[iTextureIndex];
	}
	else if(pxAlphaMap->m_iIndex != ASURA_TEXTURE_NONE && Asura_GX_Texture_Management::s_apxTextureStorage[pxAlphaMap->m_iIndex])
	{
		GXLoadTexObj(Asura_GX_Texture_Management::s_apxTextureStorage[pxAlphaMap->m_iIndex], GetAlphaTexMapID(iTextureIndex));
		++s_aucTextureChannelsLoaded[iTextureIndex];
		Asura_Assert(pxAlphaMap->m_eChannel >= 0 && pxAlphaMap->m_eChannel < 3, "");
		GXSetTevSwapModeTable(GX_TEV_SWAP1, GX_CH_RED, GX_CH_RED, GX_CH_RED, g_aeColourChannels[pxAlphaMap->m_eChannel]);
	}
}

static void TCPackTile_I8 ( const u_char* pucSource, u_int uX, u_int uY, u_int uWidth, u_int uHeight, u_char* pucDest )
{
	// dstPtr is already zeroed out, so this will take care of padding issue
	// 'realRows', 'realCols' represent actual source image texels remaining
	const u_int uRealRows = Asura_Maths::Min(4u, uHeight - uY);
	const u_int uRealCols = Asura_Maths::Min(8u, uWidth - uX);

	// pack 32B tile 
	for(u_int uRow = 0; uRow < uRealRows; ++uRow)
	{
		// move 8 bytes (8 8-bit texels) per row
		u_char* pucTile = pucDest + (uRow * 8);
		// need to reset ptr each row to account for column padding
		for(u_int uCol = 0; uCol < uRealCols; ++uCol)
		{
			*pucTile = pucSource[(uY + uRow) * uWidth + (uX + uCol)];
			++pucTile;
		}				
	}				
}

void Asura_GX_Texture_Management::SwizzleTexture(const u_char* pucSource, u_char* pucTarget, u_int uWidth, u_int uHeight)
{
	// number of 4x8 texel tile cols, rows including any partial tiles
	const u_int uNumTileCols = ((uWidth + 7) >> 3);
	const u_int uNumTileRows = ((uHeight + 3) >> 2);

	u_char* pucDest = pucTarget;
	Asura_Mem::Set(pucTarget, 0, uWidth * uHeight);
	// numTileRows, numTileCols includes any partial tiles
	for(u_int uTileRow = 0; uTileRow < uNumTileRows; ++uTileRow )
	{		
		for(u_int uTileCol = 0; uTileCol < uNumTileCols; ++uTileCol)
		{
			TCPackTile_I8( pucSource, (uTileCol * 8), (uTileRow * 4), uWidth, uHeight, pucDest);
			pucDest += 32;                  // next 32B cache line
		}			
	}
	DCFlushRange(pucTarget, uWidth * uHeight);
	Asura_Assert(pucDest == pucTarget + uWidth * uHeight, "Wrote unexpected number of bytes");
}