#include "sysconfig.h"
#include "sysdeps.h"
#include "options.h"
#include "memory.h"

#include "fsdb.h"
#include "zfile.h"
#include "win32.h"

#include <windows.h>
#include <sys/timeb.h>
#include <Shobjidl.h>
#include <ShlGuid.h>
#include <Shlwapi.h>

bool my_isfilehidden (const TCHAR *path)
{
	DWORD attr = GetFileAttributes (path);
	if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_HIDDEN))
		return true;
	return false;
}
void my_setfilehidden (const TCHAR *path, bool hidden)
{
	DWORD attr = GetFileAttributes (path);
	if (attr == INVALID_FILE_ATTRIBUTES)
		return;
	DWORD attro = attr;
	attr &= ~FILE_ATTRIBUTE_HIDDEN;
	if (hidden)
		attr |= FILE_ATTRIBUTE_HIDDEN;
	if (attro == attr)
		return;
	SetFileAttributes (path, attr);
}

int my_setcurrentdir (const TCHAR *curdir, TCHAR *oldcur)
{
	int ret = 0;
	if (oldcur)
		ret = GetCurrentDirectory (MAX_DPATH, oldcur);
	if (curdir) {
		const TCHAR *namep;
		TCHAR path[MAX_DPATH];
	
		if (currprefs.win32_filesystem_mangle_reserved_names == false) {
			_tcscpy (path, PATHPREFIX);
			_tcscat (path, curdir);
			namep = path;
		} else {
			namep = curdir;
		}
		ret = SetCurrentDirectory (namep);
	}
	return ret;
}

int my_mkdir (const TCHAR *name)
{
	const TCHAR *namep;
	TCHAR path[MAX_DPATH];
	
	if (currprefs.win32_filesystem_mangle_reserved_names == false) {
		_tcscpy (path, PATHPREFIX);
		_tcscat (path, name);
		namep = path;
	} else {
		namep = name;
	}
	return CreateDirectory (namep, NULL) == 0 ? -1 : 0;
}

