/*****************************************************************************
*																			 *
*  FID.CPP																	 *
*																			 *
*  Copyright (C) Microsoft Corporation 1989-1995							 *
*  All Rights reserved. 													 *
*																			 *
******************************************************************************
*																			 *
*  Module Intent															 *
*																			 *
*  Low level file access layer, Windows version.							 *
*																			 *
*
*****************************************************************************/

extern "C" {	// Assume C declarations for C++

#include "help.h"

#include <dos.h>		// for FP_OFF macros and file attribute constants
#include <io.h> 		// for tell() and eof()

#ifdef PRINTOUTPUT
#include <wprintf.h>
#endif
}

#include "inc\whclass.h"

class CTmpFile
{
public:
	CTmpFile(void);
	~CTmpFile(void);

	int STDCALL seek(int lPos, int wOrg);
	int STDCALL tell(void) { return FilePtr; };
	int STDCALL write(void* qv, int lcb);
	int STDCALL read(void* qv, int lcb);
	RC	STDCALL copytofile(HFILE hfDst, DWORD lcb);
	RC	STDCALL copyfromfile(HFILE hfDst, DWORD lcb);
	RC	STDCALL SetSize(DWORD lcb);

	HFILE hf;		//	!= HFILE_ERROR when a real file has been created
	PSTR  pszFileName; // temporary filename if one is created

protected:
	PBYTE pmem;

	int  cbAlloc;	// current memory allocated for temporary file
	int  cbFile;	// current size of the file
	int  FilePtr;	// current file pointer

};

/***************************************************************************\
*
*								Defines
*
\***************************************************************************/

#define MAX_RW	  ((WORD) 0xFFFE)
#define lcbSizeSeg	((DWORD) 0x10000)

/* DOS int 21 AX error codes */

#define wHunkyDory			  0x00
#define wInvalidFunctionCode  0x01
#define wFileNotFound		  0x02
#define wPathNotFound		  0x03
#define wTooManyOpenFiles	  0x04
#define wAccessDenied		  0x05
#define wInvalidHandle		  0x06
#define wInvalidAccessCode	  0x0c


extern "C" {
RC	rcIOError;

static RC STDCALL RcMapDOSErrorW(int);
}


/***************************************************************************\
*
* Function: 	RcMakeTempFile( qrwfo )
*
* Purpose:		Open a temp file with a unique name and stash the fid
*				and fm in the qrwfo.
*
* Method:		The system clock is used to generate a temporary name.
*				WARNING: this will break if you do this more than once
*				in a second
*
* ASSUMES
*
*	args IN:	qrwfo - spec open file that needs a temp file
*
* PROMISES
*
*	returns:	rcSuccess or rcFailure
*
*	args OUT:	qrwfo ->fid, qrwfo->fdT get set.
*
\***************************************************************************/

extern "C" RC STDCALL RcMakeTempFile(QRWFO qrwfo)
{
	qrwfo->fm = NULL;
	qrwfo->fidT = (FID) new CTmpFile;
	return rcSuccess;
}

/***************************************************************************

	FUNCTION:	FidCreateFm

	PURPOSE:	Creates the named file, truncating it if it already exists

	PARAMETERS:
		fm

	RETURNS:

	COMMENTS:

	MODIFICATION DATES:
		20-Jul-1994 [ralphw]

***************************************************************************/

extern "C" FID STDCALL FidCreateFm(FM fm)
{
	HFILE hf;
	UINT UErrCode;

	if (!fm) {
		rcIOError = rcBadArg;
		return HFILE_ERROR;
	}

	UErrCode = SetErrorMode(0); // latch current code, or with codes to suppress
								// NT file system popups warning about write
								// protect on floppy.
	SetErrorMode(UErrCode | SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS );

	hf = _lcreat(PszFromGh(fm), 0);
	SetErrorMode(UErrCode); // restore code

	if (hf != HFILE_ERROR) {
		if (_lclose(hf) == 0)
			hf = _lopen(PszFromGh(fm), OF_READWRITE | OF_SHARE_DENY_WRITE);
		else
			hf = HFILE_ERROR;
	}

	if (hf == HFILE_ERROR)
		rcIOError = RcMapDOSErrorW(GetLastError());
	else
		rcIOError = rcSuccess;

	return hf;
}


