// ### OpenGTA3 ###
// textures and texture dictionaries
#include "stdafx.h"
#include "ogEngine.h"
#include "ogPools.h"
#include "ogUtil.h"
#include "gtasaQueue.h"
// TXD
int (*txdIndexByName)(char * name) = (int (__cdecl *)(char *))0x731850;
void (*txdSetCurrent)(DWORD index) = (void (__cdecl *)(DWORD))0x7319C0;
int (*txdAdd)(char * name) = (int (__cdecl *)(char *))0x731C80;
/// void (*txdLoad)(DWORD index, char * filename) = (void (__cdecl *)(DWORD, char *))0x7320B0;
/// DWORD (*readTxdChunk) (void * rwstream) = (DWORD (__cdecl *)(void *))0x730FC0;

static RwTexture * (*readTextureNative)(RwStream * stream) = (RwTexture *(__cdecl *)(RwStream *))0x730E60; // (3)

// TXD 
int txdIndexByNameAlways (char * name) {
	int r = txdIndexByName (name);
	if (r == -1)
		r = txdAdd (name);
	return r;
}

CTxdEntry::CTxdEntry (char * name) {
	dictionary = NULL;
	usageCount = 0;
	parentIndex = -1;
	hash = crc32FromUpcaseString ((BYTE *)name);
}
	
void * CTxdEntry::operator new (size_t) {
	return (*txdPool)->allocate ();
}

static WORD	defaultTxds[4];

static RwTexture * removeTextureCB (RwTexture *texture, void *pData) {
	if (texture->refCount > 1) { // TODO: WTF?
		RwTextureDestroy (texture);
		RwTexDictionaryRemoveTexture (texture);
	}
	return texture;
}

static RwTexture * textureReadCB (const char * name, const char * maskName) {
	return NULL;
}

void txdAddRef (int index) {
	CTxdEntry * txd = (CTxdEntry *)(*txdPool)->at (index);
	if (txd)
		txd->usageCount++;
}

void txdRelease (int index) {
	CTxdEntry * txd = (CTxdEntry *)(*txdPool)->at (index);
	if (txd) {
		if (--(txd->usageCount) <= 0)
			releaseModel (index+20000);
	}
}

DWORD * pl253F2F5_TK = (DWORD *)0xC88018;

DWORD	getDefaultTxd (int n) {	// >> 7316D0 (XREF: 5A4220)
	return defaultTxds[n];
}

static RwTexture * textureFindCB (const char * name) {	
	RwTexDictionary * txd = (RwTexDictionary *)RwTexDictionaryGetCurrent ();
	while (txd) {
		RwTexture * tex = RwTexDictionaryFindNamedTexture (txd, name);
		if (tex)
			return tex;
		txd = *(RwTexDictionary **)(((BYTE *)txd)+*pl253F2F5_TK); // TODO: нормально назвать переменную
	}
	return NULL;
}

static void txdInitParent (CTxdEntry * te) {	// >> 731D50 (2) (NOTE: pointer used instead index)
	if (te->parentIndex == -1)
		return;
	CTxdEntry * parent = (CTxdEntry *)(*txdPool)->at(te->parentIndex);
	*(RwTexDictionary **)(((BYTE *)(te->dictionary))+*pl253F2F5_TK) = parent->dictionary;
	parent->usageCount++;
}

static RwTexture * destroyTextureCB (RwTexture * texture, void * data) { // >> 730E50 (3)
	RwTextureDestroy (texture);
	return NULL;
}


RwTexDictionary * txdReadChunk (RwStream * stream) {
	DWORD length;
	DWORD buffer = 0;
	if (!RwStreamFindChunk (stream, 1, &length, NULL))	// TODO: rwID_STRUCT
		return NULL;
	if (RwStreamRead (stream, &buffer, length) != length)
		return NULL;
	buffer &= 0xFFFF;
///	debug ("txdReadChunk: STRUCT contains %d textures\n", buffer);
	RwTexDictionary * txd = (RwTexDictionary *)RwTexDictionaryCreate ();
	if (!txd)
		return NULL;	
	while (buffer-- > 0) {
		RwTexture * tex = readTextureNative (stream);
		if (!tex) {
			RwTexDictionaryForAllTextures (txd, destroyTextureCB, NULL);
			RwTexDictionaryDestroy ((DWORD)txd);
			return NULL;
		}
		RwTexDictionaryAddTexture ((DWORD)txd, tex);
	}
///	debug ("txdReadChunk: done, size = %d\n", sizeof (CTxdEntry));
	return txd;
}

// TODO: аналогично, этот метод добавляется в объект 
bool txdRead (int index, RwStream * stream) {	// >> 731DD0 (2)	 
	CTxdEntry * te = (CTxdEntry *)(*txdPool)->at (index);
	if (!te)
		return false;
	if (!RwStreamFindChunk (stream, 0x16, NULL, NULL))	// rwID_TEXDICTIONARY
		return false;
	te->dictionary = txdReadChunk (stream);
	if (te->dictionary)
		txdInitParent (te);
	return te->dictionary != NULL;
}

// TODO: Добавить в конструктор CTxdPool
void initTxdPool () {	
	*txdPool = new CPool (5000, 12);	// TODO: сделать нормально инициализацию
	for (int i = 0; i < 4; i++)
		defaultTxds[i] = (*txdPool)->indexOf ((BYTE *)new CTxdEntry ("*"));
	RwTextureSetFindCallBack (textureFindCB);
	RwTextureSetReadCallBack (textureReadCB);
}


// TODO: сделать методом CTxdPool
void txdRemove (short index) {	// >> 731E90
	CPool * p = *txdPool;	// для простоты доступа
	if (index >= p->size || index < 0 || (p->flags[index]&0x80))
		return;
	CTxdEntry * txd = ((CTxdEntry *)p->objects)+index;
	if (txd->dictionary) {
		RwTexDictionaryForAllTextures (txd->dictionary, removeTextureCB, NULL);
		RwTexDictionaryDestroy ((DWORD)txd->dictionary );
		txd->dictionary = NULL;
	}
	if (txd->parentIndex != -1 && !(p->flags[txd->parentIndex]&0x80)) {
		CTxdEntry * parent = ((CTxdEntry *)p->objects)+txd->parentIndex;
		if (--parent->usageCount <= 0)
			releaseModel (txd->parentIndex+20000);
	}
}

// TODO: сделать методом CTxdPool
bool txdGarbageCollect () {	// >> 732060 (XREF: sub_53C900)
	CPool * p = *txdPool;	// для простоты доступа
	for (int i = 0; i < p->size; i++) {
		if (p->flags[i]&0x80)
			continue;
		CTxdEntry * txd = ((CTxdEntry *)p->objects)+i;
		if (txd->usageCount <= 0)
			txdRemove (i);
	}
	return true;
}

// TODO: сделать методом CTxdPool, объединить с txdIndexByNameAlways 
//		(эта функция всегда вызывается после txdAdd, но иногда не делается поиска по индексу)
bool txdLoad (int index, char * filename) {	// >> 7320B0 (14)
	RwStream * stream = RwStreamOpen (rwSTREAMFILENAME, rwSTREAMREAD, filename);	// NOTE: зачем-то эта операция выполнялась в цикле. Нам ведь это не нужно?
	bool status = txdRead (index, stream);
	RwStreamClose (stream, NULL);
	return status;
}