static int recycle (const TCHAR *name, bool dontrecycle)
{
	DWORD dirattr = GetFileAttributesSafe (name);
	bool isdir = dirattr != INVALID_FILE_ATTRIBUTES && (dirattr & FILE_ATTRIBUTE_DIRECTORY);
	const TCHAR *namep;
	TCHAR path[MAX_DPATH];
	
	if (currprefs.win32_filesystem_mangle_reserved_names == false) {
		_tcscpy (path, PATHPREFIX);
		_tcscat (path, name);
		namep = path;
	} else {
		namep = name;
	}

	if (dontrecycle || currprefs.win32_norecyclebin || isdir || currprefs.win32_filesystem_mangle_reserved_names == false) {
		if (isdir)
			return RemoveDirectory (namep) ? 0 : -1;
		else
			return DeleteFile (namep) ? 0 : -1;
	} else {
		SHFILEOPSTRUCT fos;
		HANDLE h;
		
		h = CreateFile (namep, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
			OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
		if (h != INVALID_HANDLE_VALUE) {
			LARGE_INTEGER size;
			if (GetFileSizeEx (h, &size)) {
				if (size.QuadPart == 0) {
					CloseHandle (h);
					return DeleteFile (namep) ? 0 : -1;
				}
			}
			CloseHandle (h);
		}

		/* name must be terminated by \0\0 */
		TCHAR *p = xcalloc (TCHAR, _tcslen (namep) + 2);
		int v;

		_tcscpy (p, namep);
		memset (&fos, 0, sizeof (fos));
		fos.wFunc = FO_DELETE;
		fos.pFrom = p;
		fos.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_NORECURSION | FOF_SILENT;
		v = SHFileOperation (&fos);
		xfree (p);
		switch (v)
		{
		case 0xb7: //DE_ERROR_MAX
		case 0x7c: //DE_INVALIDFILES
		case 0x402: // "unknown error"
			v = ERROR_FILE_NOT_FOUND;
			break;
		case 0x75: //DE_OPCANCELLED:
		case 0x10000: //ERRORONDEST:
		case 0x78: //DE_ACCESSDENIEDSRC:
		case 0x74: //DE_ROOTDIR:
			v = ERROR_ACCESS_DENIED;
			break;
		}
		SetLastError (v);
		return v ? -1 : 0;
	}
}

int my_rmdir (const TCHAR *name)
{
	struct my_opendir_s *od;
	int cnt;
	TCHAR tname[MAX_DPATH];

	/* SHFileOperation() ignores FOF_NORECURSION when deleting directories.. */
	od = my_opendir (name);
	if (!od) {
		SetLastError (ERROR_FILE_NOT_FOUND);
		return -1;
	}
	cnt = 0;
	while (my_readdir (od, tname)) {
		if (!_tcscmp (tname, _T(".")) || !_tcscmp (tname, _T("..")))
			continue;
		cnt++;
		break;
	}
	my_closedir (od);
	if (cnt > 0) {
		SetLastError (ERROR_CURRENT_DIRECTORY);
		return -1;
	}

	return recycle (name, false);
}

/* "move to Recycle Bin" (if enabled) -version of DeleteFile() */
int my_unlink (const TCHAR *name, bool dontrecycle)
{
	return recycle (name, dontrecycle);
}

int my_rename (const TCHAR *oldname, const TCHAR *newname)
{
	const TCHAR *onamep, *nnamep;
	TCHAR opath[MAX_DPATH], npath[MAX_DPATH];

	if (currprefs.win32_filesystem_mangle_reserved_names == false) {
		_tcscpy (opath, PATHPREFIX);
		_tcscat (opath, oldname);
		onamep = opath;
		_tcscpy (npath, PATHPREFIX);
		_tcscat (npath, newname);
		nnamep = npath;
	} else {
		onamep = oldname;
		nnamep = newname;
	}
	return MoveFile (onamep, nnamep) == 0 ? -1 : 0;
}

struct my_opendir_s {
	HANDLE h;
	WIN32_FIND_DATA fd;
	int first;
};

struct my_opendir_s *my_opendir (const TCHAR *name)
{
	return my_opendir (name, _T("*.*"));
}
struct my_opendir_s *my_opendir (const TCHAR *name, const TCHAR *mask)
{
	struct my_opendir_s *mod;
	TCHAR tmp[MAX_DPATH];

	tmp[0] = 0;
	if (currprefs.win32_filesystem_mangle_reserved_names == false)
		_tcscpy (tmp, PATHPREFIX);
	_tcscat (tmp, name);
	_tcscat (tmp, _T("\\"));
	_tcscat (tmp, mask);
	mod = xmalloc (struct my_opendir_s, 1);
	if (!mod)
		return NULL;
	mod->h = FindFirstFile(tmp, &mod->fd);
	if (mod->h == INVALID_HANDLE_VALUE) {
		xfree (mod);
		return NULL;
	}
	mod->first = 1;
	return mod;
}

void my_closedir (struct my_opendir_s *mod)
{
	if (mod)
		FindClose (mod->h);
	xfree (mod);
}

int my_readdir (struct my_opendir_s *mod, TCHAR *name)
{
	if (mod->first) {
		_tcscpy (name, mod->fd.cFileName);
		mod->first = 0;
		return 1;
	}
	if (!FindNextFile (mod->h, &mod->fd))
		return 0;
	_tcscpy (name, mod->fd.cFileName);
	return 1;
}

struct my_openfile_s {
	HANDLE h;
};

void my_close (struct my_openfile_s *mos)
{
	CloseHandle (mos->h);
	xfree (mos);
}

uae_s64 int my_lseek (struct my_openfile_s *mos, uae_s64 int offset, int whence)
{
	LARGE_INTEGER li, old;

	old.QuadPart = 0;
	old.LowPart = SetFilePointer (mos->h, 0, &old.HighPart, FILE_CURRENT);
	if (old.LowPart == INVALID_SET_FILE_POINTER && GetLastError () != NO_ERROR)
		return -1;
	if (offset == 0 && whence == SEEK_CUR)
		return old.QuadPart;
	li.QuadPart = offset;
	li.LowPart = SetFilePointer (mos->h, li.LowPart, &li.HighPart,
		whence == SEEK_SET ? FILE_BEGIN : (whence == SEEK_END ? FILE_END : FILE_CURRENT));
	if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError () != NO_ERROR)
		return -1;
	return old.QuadPart;
}
uae_s64 int my_fsize (struct my_openfile_s *mos)
{
	LARGE_INTEGER li;
	if (!GetFileSizeEx (mos->h, &li))
		return -1;
	return li.QuadPart;
}


unsigned int my_read (struct my_openfile_s *mos, void *b, unsigned int size)
{
	DWORD read = 0;
	ReadFile (mos->h, b, size, &read, NULL);
	return read;
}

unsigned int my_write (struct my_openfile_s *mos, void *b, unsigned int size)
{
	DWORD written = 0;
	WriteFile (mos->h, b, size, &written, NULL);
	return written;
}

