#include "stdafx.h"
#include "ogStreaming.h"

CStream	streams[STREAM_COUNT];

DWORD	requestQueue[32];	// кольцевой буфер номеров потоков, которым отправлен запрос на чтение
DWORD	requestQueueHead;	// _8E3FF4	- первый занятый элемент очереди запросов
DWORD	requestQueueTail;	// _8E3FF4	- первый свободный элемент очереди запросов
DWORD	requestQueueSize;

HANDLE	streamSemaphore;
HANDLE	streamThread;
DWORD	streamingThreadId;
DWORD	streamCreateFlags;
bool	overlappedIO;
bool	streamingInitialized = false;

HANDLE	streamHandles[STREAM_COUNT];	// >> 8E4010
char	streamNames[STREAM_COUNT][64];

DWORD	_8E4898;

DWORD getFirstStreamSize () {	//	>> 406360 (1)	TODO: inject it? (и вообще, оно нужно?)
	return GetFileSize (streamHandles[0], NULL);
}

DWORD getLastStreamIndex () {	// 406450
	return _8E4898;
}

static DWORD __stdcall streamReaderThread (void * param) {
	while (1) {
		WaitForSingleObject (streamSemaphore, INFINITE);
		if (requestQueueHead == requestQueueTail) {	
			debug ("Stream reader: queue is empty\n");
			continue;	// nothing to do: queue is empty
		}
		CStream * s = streams+requestQueue[requestQueueHead];
		s->bInUse = true;
		DWORD bytes;
		if (!s->dwStatus) {
			if (overlappedIO) {
				s->overlapped.Offset = s->nSectorOffset << 11;
				// dwStatus здесь гарантированно равен 0, так что обнулять дополнительно его не надо
				if (!ReadFile (s->hFile, s->lpBuffer, s->nSectorsToRead << 11, NULL, &s->overlapped)) {
					DWORD result = GetLastError ();
					if (result != ERROR_IO_PENDING) {
						debug ("Stream reader: error reading stream (req: %d): %d\n", requestQueueHead, result);
						s->dwStatus = 0xFE;
					} else {	// wait for overlapped IO
						if (!GetOverlappedResult (s->hFile, &s->overlapped, &bytes, true)) {
							debug ("Stream reader: GetOverlappedResult returns error: %d\n", GetLastError ());
							s->dwStatus = 0xFE;
						} 
					}
				}
			} else {
				SetFilePointer (s->hFile, s->nSectorOffset<<11, NULL, FILE_BEGIN);
				s->dwStatus = ReadFile (s->hFile, s->lpBuffer, s->nSectorsToRead<<11, &bytes, NULL) ? 0 : 0xFE;
			}
		}
		
		requestQueueHead++;
		requestQueueHead &= 31;
		s->nSectorsToRead = 0;
		if (s->bLocked) 
			ReleaseSemaphore (s->hSemaphore, 1, NULL);
		s->bInUse = false;
	}
}

DWORD getStreamStatus (int streamNum) { // >> 4063E0
	CStream * s = streams+streamNum;
	if (streamingInitialized) {
		if (s->bInUse)
			return 0xFF;
		if (s->nSectorsToRead)
			return 0xFA;
		DWORD status = s->dwStatus;
		s->dwStatus = 0;
		return status;
	} else {
		if (!overlappedIO)
			return 0;
		return WaitForSingleObjectEx (s->hFile, 0, true) ? 0xFF : 0;
	}
}

// allocate synchronization objects and start streaming thread
void startStreaming () {	// >> 4068F0
	for (int i = 0; i < STREAM_COUNT; i++) 
		if (!(streams[i].hSemaphore = CreateSemaphore (NULL, 0, 2, NULL))) {
			debug ("CDDVD_STREAM: Failed to create sync semaphore\n");
			return;
		}

	requestQueueHead = 0;
	requestQueueTail = 0;
	requestQueueSize = STREAM_COUNT;
	streamSemaphore = CreateSemaphore(NULL, 0, 5, NULL);
	if (!streamSemaphore) {
		debug ("CDDVD_STREAM: Failed to create stream semaphore\n");
		return ;
	}
		
	streamThread = CreateThread (NULL, 65536, streamReaderThread, NULL, CREATE_SUSPENDED, &streamingThreadId);
	if (!streamThread) {
		debug ("CDDVD_STREAM: Failed to create streaming thread\n");
		return ;
	}

	SetThreadPriority (streamThread, GetThreadPriority (GetCurrentThread ()));
	ResumeThread (streamThread);
}

void initializeStreaming () {	// 406B70
	debug ("Initializing streaming.\n");
	for (int i = 0; i < STREAM_COUNT; i++) {
		streamHandles[i] = 0;
		streamNames[i][0] = '\0';
	}
	memset (streams, 0, sizeof (CStream)*STREAM_COUNT);
	// NOTE: дальше проверялся размер сектора. Выкинуто за ненадобнастью
	//	Если размер сектора больше 2048 байт, выставлялся FILE_FLAG_NO_BUFFERING

	streamCreateFlags = FILE_FLAG_OVERLAPPED;
	overlappedIO = true;
	streamingInitialized = false;
	
	BYTE	buf[2048];
	SetLastError (0);
	openStream ("models/gta3.img", false);
	bool result = readStream (0, buf, 0, 1);
	closeAllStreams ();
	streamingInitialized = true;
	if (!result) {
		debug ("initializeStreaming: overlapped IO failed\n");
		overlappedIO = false;
		streamCreateFlags &= ~FILE_FLAG_OVERLAPPED;
	}
	startStreaming ();
}

