// file.cpp

// file routine overrides

// 08/20/05 (mv)

#include <windows.h>
#include <stdio.h>
#include "libct.h"

/* FILE, as defined in stdio.h
struct _iobuf {
        char *_ptr;
        int   _cnt;
        char *_base;				Used to store HANDLE
        int   _flag;
        int   _file;
        int   _charbuf;
        int   _bufsiz;
        char *_tmpfname;
        };
typedef struct _iobuf FILE;
*/

//_flag values (not the ones used by the normal CRT
#define _FILE_TEXT		0x0001
#define _FILE_EOF		0x0002
#define _FILE_ERROR		0x0004

struct _FILE : public FILE
{
	void set_handle(HANDLE h) {_base = (char*)h;};
	HANDLE get_handle() const {return (HANDLE)_base;};
};

// used directly by the stdin, stdout, and stderr macros
_FILE __iob[3];
FILE *__iob_func() {return (FILE*)__iob;}

void _init_file()
{
	// STDIN
	__iob[0].set_handle(GetStdHandle(STD_INPUT_HANDLE));
	__iob[0]._flag = _FILE_TEXT;

	// STDOUT
	__iob[1].set_handle(GetStdHandle(STD_OUTPUT_HANDLE));
	__iob[1]._flag = _FILE_TEXT;

	// STDERR
	__iob[2].set_handle(GetStdHandle(STD_ERROR_HANDLE));
	__iob[2]._flag = _FILE_TEXT;
}


BEGIN_EXTERN_C

/*int _fileno(FILE *fp)
{
	return (int)fp;			// FIXME:  This doesn't work under Win64
}

HANDLE _get_osfhandle(int i)
{
	return (HANDLE)i;		// FIXME:  This doesn't work under Win64
}*/

FILE *fopen(const char *path, const char *attrs)
{
	DWORD access, disp;
	if (strchr(attrs, 'w'))
	{
		access = GENERIC_WRITE;
		disp = CREATE_ALWAYS;
	}
	else
	{
		access = GENERIC_READ;
		disp = OPEN_EXISTING;
	}

	HANDLE hFile = CreateFileA(path, access, 0, 0, disp, 0, 0);
	if (hFile == INVALID_HANDLE_VALUE)
		return 0;

	_FILE *file = new _FILE;
	memset(file, 0, sizeof(_FILE));
	file->set_handle(hFile);

	if (strchr(attrs, 't'))
		file->_flag |= _FILE_TEXT;

	return file;
}

FILE *_wfopen(const wchar_t *path, const wchar_t *attrs)
{
	DWORD access, disp;
	if (wcschr(attrs, L'w'))
	{
		access = GENERIC_WRITE;
		disp = CREATE_ALWAYS;
	}
	else
	{
		access = GENERIC_READ;
		disp = OPEN_EXISTING;
	}

	HANDLE hFile = CreateFileW(path, access, 0, 0, disp, 0, 0);
	if (hFile == INVALID_HANDLE_VALUE)
		return 0;

	_FILE *file = new _FILE;
	memset(file, 0, sizeof(_FILE));
	file->set_handle(hFile);

	if (wcschr(attrs, L't'))
		file->_flag |= _FILE_TEXT;

	return file;
}


int fprintf(FILE *fp, const char *s, ...)
{
	va_list args;
	va_start(args, s);

	char bfr[1024];
	int len = wvsprintfA(bfr, s, args);
	va_end(args);

	fwrite(bfr, len+1, sizeof(char), fp);
	return len;
}

int fwprintf(FILE *fp, const wchar_t *s, ...)
{
	va_list args;
	va_start(args, s);

	wchar_t bfr[1024];
	int len = wvsprintfW(bfr, s, args);

	va_end(args);

	char ansibfr[1024];
	WideCharToMultiByte(CP_ACP, 0, bfr, -1, ansibfr, sizeof(ansibfr), 0, 0);

	fwrite(ansibfr, len+1, sizeof(char), fp);
	return len;
}


int fclose(FILE *fp)
{
	CloseHandle(((_FILE*)fp)->get_handle());
	delete fp;
	return 0;
}

int feof(FILE *fp)
{
	return (fp->_flag & _FILE_EOF) ? 1 : 0;
}

int fseek(FILE *str, long offset, int origin)
{
	DWORD meth = FILE_BEGIN;
	if (origin == SEEK_CUR)
		meth = FILE_CURRENT;
	else if (origin == SEEK_END)
		meth = FILE_END;
	SetFilePointer(((_FILE*)str)->get_handle(), offset, 0, meth);
	((_FILE*)str)->_flag &= ~_FILE_EOF;
	return 0;
}

long ftell(FILE *fp)
{
	return SetFilePointer(((_FILE*)fp)->get_handle(), 0, 0, FILE_CURRENT);
}