/***************************************************************************\
*
* Function: 	FidOpenFm()
*
* Purpose:		Open a file in binary mode.
*
* ASSUMES
*
*	args IN:	fm
*				wOpenMode - read/write/share modes
*							Undefined if wRead and wWrite both unset.
*
* PROMISES
*
*	returns:	HFILE_ERROR on failure, else a valid FID.
*
\***************************************************************************/

extern "C" FID STDCALL FidOpenFm(FM fm, int mode)
{
	FID fid;

	if (!fm) {
		rcIOError = rcBadArg;
		return HFILE_ERROR;
	}

	if ((fid = _lopen(PszFromGh(fm), mode)) == HFILE_ERROR)
		rcIOError = RcMapDOSErrorW(GetLastError());
	else
		rcIOError = rcSuccess;

	return fid;
}

/***************************************************************************\
*
* Function: 	LcbReadFid()
*
* Purpose:		Read data from a file.
*
* ASSUMES
*
*	args IN:	fid - valid FID of an open file
*				lcb - count of bytes to read (must be less than 2147483648)
*
* PROMISES
*
*	returns:	count of bytes actually read or -1 on error
*
*	args OUT:	qv	- pointer to user's buffer assumed huge enough for data
*
*	globals OUT: rcIOError
*
\***************************************************************************/

extern "C" LONG STDCALL LcbReadFid(FID fid, LPVOID qv, LONG lcb)
{
	if (fid > 0xffff)
		return (LONG) ((CTmpFile*) fid)->read(qv, lcb);

	LONG lcbTotalRead = _lread(fid, qv, lcb);

	if (lcbTotalRead == HFILE_ERROR)
		rcIOError = RcMapDOSErrorW(GetLastError());
	else
		rcIOError = rcSuccess;
	return lcbTotalRead;
}

/***************************************************************************\
*
* Function: 	LcbWriteFid()
*
* Purpose:		Write data to a file.
*
* ASSUMES
*
*	args IN:	fid - valid fid of an open file
*				qv	- pointer to user's buffer
*				lcb - count of bytes to write (must be less than 2147483648)
*
* PROMISES
*
*	returns:	count of bytes actually written or -1 on error
*
*	globals OUT: rcIOError
*
\***************************************************************************/

extern "C" LONG STDCALL LcbWriteFid(FID fid, LPVOID qv, LONG lcb)
{
	LONG lcbTotalWrote;

	if (fid > 0xffff)
		return (LONG) ((CTmpFile*) fid)->write(qv, lcb);

	if (lcb == 0L) {
		rcIOError = rcSuccess;
		return 0L;
	}

	lcbTotalWrote = _lwrite(fid, (LPCSTR) qv, lcb);

	if (lcbTotalWrote == lcb)
		rcIOError = rcSuccess;
	else if (lcbTotalWrote == HFILE_ERROR)
		rcIOError = RcMapDOSErrorW(GetLastError());
	else
		rcIOError = rcDiskFull;

	return lcbTotalWrote;
}

/***************************************************************************\
*
* Function: 	RcCloseFid()
*
* Purpose:		Close a file.
*
* Method:
*
* ASSUMES
*
*	args IN:	fid - a valid fid of an open file
*
* PROMISES
*
*	returns:	rcSuccess or something else
*
\***************************************************************************/

extern "C" RC STDCALL RcCloseFid(FID fid)
{
	if (fid > 0xffff) {
		delete ((CTmpFile*) fid);
		return rcSuccess;
	}

	if (_lclose(fid) == HFILE_ERROR)
		rcIOError = RcMapDOSErrorW(GetLastError());
	else
		rcIOError = rcSuccess;

	return rcIOError;
}