void terminateStreaming () {	// >> 406370
	if (!streamingInitialized)
		return;
	
	debug ("Terminating streaming\n");
	CloseHandle (streamSemaphore);
	CloseHandle (streamThread);
	for (int i = 0; i < STREAM_COUNT; i++)
		CloseHandle(streams[i].hSemaphore);

	streamingInitialized = false;
}

DWORD openStream (char * name, bool unused) { // >> 4067B0
	int i;
	for (i = 0; i < STREAM_COUNT; i++)	
		if (!streamHandles[i])
			break;
	if (i >= STREAM_COUNT) {
		debug ("openStream: no free streams\n");
		return 0;
	}

	SetLastError (0);
	streamHandles[i] = CreateFile (name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 
		streamCreateFlags|FILE_FLAG_RANDOM_ACCESS|FILE_ATTRIBUTE_READONLY, NULL);
	if (streamHandles[i] == INVALID_HANDLE_VALUE) {
		debug ("openStream: can't open stream '%s'\n", name);
		return 0;
	}

	strcpy_s (streamNames[i], 64, name);
	debug ("Stream '%s' open, #%d\n", name, i);
	
	return i << 24;
}

bool readStream (int streamNum, BYTE * buf, DWORD streamHandle, DWORD sectorCount) {
	if (sectorCount >= (1<<12) || streamHandle >= (0x20000000))
		return false;
	// debug ("readStream: %d %08x %d\n", streamNum, streamHandle, sectorCount);
	CStream * s = streams+streamNum;
	int n = streamHandle >> 24;
	
	_8E4898 = streamHandle+sectorCount;	// TODO: а это зачем?

	s->hFile = streamHandles[n];	// это должно делаться после проверки на свободность stream, в каждой ветке
	SetLastError (0);
	if (streamingInitialized) {
		if (s->nSectorsToRead || s->bInUse)
			return false;
		s->dwStatus = 0;
		s->nSectorOffset = streamHandle&0xFFFFFF;
		s->nSectorsToRead = sectorCount;
		s->lpBuffer = buf;
		s->bLocked = false;
		debug ("readStream: added req %d (num: %d, handle: %08x, sectors: %d)\n", requestQueueTail, streamNum, streamHandle, sectorCount);
		requestQueue[requestQueueTail++] = streamNum;
		requestQueueTail &= 31;	// TODO: проверить!
		if (!ReleaseSemaphore (streamSemaphore, 1, NULL))
			debug ("Signal sema error %d\n", GetLastError());
		return true;
	}

	DWORD pos = (streamHandle&0xFFFFFF) << 11;
	if (overlappedIO) {
		s->overlapped.Offset = pos;

		if (ReadFile (streamHandles[n], buf, sectorCount << 11, NULL, &s->overlapped) || GetLastError() == ERROR_IO_PENDING)
			return true;
		return false;
	}

	SetFilePointer (streamHandles[n], pos, NULL, FILE_BEGIN);
	return ReadFile (streamHandles[n], buf, sectorCount << 11, NULL, NULL) != 0;
}

DWORD closeStream (int streamNum) {	// 406460	
	CStream * s = streams+streamNum;
	if (streamingInitialized) {
		if (s->nSectorsToRead) {
			s->bLocked = true;
			WaitForSingleObject (s->hSemaphore, INFINITE);
		}
		s->bInUse = false;
		return s->dwStatus;
	}
	DWORD tmp;
	if (overlappedIO && s->hFile) 
		return GetOverlappedResult (s->hFile, &s->overlapped, &tmp, true) ? 0 : 0xFE;
	
	return 0;
}

void closeAllStreams () {	// >> 406690
	for (int i = 0; i < STREAM_COUNT; i++) {
		closeStream (i);
		if (streamHandles[i]) {
			CloseHandle (streamHandles[i]);
			streamHandles[i] = NULL;
		}
		streamNames[i][0] = '\0';
	}
}

//=============================================================================
// .IMG handling

CImgDescriptor * imgs = (CImgDescriptor *)0x8E48D8;

int openImgFile (TCHAR * filename, bool notPlayerFile) {
	// find free record
	int i;
	for (i = 0; i < IMG_COUNT; i++)
		if (imgs[i].name[0] == '\0')
			break;
	if (i >= IMG_COUNT)
		return 0;	// TODO: return -1; 

	imgs[i].notPlayerFile = notPlayerFile;
	imgs[i].streamHandle = openStream (filename, false);
	// TODO: check error
	strcpy_s (imgs[i].name, 40, filename); 
	return i;
}

extern void injectFunction (DWORD, DWORD);
void streamingInject () {
/*	
	injectFunction (0x406360, (DWORD)getFirstStreamSize);

	injectFunction (0x4067B0, (DWORD)openStream);
	injectFunction (0x406460, (DWORD)closeStream);
	injectFunction (0x406690, (DWORD)closeAllStreams);
	injectFunction (0x406A20, (DWORD)readStream);
	injectFunction (0x4063E0, (DWORD)getStreamStatus);
	injectFunction (0x406450, (DWORD)getLastStreamIndex);

	injectFunction (0x406B70, (DWORD)initializeStreaming);
	injectFunction (0x406370, (DWORD)terminateStreaming);	

	// .IMG handling
	injectFunction (0x407610, (DWORD)openImgFile);
*/
}