size_t fread(void *buffer, size_t size, size_t count, FILE *str)
{
	if (size*count == 0)
		return 0;
	if (feof(str))
		return 0;

	HANDLE hFile = ((_FILE*)str)->get_handle();
	int textMode = ((_FILE*)str)->_flag & _FILE_TEXT;

	char *src;
	if (textMode)
		src = (char*)malloc(size*count);
	else
		src = (char*)buffer;

	DWORD br;
	if (!ReadFile(hFile, src, (DWORD)(size*count), &br, 0))
		((_FILE*)str)->_flag |= _FILE_ERROR;
	else if (!br)		// nonzero return value and no bytes read = EOF
		((_FILE*)str)->_flag |= _FILE_EOF;

	if (!br)
		return 0;

	// Text-mode translation is always ANSI
	if (textMode)		// text mode: must translate CR -> LF
	{
		char *dst = (char*)buffer;
		for (DWORD i = 0; i < br; i++)
		{
			if (src[i] != '\r')
			{
				*dst++ = src[i];
				continue;
			}

			// If next char is LF -> convert CR to LF
			if (i+1 < br)
			{
				if (src[i+1] == '\n')
				{
					*dst++ = '\n';
					i++;
				}
				else
					*dst++ = src[i];
			}
			else if (br > 1)
			{
				// This is the hard part: must peek ahead one byte
				DWORD br2 = 0;
				char peekChar = 0;
				ReadFile(hFile, &peekChar, 1, &br2, 0);
				if (!br2)
					*dst++ = src[i];
				else if (peekChar == '\n')
					*dst++ = '\n';
				else
				{
					fseek(str, -1, SEEK_CUR);
					*dst++ = src[i];
				}
			}
			else
				*dst++ = src[i];
		}

		free(src);
	}

	return br/size;
}

size_t fwrite(const void *buffer, size_t size, size_t count, FILE *str)
{
	DWORD bw = 0, bw2 = 0;

	if (size*count == 0)
		return 0;

	HANDLE hFile = ((_FILE*)str)->get_handle();
	int textMode = ((_FILE*)str)->_flag & _FILE_TEXT;

	// Text-mode translation is always ANSI!
	if (textMode)			// text mode -> translate LF -> CRLF
	{
		const char *src = (const char*)buffer;
		size_t startpos = 0, i = 0;
		for (i = 0; i < size*count; i++)
		{
			if (src[i] != '\n')
				continue;
			if (i > 0 && src[i-1] == '\r')		// don't translate CRLF
				continue;

			if (i > startpos)
			{
				WriteFile(hFile, &src[startpos], i-startpos, &bw2, 0);
				bw += bw2;
			}

			const char *crlf = "\r\n";
			WriteFile(hFile, crlf, 2, &bw2, 0);
			bw++;		// one '\n' written

			startpos = i+1;
		}

		if (i > startpos)
		{
			WriteFile(hFile, &src[startpos], i-startpos, &bw2, 0);
			bw += bw2;
		}
	}
	else
		WriteFile(hFile, buffer, (DWORD)(size*count), &bw, 0);
	return bw/size;
}

char *fgets(char *str, int n, FILE *s)
{
	if (feof(s))
		return 0;

	int i;
	for (i = 0; i < n-1; i++)
	{
		if (!fread(&str[i], 1, sizeof(char), s))
			break;
		if (str[i] == '\r')
		{
			i--;
			continue;
		}
		if (str[i] == '\n')
		{
			i++;
			break;
		}
	}

	str[i] = 0;
	return str;
}

wchar_t *fgetws(wchar_t *str, int n, FILE *s)
{
	// Text-mode fgetws converts MBCS->Unicode
	if (((_FILE*)str)->_flag & _FILE_TEXT)
	{
		char *bfr = (char*)malloc(n);
		fgets(bfr, n, s);
		MultiByteToWideChar(CP_ACP, 0, bfr, -1, str, n);
		free(bfr);
		return str;
	}

	// Binary fgetws reads as Unicode

	if (feof(s))
		return 0;

	int i;
	for (i = 0; i < n-1; i++)
	{
		if (!fread(&str[i], 1, sizeof(wchar_t), s))
			break;
		if (str[i] == L'\r')
		{
			i--;
			continue;	// does i++
		}
		if (str[i] == L'\n')
		{
			i++;
			break;
		}
	}

	str[i] = 0;
	return str;
}


int fgetc(FILE *s)
{
	if (s == 0 || feof(s))
		return EOF;

	char c;
	fread(&c, 1, sizeof(char), s);

	return (int)c;
}

wint_t fgetwc(FILE *s)
{
	if (s == 0 || feof(s))
		return (wint_t)EOF;

	// text-mode fgetwc reads and converts MBCS
	if (((_FILE*)s)->_flag & _FILE_TEXT)
	{
		char ch = (char)fgetc(s);
		wint_t wch;
		MultiByteToWideChar(CP_ACP, 0, &ch, 1, (LPWSTR)&wch, 1);
		return wch;
	}

	// binary fgetwc reads unicode

	wint_t c;
	fread(&c, 1, sizeof(wint_t), s);

	return c;
}


END_EXTERN_C