// Open SanAndreas
// (c) 2006-2008 Stanislav "listener" Golovin
// 
// all texture related objects and functions
#include <stdafx.h>
#include <stddef.h>
#include "ogMath.h"
#include "ogMemory.h"
#include "ogRW.h"
#include "ogModels.h"
#include "ogTextures.h"
#include "ogHud.h"

#include "gtaInterface.h"

//=============================================================================
CTextureManager	texMan;
CTxdPool ** txdPool	= (CTxdPool **)0xC8800C;	// TODO: see comment in .h
//static TODO: временно, пока инициализация вынесена наружу
short	defaultTxds[4];	// >> C88004 (2)


CTextureManager::CTextureManager () {
	texAllocator = new CBlockAllocator (sizeof (CTexture), 128);
	txdAllocator = new CBlockAllocator (sizeof (CTexDictionary), 128);

	// TODO: allocate txdPool
	// (*txdPool) = new CTxdPool ()
	// TODO: init callbacks
	// TODO: do other stuff from RwOpenTextureModule and createTxdPool
	currentTxd = NULL;
}

CTextureManager::~CTextureManager() {
	// TODO: free txdPool
	// TODO: cleanup
	// NOTE: do not release block allocators! They are already released in _rwMemoryClose
}

short CTextureManager::getDefaultTxd(int index) {
	return defaultTxds[index];
}

CTexture * CTextureManager::readTexture(const char *name, const char *maskName) {
	CTexture * t = findCB (name);
	if (t)
		t->refCount++;	
	return t;	// NOTE: поскольку read callback не реализован, сделать здесь дальше ничего нельзя
}

CTexDictionary * CTextureManager::readTxdChunk(CRwStream *stream) {
	int length;
	if (!stream->findChunk (1, &length, NULL))	// TODO: rwID_STRUC
		return NULL;

	DWORD texCount;
	if (stream->read (&texCount, length) != length)	//	TODO: проверить, что length <= 4
		return NULL;
	texCount &= 0xFFFF;
	CTexDictionary * txd = new CTexDictionary ();
	
	while (texCount-- > 0) {
		CTexture * tex = RwReadTextureNative (stream);	
		if (!tex) {
			delete txd;
			return NULL;
		}
		txd->addTexture (tex);
	}
	return txd;
}
//=============================================================================
//	CTexDictionary
void * CTexDictionary::operator new(size_t) {
	// return __txdAllocator->allocateEntry ();
	return texMan.txdAllocator->allocateEntry ();
}
void CTexDictionary::operator delete (void * ptr) {
	// __txdAllocator->freeEntry ((BYTE *)ptr);
	texMan.txdAllocator->freeEntry ((BYTE *)ptr);
}

CTexDictionary::CTexDictionary () {
	type = 6; // TXD
	subType = 0;
	flags = 0;
	privateFlags = 0;
	parent = NULL;

	// link created TXD into global list
//	CTextureModule * m = __textureModule;
//	lInInstance.next = m->txdList.next;
//	lInInstance.prev = &(m->txdList);
//	m->txdList.next->prev = &lInInstance;
//	m->txdList.next = &lInInstance;

	texturesInDict.next = texturesInDict.prev = &texturesInDict;

	// initialze plugins
	parent = NULL;
}

CTexDictionary::~CTexDictionary() {
//	CTextureModule * m = __textureModule;	// если удаляется удаляемый словарь - текущий, нужно сбросить текущий словарь
//	if (m->currentTxd == this)
//		m->currentTxd = NULL;
	if (texMan.getCurrentTxd() == this)
		texMan.setCurrentTxd (NULL);
	
	// remove all textures from dictionary
	RwLLLink * cur = texturesInDict.next;
	for (RwLLLink * next = cur->next; cur != &texturesInDict; cur = next, next = cur->next)
		((CTexture *)(((BYTE *)cur)-offsetof (CTexture, lInDictionary)))->destroy ();
//	lInInstance.remove ();
}

void CTexDictionary::addTexture(CTexture *texture) {
	texture->lInDictionary.remove ();
	texture->lInDictionary.insertAfter (&texturesInDict);
}

void CTexDictionary::addAllTexturesFrom(CTexDictionary *txd) {
	RwLLLink * cur = txd->texturesInDict.next;
	for (RwLLLink * next = cur->next; cur != &texturesInDict; cur = next, next = cur->next) {
		cur->remove ();	// remove texture from source dictionary
		cur->insertAfter (&texturesInDict);
	}
}

CTexture * CTexDictionary::findNamedTexture(const char *name) {
	for (RwLLLink * cur = texturesInDict.next; cur != &texturesInDict; cur = cur->next)  {
		CTexture * tex = (CTexture *)(((BYTE *)cur)-offsetof (CTexture, lInDictionary));
		if (!_stricmp (tex->name, name))
			return tex;
	}
	return NULL;
}
//=============================================================================
//	CTexture
void * CTexture::operator new(size_t size) {
	// return __textureAllocator->allocateEntry ();
	return texMan.texAllocator->allocateEntry ();
}
void CTexture::operator delete (void * ptr) {
	// __textureAllocator->freeEntry ((BYTE *)ptr);
	texMan.texAllocator->freeEntry ((BYTE *)ptr);
}