BOOL SetFileAttributesSafe (const TCHAR *name, DWORD attr)
{
	DWORD last;
	BOOL ret;
	const TCHAR *namep;
	TCHAR path[MAX_DPATH];

	last = SetErrorMode (SEM_FAILCRITICALERRORS);
	if (currprefs.win32_filesystem_mangle_reserved_names == false) {
		_tcscpy (path, PATHPREFIX);
		_tcscat (path, name);
		namep = path;
	} else {
		namep = name;
	}
	ret = SetFileAttributes (namep, attr);
	SetErrorMode (last);
	return ret;
}

DWORD GetFileAttributesSafe (const TCHAR *name)
{
	DWORD attr, last;
	const TCHAR *namep;
	TCHAR path[MAX_DPATH];

	last = SetErrorMode (SEM_FAILCRITICALERRORS);
	if (currprefs.win32_filesystem_mangle_reserved_names == false) {
		_tcscpy (path, PATHPREFIX);
		_tcscat (path, name);
		namep = path;
	} else {
		namep = name;
	}
	attr = GetFileAttributes (namep);
	SetErrorMode (last);
	return attr;
}

int my_existsfile (const TCHAR *name)
{
	DWORD attr;
	
	attr = GetFileAttributesSafe (name);
	if (attr == INVALID_FILE_ATTRIBUTES)
		return 0;
	if (!(attr & FILE_ATTRIBUTE_DIRECTORY))
		return 1;
	return 0;
}

int my_existsdir (const TCHAR *name)
{
	DWORD attr;

	attr = GetFileAttributesSafe (name);
	if (attr == INVALID_FILE_ATTRIBUTES)
		return 0;
	if (attr & FILE_ATTRIBUTE_DIRECTORY)
		return 1;
	return 0;
}

int my_readonlyfile(const TCHAR *name)
{
	HANDLE h;

	if (!my_existsfile(name))
		return -1;
	h = CreateFile(name, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (h == INVALID_HANDLE_VALUE) {
		DWORD err = GetLastError();
		if (err == ERROR_ACCESS_DENIED || err == ERROR_WRITE_PROTECT)
			return true;
	}
	CloseHandle(h);
	return false;
}

struct my_openfile_s *my_open (const TCHAR *name, int flags)
{
	struct my_openfile_s *mos;
	HANDLE h;
	DWORD DesiredAccess = GENERIC_READ;
	DWORD ShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
	DWORD CreationDisposition = OPEN_EXISTING;
	DWORD FlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
	DWORD attr;
	const TCHAR *namep;
	TCHAR path[MAX_DPATH];
	
	if (currprefs.win32_filesystem_mangle_reserved_names == false) {
		_tcscpy (path, PATHPREFIX);
		_tcscat (path, name);
		namep = path;
	} else {
		namep = name;
	}
	mos = xmalloc (struct my_openfile_s, 1);
	if (!mos)
		return NULL;
	attr = GetFileAttributesSafe (name);
	if (flags & O_TRUNC)
		CreationDisposition = CREATE_ALWAYS;
	else if (flags & O_CREAT)
		CreationDisposition = OPEN_ALWAYS;
	if (flags & O_WRONLY)
		DesiredAccess = GENERIC_WRITE;
	if (flags == O_RDONLY) {
		DesiredAccess = GENERIC_READ;
		CreationDisposition = OPEN_EXISTING;
	}
	if (flags & O_RDWR)
		DesiredAccess = GENERIC_READ | GENERIC_WRITE;
	if (CreationDisposition == CREATE_ALWAYS && attr != INVALID_FILE_ATTRIBUTES && (attr & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN)))
		SetFileAttributesSafe (name, FILE_ATTRIBUTE_NORMAL);
	h = CreateFile (namep, DesiredAccess, ShareMode, NULL, CreationDisposition, FlagsAndAttributes, NULL);
	if (h == INVALID_HANDLE_VALUE) {
		DWORD err = GetLastError();
		if (err == ERROR_ACCESS_DENIED && (DesiredAccess & GENERIC_WRITE)) {
			DesiredAccess &= ~GENERIC_WRITE;
			h = CreateFile (namep, DesiredAccess, ShareMode, NULL, CreationDisposition, FlagsAndAttributes, NULL);
			if (h == INVALID_HANDLE_VALUE)
				err = GetLastError();
		}
		if (h == INVALID_HANDLE_VALUE) {
			write_log (_T("failed to open '%s' %x %x err=%d\n"), namep, DesiredAccess, CreationDisposition, err);
			xfree (mos);
			mos = NULL;
			goto err;
		}
	}
	mos->h = h;
err:
	//write_log (_T("open '%s' = %x\n"), namep, mos ? mos->h : 0);
	return mos;
}