/***************************************************************************\
*
* Function: 	LTellFid()
*
* Purpose:		Return current file position in an open file.
*
* ASSUMES
*
*	args IN:	fid - valid fid of an open file
*
* PROMISES
*
*	returns:	offset from beginning of file in bytes; -1L on error.
*
\***************************************************************************/

extern "C" LONG STDCALL LTellFid(FID fid)
{
	LONG l;

	if (fid > 0xffff)
		return (LONG) ((CTmpFile*) fid)->tell();

	if ((l = _llseek(fid, 0, 1)) == HFILE_ERROR)
		rcIOError = RcMapDOSErrorW(GetLastError());
	else
		rcIOError = rcSuccess;

	return l;
}

/***************************************************************************\
*
* Function: 	LSeekFid()
*
* Purpose:		Move file pointer to a specified location.	It is an error
*				to seek before beginning of file, but not to seek past end
*				of file.
*
* ASSUMES
*
*	args IN:	fid   - valid fid of an open file
*				lPos  - offset from origin
*				wOrg  - one of: SEEK_SET: beginning of file
*								SEEK_CUR: current file pos
*								SEEK_END: end of file
*
* PROMISES
*
*	returns:	offset in bytes from beginning of file or -1L on error
*
\***************************************************************************/

extern "C" LONG STDCALL LSeekFid(FID fid, LONG lPos, DWORD wOrg)
{
	if (fid > 0xffff)
		return (LONG) ((CTmpFile*) fid)->seek(lPos, wOrg);

	LONG l = _llseek(fid, lPos, wOrg);

	if (l == -1L)
		rcIOError = RcMapDOSErrorW(GetLastError());
	else
		rcIOError = rcSuccess;

	return l;
}

extern "C" RC STDCALL RcChSizeFid(HFILE fid, LONG lcb)
{
	if (fid > 0xffff)
		return (LONG) ((CTmpFile*) fid)->SetSize(lcb);

	ASSERT(fid < 0xffff);
	DWORD res = SetFilePointer((HANDLE) fid, lcb, 0, FILE_BEGIN);
	if (res >= 0) {
		if (SetEndOfFile((HANDLE) fid))

			// REVIEW: necessary to set rcIOError?

			return (rcIOError = rcSuccess);
	}
	else {
		DWORD err = GetLastError();
		rcIOError = rcInvalid;
		return rcIOError;
	}
	return rcSuccess;
}

extern "C" RC STDCALL RcUnlinkFm(FM fm)
{
	if (!fm)
		return rcSuccess;

	if (!DeleteFile((PSTR) fm))
		rcIOError = RcMapDOSErrorW(GetLastError());
	else
		rcIOError = rcSuccess;
	return rcIOError;
}

static RC STDCALL RcMapDOSErrorW(int wError)
{
	RC rc;
	switch (wError) {
		case wHunkyDory:
			rc = rcSuccess;
			break;

		case ERROR_INVALID_FUNCTION:
		case ERROR_INVALID_HANDLE:
			rc = rcBadArg;
			break;

		case ERROR_FILE_NOT_FOUND:
		case ERROR_PATH_NOT_FOUND:
			rc = rcNoExists;
			break;

		case ERROR_TOO_MANY_OPEN_FILES:
			rc = rcNoFileHandles;
			break;

		case ERROR_ACCESS_DENIED:
			rc = rcNoPermission;
			break;

		default:
			rc = rcFailure;
			break;
	}

	return rc;
}

const int CB_COPY_BUFFER = 4096 * 8;				  // 64K

// If memory exceeds LARGEST_ALLOC then we switch to a real file

static int LARGEST_ALLOC = (1024 * 1024 * 8) - 4096L; // 8 megs

