// ### OpenGTA3 ###
// RenderWare streams implementation (Plugin 0x404)

#include "stdafx.h"
#include "ogEngine.h"

static int	__streamModuleOffset;	// TODO: move to CStreamModule
static int __streamModuleUsageCount = 0;
static CBlockAllocator * __streamAllocator = NULL;

// TODO: по ходу оборачивания плагинов в объекты, собрать объект CStreamModule
// NOTE: в оригинале, __streamAllocator хранился в engine. Поскольку все обращения все равно шли через RwEngineInstance, 
//		используется статическая переменная, чтобы не заморачитьвася с приведением типов. При переносе в объект это будет поправлено
void * _rwStreamModuleOpen (void * object, int offset, int size) {	// NOTE: pluginConstructCB
	debug ("_rwStreamModuleOpen %08x (%d, %d), size = %d\n", (DWORD)object, offset, size, sizeof (RwStream));

	__streamModuleOffset = offset;
	__streamAllocator = new CBlockAllocator (sizeof (RwStream), 16);
	// *(CBlockAllocator **)(((BYTE *)(*engine))+offset) = __streamAllocator;	
	if (__streamAllocator) {
		__streamModuleUsageCount++;
		return object;
	}
	return NULL;
}

void * _rwStreamModuleClose (void * object, int offset, int size) {	// NOTE: pluginDestructCB
	debug ("_rwStreamModuleClose %08x\n", (DWORD)object);
	if (__streamAllocator)
		delete __streamAllocator;
	__streamModuleUsageCount--;
	return object;
}

RwStream * RwStreamOpen (RwStreamType type, RwStreamAccessType accessType, void * data) {
	debug ("RwStreamOpen: %d, %d\n", type, accessType);
	if ((type != rwSTREAMFILENAME && type != rwSTREAMMEMORY) || accessType != rwSTREAMREAD) {
		debug ("RwStreamOpen: ERROR: Unsupported type/accessType combination\n");
		return NULL;
	}

	RwStream * stream = (RwStream *)__streamAllocator->allocateEntry ();
	debug ("RwStreamOpen: allocated stream %08x\n", (DWORD)stream);
	if (!stream)
		return NULL;

	stream->type = type;
	stream->accessType = accessType;
	stream->owned = true;

	switch (type) {
	case rwSTREAMFILENAME:
		stream->file.file = fopen ((char *)data, "rb");
		if (!stream->file.file) {
			debug ("rwStreamOpen: ERROR: unable to open file '%s'\n", (char *)data);
			__streamAllocator->freeEntry ((BYTE *)stream);
			return NULL;
		}
		break;
	case rwSTREAMMEMORY:
		stream->memory.block = ((RwMemory *)data)->start;
		stream->memory.size = ((RwMemory *)data)->length;
		stream->memory.position = 0;
		break;
	}
	return stream;
}

bool RwStreamClose (RwStream * stream, void * data) {
	// NOTE: поскольку открыть поток с "неправильным" типом мы не могли, дополнительных проверок не делается
	bool rc = true;
	if (stream->type == rwSTREAMFILENAME) 
		rc = fclose ((FILE *)stream->file.file) == 0;
	if (stream->owned)
		__streamAllocator->freeEntry ((BYTE *)stream);
	return rc;
}

RwStream * RwStreamWrite (RwStream * stream, void * buffer, DWORD length) {
	debug ("RwStreamWrite: ERROR: unsupported operation\n");
	return stream;
}

DWORD RwStreamRead(RwStream * stream, void *buffer, DWORD length) {
	DWORD l;
	if (stream->type == rwSTREAMFILENAME) {
		l = fread (buffer, 1, length, (FILE *)stream->file.file);
		if (l != length) {			
			if (feof ((FILE *)stream->file.file)) 
				debug ("RwStreamRead: ERROR: premature end of file\n");
			else
				debug ("RwStreamRead: ERROR: file read error\n");		
		}
	} else {	// rwSTREAMMEMORY
		l = stream->memory.size-stream->memory.position;
		if (l < length) 
			debug ("RwStreamRead: ERROR: premature end of data\n");			
		else 
			l = length;
		memcpy (buffer, stream->memory.block+stream->memory.position, l);
		stream->memory.position += l;
	}
	return l;
}

RwStream * RwStreamSkip (RwStream * stream, DWORD offset) {
	if (stream->type == rwSTREAMFILENAME) {
		if (fseek ((FILE *)stream->file.file, offset, 1)) {
			debug ("RwStreamSkip: seek error\n");
			return NULL;
		}
	} else {
		if (stream->memory.position+(int)offset > stream->memory.size) {
			debug ("RwStreamSkip: premature end of data\n");
			return NULL;
		}
		stream->memory.position += offset;
	}
	return stream;
}