int my_truncate (const TCHAR *name, uae_u64 len)
{
	HANDLE hFile;
	BOOL bResult = FALSE;
	int result = -1;
	const TCHAR *namep;
	TCHAR path[MAX_DPATH];
	
	if (currprefs.win32_filesystem_mangle_reserved_names == false) {
		_tcscpy (path, PATHPREFIX);
		_tcscat (path, name);
		namep = path;
	} else {
		namep = name;
	}
	if ((hFile = CreateFile (namep, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
		OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ) ) != INVALID_HANDLE_VALUE )
	{
		LARGE_INTEGER li;
		li.QuadPart = len;
		li.LowPart = SetFilePointer (hFile, li.LowPart, &li.HighPart, FILE_BEGIN);
		if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError () != NO_ERROR) {
			write_log (_T("truncate: SetFilePointer() failure for %s to posn %d\n"), namep, len);
		} else {
			if (SetEndOfFile (hFile) == TRUE)
				result = 0;
		}
		CloseHandle (hFile);
	} else {
		write_log (_T("truncate: CreateFile() failed to open %s\n"), namep);
	}
	return result;
}

int dos_errno (void)
{
	DWORD e = GetLastError ();

	//write_log (_T("ec=%d\n"), e);
	switch (e) {
	case ERROR_NOT_ENOUGH_MEMORY:
	case ERROR_OUTOFMEMORY:
		return ERROR_NO_FREE_STORE;

	case ERROR_FILE_EXISTS:
	case ERROR_ALREADY_EXISTS:
		return ERROR_OBJECT_EXISTS;

	case ERROR_WRITE_PROTECT:
	case ERROR_ACCESS_DENIED:
		return ERROR_WRITE_PROTECTED;

	case ERROR_FILE_NOT_FOUND:
	case ERROR_INVALID_DRIVE:
	case ERROR_INVALID_NAME:
	case ERROR_PATH_NOT_FOUND:
	case ERROR_NOT_READY:
	case ERROR_BAD_UNIT:
	case ERROR_REQUEST_ABORTED:
	case ERROR_INVALID_HANDLE:
	case ERROR_DEV_NOT_EXIST:
	case ERROR_INVALID_PARAMETER:
	case ERROR_NETNAME_DELETED:
	case ERROR_NETWORK_ACCESS_DENIED:
	case ERROR_BAD_NET_NAME:
	case ERROR_BAD_NETPATH:
	case ERROR_NETWORK_BUSY:
	case ERROR_SEM_TIMEOUT:
		return ERROR_OBJECT_NOT_AROUND;

	case ERROR_HANDLE_DISK_FULL:
	case ERROR_DISK_FULL:
		return ERROR_DISK_IS_FULL;

	case ERROR_SHARING_VIOLATION:
	case ERROR_BUSY:
	case ERROR_USER_MAPPED_FILE:
		return ERROR_OBJECT_IN_USE;

	case ERROR_CURRENT_DIRECTORY:
		return ERROR_DIRECTORY_NOT_EMPTY;

	case ERROR_NEGATIVE_SEEK:
	case ERROR_SEEK_ON_DEVICE:
		return ERROR_SEEK_ERROR;

	default:
		write_log (_T("Unimplemented error %d\n"), e);
		/* most common */
		return ERROR_OBJECT_NOT_AROUND;
	}
}

int my_getvolumeinfo (const TCHAR *root)
{
	DWORD v, err;
	int ret = 0;
	TCHAR volume[MAX_DPATH];

	v = GetFileAttributesSafe (root);
	err = GetLastError ();
	if (v == INVALID_FILE_ATTRIBUTES)
		return -1;
	if (!(v & FILE_ATTRIBUTE_DIRECTORY))
		return -2;
	/*
	if (v & FILE_ATTRIBUTE_READONLY)
	ret |= MYVOLUMEINFO_READONLY;
	*/
	if (GetVolumePathName (root, volume, sizeof (volume) / sizeof(TCHAR))) {
		TCHAR fsname[MAX_DPATH];
		DWORD comlen;
		DWORD flags;
		if (GetVolumeInformation (volume, NULL, 0, NULL, &comlen, &flags, fsname, sizeof fsname / sizeof (TCHAR))) {
			//write_log (_T("Volume %s FS=%s maxlen=%d flags=%08X\n"), volume, fsname, comlen, flags);
			if (flags & FILE_NAMED_STREAMS)
				ret |= MYVOLUMEINFO_STREAMS;
		}
	}
	return ret;
}