CTmpFile::CTmpFile(void)
{
	cbAlloc = 4096;
	pmem = (PBYTE) VirtualAlloc(NULL, LARGEST_ALLOC, MEM_RESERVE,
		PAGE_READWRITE);
	if (!pmem)
		OOM();
	if (!VirtualAlloc(pmem, cbAlloc, MEM_COMMIT, PAGE_READWRITE))
		OOM();

	cbFile = 0;
	FilePtr = 0;
	hf = HFILE_ERROR;
	pszFileName = NULL;
}

CTmpFile::~CTmpFile(void)
{
	if (pszFileName) {
		_lclose(hf);
		remove(pszFileName);
		lcFree(pszFileName);
	}
	if (pmem) {
		VirtualFree(pmem, LARGEST_ALLOC, MEM_DECOMMIT);
		VirtualFree(pmem, 0, MEM_RELEASE);
	}
}

int STDCALL CTmpFile::seek(int lPos, int Origin)
{
	if (pszFileName)
		return _llseek(hf, lPos, Origin);

	// REVIEW (niklasb): Seeking past the end of a real file does
	//	 not grow the file until the next write operation. We should
	//	 probably do the same here, especially since the code in this
	//	 function chokes if we grow past LARGEST_ALLOC.
	//

	switch (Origin) {
		case SEEK_SET:
			FilePtr = lPos;
			break;

		case SEEK_CUR:
			FilePtr += lPos;
			break;

		case SEEK_END:
			ASSERT(cbFile + lPos < cbAlloc);
			FilePtr = cbFile + lPos;
			break;

		default:
			ASSERT(TRUE);
			break;
	}
	return FilePtr;
}

int STDCALL CTmpFile::write(void* qv, int lcb)
{
	ASSERT(qv);

	// If this is a real file, just write the data; we no
	// longer track cbFile in this case.
	if (pszFileName)
		return (int) _lwrite(hf, (LPCSTR) qv, lcb);

	// Grow the pseudo-file if necessary.
	if (FilePtr + lcb > cbAlloc) {

		// Calculate the new committed size (always 4K aligned).
		int cbNew = (FilePtr + lcb) / 4096 * 4096 + 4096;

		// If > reserved size, create a real file.
		if (cbNew > LARGEST_ALLOC) {
			DBWIN("Switching CTmpFile to a real file.");

			pszFileName = (PSTR) FmNewTemp();
			if (!pszFileName)
				OOM();

			hf = _lcreat(pszFileName, 0);
			if (hf == HFILE_ERROR)
				return HFILE_ERROR;

			// Write all of our current data to the temp file.
			_lwrite(hf, (LPCSTR) pmem, cbFile);

			// Free all of the current memory
			VirtualFree(pmem, cbAlloc, MEM_DECOMMIT);
			VirtualFree(pmem, 0, MEM_RELEASE);
			pmem = NULL;

			// Add the new data
			_llseek(hf, FilePtr, SEEK_SET);
			return (int) _lwrite(hf, (LPCSTR) qv, lcb);
		}

		// We're still a pseudo-file: commit more memory.
		ASSERT(cbNew <= LARGEST_ALLOC);
		if (!VirtualAlloc(pmem + cbAlloc, cbNew - cbAlloc,
				MEM_COMMIT, PAGE_READWRITE))
			OOM();

		cbAlloc = cbNew;
	}

	// We're still a pseudo-file; and we either already
	// reallocated or don't need to.
	ASSERT(pmem && FilePtr + lcb <= cbAlloc);

	// Copy the new data to the pseudo-file.
	memcpy(pmem + FilePtr, qv, lcb);
	FilePtr += lcb;
	if (FilePtr > cbFile)
		cbFile = FilePtr;
	return lcb;
}

int STDCALL CTmpFile::read(void* qv, int lcb)
{
	if (pszFileName)
		return _lread(hf, qv, lcb);
	else {
		if (lcb > cbFile)
			lcb = cbFile;
		memcpy(qv, pmem + FilePtr, lcb);
		FilePtr += lcb;
		return lcb;
	}
}