// NOTE: для простоты, здесь пока поддерживаются только rwSTREAMMEMORY/rwSTREAMREAD 
RwStream * _rwStreamInitialize (RwStream * stream, bool rwOwned, RwStreamType type, RwStreamAccessType accessType, void * data) {
	// debug ("RwStreamInitialize: %d, %d\n", type, accessType);
	if (type != rwSTREAMMEMORY || accessType != rwSTREAMREAD) {
		debug ("RwStreamInitialize: ERROR: Unsupported type/accessType combination\n");
		return NULL;
	}

	stream->type = type;
	stream->accessType = accessType;
	stream->owned = rwOwned;

	stream->memory.block = ((RwMemory *)data)->start;
	stream->memory.size = ((RwMemory *)data)->length;
	stream->memory.position = 0;
	return stream;
}

// TODO: inline it (используется только в RwStreamReadChunkHeaderInfo)
static bool chunkIsComplex (RwChunkHeaderInfo * chunk) {
	switch (chunk->type) {
	case 5:	// CAMERA
	case 6:	// TEXTURE
	case 7:	// MATERIAL
	case 8:	// MATLIST
	case 9:	// ATOMICSECT
	case 10:	// PLANESECT
	case 11:	// WORLD
	case 14:	// FRAMELIST
	case 15:	// GEOMETRY
	case 16:	// CLUMP
	case 18:	// LIGHT
	case 20:	// ATOMIC
	case 26:	// IMAGE
		return true;
	}
	return false;
}

struct chunkHeaderInfo {
	DWORD	type;
	DWORD	length;
	DWORD	ver;
};
bool _rwStreamReadChunkHeader (RwStream * stream, DWORD * type, DWORD * length, DWORD * version, DWORD * build) {
	chunkHeaderInfo		header;
	if (RwStreamRead (stream, &header, sizeof (chunkHeaderInfo)) != sizeof (chunkHeaderInfo))  {
		debug ("_rwStreamReadChunkHeaderInfo: read error\n");
		return false;
	}
	DWORD _build, _version;
	if (header.ver & 0xFFFF0000) {
		_version = ((header.ver >> 14) & 0x3FF00)+0x30000;
		_build = (header.ver >> 16)&0x3F;
		_version |= _build;
		_build &= 0xFFFF;
	} else {
		_build = 0;
		_version = header.ver;
	}

	if (header.type == 0x13) 
		debug ("DEBUG: UNICODE string found\n");


	if (type)
		*type = header.type;
	if (length)
		*length = header.length;
	if (version)
		*version = _version;
	if (build)
		*build = _build;
	return true;
}

// RwStreamReadReal, RwStreamReadInt32 (отличаются только типом параметра. Для простоты берем его как void *)
RwStream * _rwStreamReadData (RwStream * stream, void * data, DWORD bytes) {
	DWORD l = RwStreamRead (stream, data, bytes);
	if (l != bytes) {
		debug ("rwStreamReadData: read error\n");
		return NULL;
	}
	return stream;
}

RwStream * RwStreamReadChunkHeaderInfo (RwStream * stream, RwChunkHeaderInfo * info) {
	if (!_rwStreamReadChunkHeader (stream, &info->type, &info->length, &info->version, &info->buildNum))
		return NULL;	
	info->isComplex = chunkIsComplex (info);
	return stream;
}

bool RwStreamFindChunk (RwStream * stream, DWORD type, DWORD * outLength, DWORD * outVersion) {
	DWORD _type, _length, _version, _build;
	while (_rwStreamReadChunkHeader (stream, &_type, &_length, &_version, &_build)) {
		if (_type == type) {
			if (_version < 0x34000 || _version > 0x36003) {
				debug ("RwStreamFindChunk: ERROR: unsupported version %x (type=%x)\n", _version, _type);
				return false;
			}
			if (outLength)
				*outLength = _length;
			if (outVersion)
				*outVersion = _version;
			return true;
		}
		if (!RwStreamSkip (stream, _length)) 
			break;
	}
	return false;
}

int _rwStringStreamGetSize (char * str) {	
	return (strlen (str ? str : "")+4)&0xFFFFFFFC;
}

char * _rwStringStreamFindAndRead(char *string, RwStream * stream) {
	DWORD length, version;
	bool allocated = false;
	if (!RwStreamFindChunk (stream, 2, &length, &version))	// rwID_STRING	// TODO: это не совсем правильно. В оригинале, здесь проверялось на rwID_UNICODESTRING
		return NULL;
	if (!string) {
		string =  (char *)(*engine)->mem.rwmalloc (length, 0);	// TODO: заменить на нормальный malloc/new
		allocated = true;
		if (!string) {
			debug ("_rwStringStreamFindAndRead: ERROR: out of memory\n");
			return NULL;
		}
	}
	if (RwStreamRead (stream, string, length) != length) {
		if (allocated)
			(*engine)->mem.rwfree (string);	// TODO: заменить на нормальный new/delete
		return NULL;
	}
	return string; 
}