FILE *my_opentext (const TCHAR *name)
{
	FILE *f;
	uae_u8 tmp[4];
	size_t v;

	f = _tfopen (name, _T("rb"));
	if (!f)
		return NULL;
	v = fread (tmp, 1, sizeof tmp, f);
	fclose (f);
	if (v == 4) {
		if (tmp[0] == 0xef && tmp[1] == 0xbb && tmp[2] == 0xbf)
			return _tfopen (name, _T("r, ccs=UTF-8"));
		if (tmp[0] == 0xff && tmp[1] == 0xfe)
			return _tfopen (name, _T("r, ccs=UTF-16LE"));
	}
	return _tfopen (name, _T("r"));
}

typedef BOOL (CALLBACK* GETVOLUMEINFORMATIONBYHANDLEW)
    (_In_ HANDLE hFile,
    LPWSTR lpVolumeNameBuffer,
    DWORD nVolumeNameSize,
    LPDWORD lpVolumeSerialNumber,
    LPDWORD lpMaximumComponentLength,
    LPDWORD lpFileSystemFlags,
    LPWSTR lpFileSystemNameBuffer,
    DWORD nFileSystemNameSize);


static bool isfat (HANDLE h)
{
	TCHAR fsname[MAX_DPATH];
	DWORD comlen;
	DWORD flags;
	GETVOLUMEINFORMATIONBYHANDLEW pGetVolumeInformationByHandleW;
	
	pGetVolumeInformationByHandleW = (GETVOLUMEINFORMATIONBYHANDLEW)GetProcAddress(
		GetModuleHandle (_T("kernel32.dll")), "GetVolumeInformationByHandleW");
	if (!pGetVolumeInformationByHandleW)
		return false;
	if (pGetVolumeInformationByHandleW (h, NULL, NULL, NULL, &comlen, &flags, fsname, sizeof fsname / sizeof (TCHAR))) {
		if (!_tcsncmp (fsname, _T("FAT"), 3)) {
			return true;
		}
	}
	return false;
}

bool my_stat (const TCHAR *name, struct mystat *statbuf)
{
	DWORD attr, ok;
	FILETIME ft, lft;
	HANDLE h;
	BY_HANDLE_FILE_INFORMATION fi;
	const TCHAR *namep;
	bool fat;
	TCHAR path[MAX_DPATH];

	if (currprefs.win32_filesystem_mangle_reserved_names == false) {
		_tcscpy (path, PATHPREFIX);
		_tcscat (path, name);
		namep = path;
	} else {
		namep = name;
	}

	// FILE_FLAG_BACKUP_SEMANTICS = can also "open" directories
	h = CreateFile (namep, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL);
	if (h == INVALID_HANDLE_VALUE)
		return false;
	fat = isfat (h);

	ok = GetFileInformationByHandle (h, &fi);
	CloseHandle (h);

	attr = 0;
	ft.dwHighDateTime = ft.dwLowDateTime = 0;
	if (ok) {
		attr = fi.dwFileAttributes;
		if (fat) {
			// fat lastwritetime only has 2 second resolution
			// fat creationtime has 10ms resolution
			// use creationtime if creationtime is inside lastwritetime 2s resolution
			ULARGE_INTEGER ct, wt;
			ct.HighPart = fi.ftCreationTime.dwHighDateTime;
			ct.LowPart = fi.ftCreationTime.dwLowDateTime;
			wt.HighPart = fi.ftLastWriteTime.dwHighDateTime;
			wt.LowPart = fi.ftLastWriteTime.dwLowDateTime;
			uae_u64 ctsec = ct.QuadPart / 10000000;
			uae_u64 wtsec = wt.QuadPart / 10000000;
			if (wtsec == ctsec || wtsec + 1 == ctsec) {
				ft = fi.ftCreationTime;
			} else {
				ft = fi.ftLastWriteTime;
			}
		} else {
			ft = fi.ftLastWriteTime;
		}
		statbuf->size = ((uae_u64)fi.nFileSizeHigh << 32) | fi.nFileSizeLow;
	} else {
		write_log (_T("GetFileInformationByHandle(%s) failed: %d\n"), namep, GetLastError ());
		return false;
	}

	statbuf->mode = (attr & FILE_ATTRIBUTE_READONLY) ? FILEFLAG_READ : FILEFLAG_READ | FILEFLAG_WRITE;
	if (attr & FILE_ATTRIBUTE_ARCHIVE)
		statbuf->mode |= FILEFLAG_ARCHIVE;
	if (attr & FILE_ATTRIBUTE_DIRECTORY)
		statbuf->mode |= FILEFLAG_DIR;

	FileTimeToLocalFileTime (&ft,&lft);
	uae_u64 t = (*(__int64 *)&lft-((__int64)(369*365+89)*(__int64)(24*60*60)*(__int64)10000000));
	statbuf->mtime.tv_sec = t / 10000000;
	statbuf->mtime.tv_usec = (t / 10) % 1000000;

	return true;
}