extern "C" RC STDCALL RcCopyToCTmpFile(HFILE hfDst, HFILE hfSrc, LONG lcb)
{
	return ((CTmpFile*) hfSrc)->copytofile(hfDst, lcb);
}

extern "C" RC STDCALL RcCopyFromCTmpFile(HFILE hfDst, HFILE hfSrc, LONG lcb)
{
	return ((CTmpFile*) hfDst)->copyfromfile(hfSrc, lcb);
}

/***************************************************************************

	FUNCTION:	CTmpFile::copytofile

	PURPOSE:	Copy from our temporary buffer into a real file

	PARAMETERS:
		hfDst
		lcb

	RETURNS:

	COMMENTS:

	MODIFICATION DATES:
		06-Jan-1994 [ralphw]

***************************************************************************/

RC STDCALL CTmpFile::copytofile(HFILE hfDst, DWORD lcb)
{
	int cbRead = CB_COPY_BUFFER;;

	if (hf == HFILE_ERROR) {

		// Not a real file, so just copy our entire buffer

		ASSERT(FilePtr == 0);
		ASSERT((int) lcb == cbFile);

		if (_lwrite(hfDst, (LPCSTR) pmem, cbFile) != (UINT) cbFile)
			cbRead = HFILE_ERROR;
	}
	else {
		CLMem buf(CB_COPY_BUFFER);

#ifdef _DEBUG
		if (lcb > CB_COPY_BUFFER)
			DBWIN("CB_COPY_BUFFER wasn't large enough in copytofile.");
#endif

		while (lcb > CB_COPY_BUFFER) {
			if ((cbRead = read(buf.pBuf, CB_COPY_BUFFER)) == HFILE_ERROR)
				break;

			if (_lwrite(hfDst, (LPCSTR) buf.pBuf, cbRead) != (UINT) cbRead) {
				cbRead = HFILE_ERROR;
				break;
			}

			lcb -= cbRead;
		}

		if (cbRead != HFILE_ERROR && lcb &&
			  (cbRead = read(buf.pBuf, lcb)) != HFILE_ERROR) {
		  if (_lwrite(hfDst, (LPCSTR) buf.pBuf, cbRead) != (UINT) cbRead)
			  cbRead = HFILE_ERROR;
		}
	}

	if (cbRead == HFILE_ERROR) {
		return RcGetIOError();
	}

	return rcSuccess;
}

RC STDCALL CTmpFile::SetSize(DWORD lcb)
{
	if ((DWORD) cbFile > lcb)
		cbFile = lcb;
	else if ((DWORD) cbFile < lcb) {
		// Calculate the new committed size (always 4K aligned).
		int cbNew = lcb / 4096 * 4096 + 4096;

		ASSERT(cbNew <= LARGEST_ALLOC);
		if (!VirtualAlloc(pmem + cbAlloc, cbNew - cbAlloc,
				MEM_COMMIT, PAGE_READWRITE))
			OOM();

		cbAlloc = cbNew;
	}
	return rcSuccess;
}

/***************************************************************************

	FUNCTION:	CTmpFile::copyfromfile

	PURPOSE:	Copy lcb bytes from a file into our temporary file buffer

	PARAMETERS:
		hfSrc
		lcb

	RETURNS:

	COMMENTS:
		REVIEW: now that we can handle up to 8 megs, we should read
		from the file directly into our memory rather then going through
		a temporary buffer.

	MODIFICATION DATES:
		06-Jan-1994 [ralphw]

***************************************************************************/