CTexture::CTexture (RwRaster * _raster) {
	raster = _raster;
	dict = NULL;
	name[0] = mask[0] = '\0';
	filterAddressing = 0x1101;
	refCount = 1;
	lInDictionary.next = lInDictionary.prev = &lInDictionary;

	// plugin initialization
	anisotropic = 1;
}

void CTexture::destroy () {
	if (--refCount <= 0)
		delete this;
}

CTexture::~CTexture() {
	// anisotropic plugin - no destructor
	// destroy texture itself
	if (dict) 	// remove texture from dictionary
		lInDictionary.remove ();
	if (raster)
		RwRasterDestroy (raster);	
}

//=============================================================================
//	CTxdEntry
void * CTxdEntry::operator new (size_t) {
	return (*txdPool)->allocate ();
}
void CTxdEntry::operator delete (void * ptr) {
	(*txdPool)->release ((CTxdEntry *)ptr);
}

CTxdEntry::CTxdEntry (char * name) {
	txd = NULL;
	usageCount = 0;
	parentIndex = -1;
	hash = crc32FromUpcaseString ((BYTE *)name);
}

CTxdEntry::~CTxdEntry() {
	if (txd)
		delete txd;
}
//=============================================================================
//	CTxdPool
CTxdPool::CTxdPool () : CPoolBase (5000, sizeof (CTxdEntry)) {
	// TODO: move to CTextureManager::CTextureManager
//	for (int i = 0; i < 4; i++)
//		defaultTxds[i] = indexOf (new CTxdEntry ("*"));
	// TODO: RwTextureSetFindCallBack (textureFindCB);
	// TODO: RwTextureSetReadCallBack (textureReadCB);
//	RwTextureSetFindCallBack (0x731720);
//	RwTextureSetReadCallBack (0x731710);
}

CTexDictionary * CTxdPool::getTxd(int index) {
	CTxdEntry * entry = at (index);
	return entry ? entry->txd : NULL;
}

int CTxdPool::getParentIndex (int index) {
	CTxdEntry * entry = at (index);
	return entry ? entry->parentIndex : -1;
}

void CTxdPool::createTxd(int index) {
	CTxdEntry * entry = at (index);
	if (entry)
		entry->txd = new CTexDictionary ();
}

void CTxdPool::setCurrent(int index) {
	CTxdEntry * entry = at (index);
	texMan.currentTxd = entry ? entry->txd : NULL;
}

void CTxdPool::addRef (int index) {
	CTxdEntry * entry = at (index);
	if (entry)
		entry->usageCount++;
}

void CTxdPool::releaseTxd (int index) {
	CTxdEntry * entry = at (index);
	if (entry) 
		if (--(entry->usageCount) <= 0) 
			releaseModel (index+20000);	
}

void CTxdPool::releaseWithoutUnload(int index) {
	CTxdEntry * entry = at (index);
	if (entry)
		entry->usageCount--;
}

int CTxdPool::getUsageCount(int index) {
	CTxdEntry * entry = at (index);
	return entry ? entry->usageCount : 0;
}

int CTxdPool::createEntry(char * name) {	
	return indexOf (new CTxdEntry (name));
}

void CTxdPool::destroyEntry (int index) {
	CTxdEntry * entry = at (index);
	if (entry)
		delete entry;
}

void CTxdPool::garbageCollect() {
	CTxdEntry * t = (CTxdEntry *)objects;
	for (int i = 0; i < size; i++) 
		if ((flags[i]&0x80) != 0 && t[i].usageCount == 0) 			
			delete &t[i];	
}

// cached value of the index 
static int lastIndex = 0;	// TODO: перенести внутрь CTxdPool

int CTxdPool::addEntry (char * name) {
	return indexOf (new CTxdEntry (name));
}

int CTxdPool::indexByName (char * name) {
	DWORD hash = crc32FromUpcaseString ((BYTE *)name);
	CTxdEntry * ptr = (CTxdEntry *)objects;
	for (int i = lastIndex; i > 0; i--) 
		if (!(flags[i]&0x80) && ptr[i].hash == hash) 
			return (lastIndex = i);
	for (int i = lastIndex; i < size; i++)
		if (!(flags[i]&0x80) && ptr[i].hash == hash) 
			return (lastIndex = i);
	return -1;
}

int CTxdPool::indexByNameAlways(char *name) {
	int index = indexByName (name);
	return index >= 0 ? index : addEntry (name);
}

bool CTxdPool::load(int index, char *filename) {
	CRwFilenameStream s (filename);
	return readEntryWithParent (index, &s);
}

bool CTxdPool::readEntryWithParent(int index, CRwStream *stream) {
	CTxdEntry * te = at (index);
	if (!stream->findChunk (0x16, 0, 0))	// TODO: rwID_TEXDICTIONARY
		return false;

	te->txd = texMan.readTxdChunk (stream);
	if (!te->txd)
		return false;
	initParent (index);
	return true;
}

void CTxdPool::initParent(int index) {
	CTxdEntry * te = at (index);
	if (te->parentIndex == -1)
		return;	// no parent
	CTxdEntry * pe = at (te->parentIndex);
	te->txd->parent = pe->txd;
	pe->usageCount++;
}