bool my_chmod (const TCHAR *name, uae_u32 mode)
{
	DWORD attr = FILE_ATTRIBUTE_NORMAL;
	if (!(mode & FILEFLAG_WRITE))
		attr |= FILE_ATTRIBUTE_READONLY;
	if (mode & FILEFLAG_ARCHIVE)
		attr |= FILE_ATTRIBUTE_ARCHIVE;
	if (SetFileAttributesSafe (name, attr))
		return true;
	return false;
}

static void tmToSystemTime (struct tm *tmtime, LPSYSTEMTIME systime)
{
	if (tmtime == NULL) {
		GetSystemTime (systime);
	} else {
		systime->wDay       = tmtime->tm_mday;
		systime->wDayOfWeek = tmtime->tm_wday;
		systime->wMonth     = tmtime->tm_mon + 1;
		systime->wYear      = tmtime->tm_year + 1900;
		systime->wHour      = tmtime->tm_hour;
		systime->wMinute    = tmtime->tm_min;
		systime->wSecond    = tmtime->tm_sec;
		systime->wMilliseconds = 0;
	}
}

static int setfiletime (const TCHAR *name, int days, int minute, int tick, int tolocal)
{
	FILETIME LocalFileTime, FileTime;
	HANDLE hFile;
	const TCHAR *namep;
	TCHAR path[MAX_DPATH];
	
	if (currprefs.win32_filesystem_mangle_reserved_names == false) {
		_tcscpy (path, PATHPREFIX);
		_tcscat (path, name);
		namep = path;
	} else {
		namep = name;
	}

	if ((hFile = CreateFile(namep, FILE_WRITE_ATTRIBUTES, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL)) == INVALID_HANDLE_VALUE)
		return 0;

	for (;;) {
		ULARGE_INTEGER lft;

		lft.QuadPart = (((uae_u64)(377*365+91+days)*(uae_u64)1440+(uae_u64)minute)*(uae_u64)(60*50)+(uae_u64)tick)*(uae_u64)200000;
		LocalFileTime.dwHighDateTime = lft.HighPart;
		LocalFileTime.dwLowDateTime = lft.LowPart;
		if (tolocal) {
			if (!LocalFileTimeToFileTime (&LocalFileTime, &FileTime))
				FileTime = LocalFileTime;
		} else {
			FileTime = LocalFileTime;
		}
		if (!SetFileTime (hFile, &FileTime, &FileTime, &FileTime)) {
			if (days > 47846) { // > 2108-12-31 (fat limit)
				days = 47846;
				continue;
			}
			if (days < 730) { // < 1980-01-01 (fat limit)
				days = 730;
				continue;
			}
		}
		break;
	}

	CloseHandle (hFile);

	return 1;
}

bool my_utime (const TCHAR *name, struct mytimeval *tv)
{
	int result = -1, tolocal;
	int days, mins, ticks;
	struct mytimeval tv2;

	if (!tv) {
		struct timeb time;
		ftime (&time);
		tv2.tv_sec = time.time;
		tv2.tv_usec = time.millitm * 1000;
		tolocal = 0;
	} else {
		tv2.tv_sec = tv->tv_sec;
		tv2.tv_usec = tv->tv_usec;
		tolocal = 1;
	}
	timeval_to_amiga (&tv2, &days, &mins, &ticks, 50);
	if (setfiletime (name, days, mins, ticks, tolocal))
		return true;

	return false;
}

bool my_createsoftlink(const TCHAR *path, const TCHAR *target)
{
	return CreateSymbolicLink(path, target, my_existsdir (target) ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0) != 0;
}

bool my_issamepath(const TCHAR *path1, const TCHAR *path2)
{
	TCHAR path1o[MAX_DPATH], path2o[MAX_DPATH];
	my_canonicalize_path(path1, path1o, sizeof path1o / sizeof(TCHAR));
	my_canonicalize_path(path2, path2o, sizeof path2o / sizeof(TCHAR));
	if (!_tcsicmp(path1o, path2o))
		return true;
	return false;
}