RC STDCALL CTmpFile::copyfromfile(HFILE hfSrc, DWORD lcb)
{
	CLMem buf(CB_COPY_BUFFER);

	int cbRead = CB_COPY_BUFFER;

	while (lcb > CB_COPY_BUFFER) {
		if ((cbRead = _lread(hfSrc, buf.pBuf, CB_COPY_BUFFER)) == HFILE_ERROR)
			break;

		if (write(buf.pBuf, cbRead) != cbRead) {
			cbRead = HFILE_ERROR;
			break;
		}

		lcb -= cbRead;
	}

	if (cbRead != HFILE_ERROR && lcb &&
		  (cbRead = _lread(hfSrc, buf.pBuf, lcb)) != HFILE_ERROR) {
	  if (write(buf.pBuf, cbRead) != cbRead)
		  cbRead = HFILE_ERROR;
	}

	if (cbRead == HFILE_ERROR) {
		return RcGetIOError();
	}
	lcHeapCheck();

	return rcSuccess;
}

#ifdef _THREAD

/***************************************************************************

	FUNCTION:	ActivateThread

	PURPOSE:	Activate the worker thread, creating as necessary, and
				giving it the command to act upon

	PARAMETERS:
		cmd
		pParam
		priority

	RETURNS:	FALSE if the thread is busy, or could not be created

	COMMENTS:

	MODIFICATION DATES:
		29-May-1995 [ralphw]

***************************************************************************/

static HANDLE hsemThread;
static HANDLE hthread;
static BOOL fThreadRunning;
static int ThreadPriority;
static int ThreadCommand;
static void* pThreadParam;
static DWORD thrdid;

static DWORD WINAPI WorkerThread(LPVOID pParam);

extern "C" BOOL STDCALL ActivateThread(int cmd, void* pParam, int priority)
{
	BOOL _fDualCPU;

	if (!hthread) {
		if (!hsemThread)
			hsemThread = CreateSemaphore(NULL, 0, 1, NULL);
		hthread = CreateThread(NULL, 0, &WorkerThread, NULL, 0,
			&thrdid);
	}

	if (fThreadRunning)
		return FALSE; // means the thread is busy

	// For multiple CPU's, we can use a normal priority for threads

	HKEY hkey;
	if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
			"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\1", 0,
			KEY_READ, &hkey) == ERROR_SUCCESS) {
		_fDualCPU = TRUE;
		RegCloseKey(hkey);
	}
	else
		_fDualCPU = FALSE;

	if (_fDualCPU && (priority == THREAD_PRIORITY_IDLE ||
					  priority == THREAD_PRIORITY_LOWEST ||
					  priority == THREAD_PRIORITY_BELOW_NORMAL))
		priority == THREAD_PRIORITY_NORMAL;

	if (ThreadPriority != priority)
		SetThreadPriority(hthread, ThreadPriority = priority);

	ThreadCommand = cmd;
	pThreadParam = pParam;

	fThreadRunning = TRUE;
	ReleaseSemaphore(hsemThread, 1, NULL);
	return TRUE;
}

/***************************************************************************

	FUNCTION:	WaitForThread

	PURPOSE:	Kick the background thread up to highest priority, and
				wait for it to finish.

	PARAMETERS:
		void

	RETURNS:

	COMMENTS:

	MODIFICATION DATES:
		29-May-1995 [ralphw]

***************************************************************************/

extern "C" void STDCALL WaitForThread(void)
{
	if (!fThreadRunning)
		return;

	SetThreadPriority(hthread, THREAD_PRIORITY_HIGHEST);

	{
		CWaitCursor waitcur;
		while (fThreadRunning)
			Sleep(1000);
	}

	SetThreadPriority(hthread, ThreadPriority);
}

static DWORD WINAPI WorkerThread(LPVOID pParam)
{
	for (;;) {
		if (WaitForSingleObject(hsemThread, INFINITE) != WAIT_OBJECT_0)
			return (UINT) -1;

		switch (ThreadCommand) {

			default:
				DBWIN("Invalid thread command");
				break;
		}

		fThreadRunning = FALSE;
	}
}

#endif // _THREAD