void my_canonicalize_path(const TCHAR *path, TCHAR *out, int size)
{
	TCHAR tmp[MAX_DPATH];
	int v;

	// don't attempt to validate and canonicalize invalid or fake paths
	if (path[0] == ':' || path[0] == 0 || _tcscmp(path, _T("\\")) == 0 || _tcscmp(path, _T("/")) == 0) {
		_tcsncpy(out, path, size);
		out[size - 1] = 0;
		return;
	}
	// skip network paths, prevent common few seconds delay.
	if (path[0] == '\\' && path[1] == '\\') {
		_tcsncpy(out, path, size);
		out[size - 1] = 0;
		return;
	}
	v = GetLongPathName (path, tmp, sizeof tmp / sizeof (TCHAR));
	if (v > sizeof tmp / sizeof (TCHAR)) {
		_tcsncpy (out, path, size);
		out[size - 1] = 0;
		return;
	}
	if (!v)
		_tcscpy(tmp, path);
	GetFullPathName(tmp, size, out, NULL);
}

int my_issamevolume(const TCHAR *path1, const TCHAR *path2, TCHAR *path)
{
	TCHAR p1[MAX_DPATH];
	TCHAR p2[MAX_DPATH];
	int cnt;
	size_t len;

	my_canonicalize_path(path1, p1, sizeof p1 / sizeof (TCHAR));
	my_canonicalize_path(path2, p2, sizeof p2 / sizeof (TCHAR));
	len = _tcslen (p1);
	if (len > _tcslen (p2))
		len = _tcslen (p2);
	if (_tcsnicmp (p1, p2, len))
		return 0;
	_tcscpy (path, p2 + len);
	cnt = 0;
	for (int i = 0; i < _tcslen (path); i++) {
		if (path[i] == '\\' || path[i] == '/') {
			path[i] = '/';
			cnt++;
		}
	}
	write_log (_T("'%s' (%s) matched with '%s' (%s), extra = '%s'\n"), path1, p1, path2, p2, path);
	return cnt;
}

typedef struct _REPARSE_DATA_BUFFER {
  ULONG  ReparseTag;
  USHORT ReparseDataLength;
  USHORT Reserved;
  union {
    struct {
      USHORT SubstituteNameOffset;
      USHORT SubstituteNameLength;
      USHORT PrintNameOffset;
      USHORT PrintNameLength;
      ULONG  Flags;
      WCHAR  PathBuffer[1];
    } SymbolicLinkReparseBuffer;
    struct {
      USHORT SubstituteNameOffset;
      USHORT SubstituteNameLength;
      USHORT PrintNameOffset;
      USHORT PrintNameLength;
      WCHAR  PathBuffer[1];
    } MountPointReparseBuffer;
    struct {
      UCHAR DataBuffer[1];
    } GenericReparseBuffer;
  };
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;

static int my_resolvessymboliclink2(TCHAR *linkfile, int size)
{
	WIN32_FIND_DATA fd;
	HANDLE h;
	int ret = -1;
	DWORD returnedDataSize;
	uae_u8 tmp[MAX_DPATH * 2];
	TCHAR tmp2[MAX_DPATH];

	h = FindFirstFile (linkfile, &fd);
	if (h == INVALID_HANDLE_VALUE)
		return -1;
	FindClose(h);
	if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) || fd.dwReserved0 != IO_REPARSE_TAG_SYMLINK)
		return -1;
	h = CreateFile (linkfile, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
		OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_REPARSE_POINT | FILE_FLAG_OPEN_REPARSE_POINT , NULL);
	if (h == INVALID_HANDLE_VALUE)
		return 0;
	if (DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, NULL, 0, tmp, sizeof tmp, &returnedDataSize, NULL)) {
		REPARSE_DATA_BUFFER *rdb = (REPARSE_DATA_BUFFER*)tmp;
		if (size > 0) {
			if (rdb->SymbolicLinkReparseBuffer.Flags & 1) { // SYMLINK_FLAG_RELATIVE
				_tcscpy (tmp2, linkfile);
				PathRemoveFileSpec (tmp2);
				_tcscat (tmp2, _T("\\"));
				TCHAR *p = tmp2 + _tcslen (tmp2);
				memcpy (p,
					(uae_u8*)rdb->SymbolicLinkReparseBuffer.PathBuffer + rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset,
					rdb->SymbolicLinkReparseBuffer.SubstituteNameLength);
				p[rdb->SymbolicLinkReparseBuffer.SubstituteNameLength / 2] = 0;
			} else {
				memcpy (tmp2,
					(uae_u8*)rdb->SymbolicLinkReparseBuffer.PathBuffer + rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset,
					rdb->SymbolicLinkReparseBuffer.SubstituteNameLength);
				tmp2[rdb->SymbolicLinkReparseBuffer.SubstituteNameLength / 2] = 0;
			}
			if (!_tcsnicmp (tmp2, _T("\\??\\"), 4)) {
				memmove (tmp2, tmp2 + 4, (_tcslen (tmp2 + 4) + 1) * sizeof (TCHAR));
			}
			my_canonicalize_path (tmp2, linkfile, size);
		}
		ret = 1;
	}
	CloseHandle(h);
	return ret;
}
bool my_resolvessymboliclink(TCHAR *linkfile, int size)
{
	return my_resolvessymboliclink2(linkfile, size) > 0;
}

// http://msdn.microsoft.com/en-us/library/aa969393.aspx
bool my_resolveshortcut(TCHAR *linkfile, int size) 
{ 
	bool ok = false;
    HRESULT hres; 
    IShellLink* psl; 
    WCHAR szGotPath[MAX_DPATH]; 
    WCHAR szDescription[MAX_DPATH]; 
    WIN32_FIND_DATA wfd; 
 
	const TCHAR *ext = _tcsrchr (linkfile, '.');
	if (!ext || _tcsicmp (ext, _T(".lnk")) != 0)
		return false;

    // Get a pointer to the IShellLink interface. It is assumed that CoInitialize
    // has already been called. 
    hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl); 
    if (SUCCEEDED(hres)) 
    { 
        IPersistFile* ppf;
 
        // Get a pointer to the IPersistFile interface. 
        hres = psl->QueryInterface(IID_IPersistFile, (void**)&ppf); 
        
        if (SUCCEEDED(hres)) 
        { 
            // Add code here to check return value from MultiByteWideChar 
            // for success.
 
            // Load the shortcut. 
            hres = ppf->Load(linkfile, STGM_READ); 
            
            if (SUCCEEDED(hres)) 
            { 
                // Resolve the link. 
                hres = psl->Resolve(NULL, SLR_NO_UI); 

                if (SUCCEEDED(hres)) 
                { 
                    // Get the path to the link target. 
                    hres = psl->GetPath(szGotPath, MAX_DPATH, &wfd, SLGP_SHORTPATH);

                    if (SUCCEEDED(hres)) 
                    { 
                        // Get the description of the target. 
                        hres = psl->GetDescription(szDescription, MAX_DPATH);

                        if (SUCCEEDED(hres)) 
                        {
							if (size > 0)
								my_canonicalize_path (szGotPath, linkfile, size);
                            ok = SUCCEEDED(hres);
                        }
                    }
                } 
            } 

            // Release the pointer to the IPersistFile interface. 
            ppf->Release(); 
        } 

        // Release the pointer to the IShellLink interface. 
        psl->Release(); 
    } 
    return ok; 
}

// http://msdn.microsoft.com/en-us/library/aa969393.aspx
bool my_createshortcut(const TCHAR *source, const TCHAR *target, const TCHAR *description) 
{ 
	HRESULT hres; 
	IShellLink *psl;
 
	// Get a pointer to the IShellLink interface. It is assumed that CoInitialize
	// has already been called.
	hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl); 
	if (SUCCEEDED(hres)) 
	{ 
		IPersistFile *ppf; 
 
		// Set the path to the shortcut target and add the description. 
		TCHAR target2[MAX_DPATH];
		my_canonicalize_path(target, target2, sizeof(target2) / sizeof(TCHAR));
		psl->SetPath(target2);
		psl->SetDescription(description); 
 
		// Query IShellLink for the IPersistFile interface, used for saving the 
		// shortcut in persistent storage. 
		hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf); 
 
		if (SUCCEEDED(hres)) 
		{ 
			// Save the link by calling IPersistFile::Save. 
			TCHAR tmp[MAX_DPATH];
			my_canonicalize_path(source, tmp, sizeof(tmp) / sizeof(TCHAR));
			const TCHAR *ext = _tcsrchr (tmp, '.');
			if (!ext || _tcsicmp(ext, _T(".lnk")) != 0) {
				_tcscat(tmp, _T(".lnk"));
			}
			hres = ppf->Save(tmp, TRUE); 
			ppf->Release(); 
		}
		psl->Release(); 
	}
	return SUCCEEDED(hres);
}

bool my_resolvesoftlink(TCHAR *linkfile, int size, bool linkonly)
{
	if (!linkonly) {
		int v = my_resolvessymboliclink2(linkfile, size);
		if (v > 0)
			return true;
		if (v == 0)
			return false;
	}
	if (my_resolveshortcut(linkfile,size))
		return true;
	return false;
}

const TCHAR *my_getfilepart(const TCHAR *filename)
{
	const TCHAR *p;

	p = _tcsrchr(filename, '\\');
	if (p)
		return p + 1;
	p = _tcsrchr(filename, '/');
	if (p)
		return p + 1;
	return filename;
}
