#include <../arch/arch.h>

/*
 * FIXME: move to dospath.c
 */
void V_CALL
dos_name_to_unix_name(char *name,char *native)
{
	if(native[1]==':')
	{
		/* drive present */
		name[0] = name[1]='/';
		name[2] = (char)tolower(native[0]);
		name += 3;
		native += 2;
	}
	for(;*native;native++,name++)
	{
		if(*native=='\\')
			*name = '/';
		else
			*name = *native;
	}
	*name=0;
	return;
}

void V_CALL
unix_name_to_dos_name(char *native,char *name)
{
	if(name[0]=='/' && name[1]=='/' && 
		(name[3]=='/' || !name[3]))
	{
		/* drive present */
		native[0] = (char)toupper(name[2]);
		native[1] = ':';
		name += 3;
		native += 2;
	}
	for(;*name;native++,name++)
	{
		if(*name=='/')
			*native = '\\';
		else
			*native = *name;
	}
	*native=0;
	return;
}

#ifdef	BUILD_DLL

void	V_GLOBAL
libvDosPathToUnixPath(char *unix_path_ret,char *dos_path)
{
	if(unix_path_ret && dos_path)
	{
		dos_name_to_unix_name(unix_path_ret,dos_path);
		return;
	}
#ifdef	CHECKED_BUILD
	conlPuts(LOG_ERROR,"libvDosPathToUnixPath(): incorrect parameter(s).\n");
#endif	/* CHECKED_BUILD */
}

void	V_GLOBAL
libvUnixPathToDosPath(char *dos_path_ret,char *unix_path)
{
	if(dos_path_ret && unix_path)
	{
		unix_name_to_dos_name(dos_path_ret,unix_path);
		return;
	}
#ifdef	CHECKED_BUILD
	conlPuts(LOG_ERROR,"libvUnixPathToDosPath(): incorrect parameter(s).\n");
#endif	/* CHECKED_BUILD */
}

#endif	/* BUILD_DLL */

vboolean	V_CALL
archFileExists(char *right_slashed_name)
{
	DWORD attrib;
	char native[_MAX_PATH] = {0};
	unix_name_to_dos_name(native,right_slashed_name);
	attrib = GetFileAttributes(native);
	if(attrib != 0xFFFFFFFF && !(attrib & FILE_ATTRIBUTE_DIRECTORY)) /* fixme?: unsigned(-1) */
		return true;
	return false;
}

vboolean	V_CALL
archDirectoryExists(char *right_slashed_name)
{
	DWORD attrib;
	char native[_MAX_PATH] = {0};
	unix_name_to_dos_name(native,right_slashed_name);
	attrib = GetFileAttributes(native);
	if(attrib == FILE_ATTRIBUTE_DIRECTORY)
		return true;
	return false;
}

vboolean	V_CALL
archDeleteFile(char *right_slashed_name)
{
	char native[_MAX_PATH] = {0};
	unix_name_to_dos_name(native,right_slashed_name);
	return DeleteFile(native) ? true : false;
}

vboolean	V_CALL
archMoveFile(char *old_path,char *new_path)
{
	char native_old[_MAX_PATH] = {0}, native_new[_MAX_PATH] = {0};
	unix_name_to_dos_name(native_old,old_path);
	unix_name_to_dos_name(native_new,new_path);
	return MoveFile(native_old,native_new) ? true : false;
}

arch_file_t	V_CALL
archFileOpen(char *right_slashed_name,file_mode_t filemode,
	file_type_t *ft_ret_opt)
{
	ARCH_CALLER_NAME(archFileOpen)
	HANDLE fd;
	char native[_MAX_PATH] = {0};
	DWORD desired_access, share_mode, creation_distribution;
	switch(filemode)
	{
		case file_mode_read:
			desired_access = GENERIC_READ;
			share_mode = FILE_SHARE_READ;
			creation_distribution = OPEN_EXISTING;
			break;
		case file_mode_write:
			desired_access = GENERIC_READ | GENERIC_WRITE;
			share_mode = 0;
			creation_distribution = CREATE_ALWAYS;
			break;
		case file_mode_append:
			desired_access = GENERIC_READ | GENERIC_WRITE;
			share_mode = 0;
			creation_distribution = OPEN_EXISTING;
			break;
		default:
			return ARCH_INVALID_FILE;
	}
	unix_name_to_dos_name(native,right_slashed_name);
	fd = CreateFileA(native,desired_access,share_mode,NULL,
		creation_distribution,0,NULL);
	if(fd == ARCH_INVALID_FILE)
	{
		SYSCALL_FAILED_1STR("CreateFile",native);
		return ARCH_INVALID_FILE;
	}
	if(filemode == file_mode_append)
		SetFilePointer(fd,0,NULL,FILE_END);
	if(ft_ret_opt)
		*ft_ret_opt = archGetFileType(fd);
	return fd;
}

file_type_t V_CALL
archGetFileType(arch_file_t file)
{
	DWORD ft = GetFileType(file);
	if(ft == FILE_TYPE_DISK)
		return file_type_reg;
	else if(ft == FILE_TYPE_CHAR)
		return file_type_chr_dev;
	return file_type_unknown;
}

vsize_t	V_CALL
archGetFileSize(arch_file_t file)
{
	ARCH_CALLER_NAME(archGetFileSize)
	DWORD size_lo, size_hi = 0;
	size_lo = GetFileSize(file,&size_hi);
#if 0
	/*
	 * FIXME: why can't we use preprocessor with _MSC ?
	 */
	if(sizeof(vsize_t) == sizeof(DWORD))
	{
		if(size_hi)
			return (vsize_t)0xFFFFFFFF;
		return (vsize_t)size_lo;
	}
	else
		return (size_hi << 32) + size_lo;
#else
	if(size_hi)
	{
		ARCH_WARNING("file too long");
	}
#endif
	return size_lo;
}

vsize_t	V_CALL
archGetFilePointer(arch_file_t file)
{
	ARCH_CALLER_NAME(archGetFilePointer)
	LONG hibits = 0;
	DWORD ptr = (vsize_t)SetFilePointer(file,0,&hibits,FILE_CURRENT);
	if(hibits)
	{
		ARCH_WARNING("file too long");
		return (vsize_t)-1;
	}
	return ptr;
}

vboolean	V_CALL
archSetFilePointer(arch_file_t file,vsize_t	offset,u32_t whence)
{
	DWORD move_method;
	switch(whence)
	{
		case SEEK_SET:
			move_method = FILE_BEGIN;
			break;
		case SEEK_CUR:
			move_method = FILE_CURRENT;
			break;
		case SEEK_END:
			move_method = FILE_END;
			break;
		default:
			return false;
	}
	SetFilePointer(file,offset,NULL,move_method);
	return GetLastError() ? false : true;
}

vboolean 	V_CALL
archFlushFileBuffers(arch_file_t file)
{
	return FlushFileBuffers(file) ? true : false;
}

vsize_t	V_CALL
archFileRead(arch_file_t file,void *buffer,vsize_t len)
{
	DWORD bytes_read;
	if(ReadFile(file,buffer,(DWORD)len,&bytes_read,NULL))
		return (vsize_t)bytes_read;
	else
		return 0;
}

vsize_t	V_CALL
archFileWrite(arch_file_t file,void *buffer,vsize_t len)
{
	DWORD bytes_written;
	if(WriteFile(file,buffer,(DWORD)len,&bytes_written,NULL))
		return bytes_written;
	else
		return 0;
}

void		V_CALL
archFileClose(arch_file_t file)
{
	if(file != ARCH_INVALID_FILE &&
		file != GetStdHandle(STD_INPUT_HANDLE) &&
		file != GetStdHandle(STD_OUTPUT_HANDLE))
		CloseHandle(file);
}

arch_file_t	V_CALL
archOpenConsole(file_mode_t filemode)
{
	if(filemode == file_mode_read)
		return GetStdHandle(STD_INPUT_HANDLE);
	else
		return GetStdHandle(STD_OUTPUT_HANDLE);
}

vboolean	V_CALL
archGetCurrentDirectory(char *dir_ret,u32_t dir_ret_size)
{
	char native[_MAX_PATH], name[_MAX_PATH];
	if(GetCurrentDirectoryA(_MAX_PATH,native))
	{
		dos_name_to_unix_name(name,native);
		if(strlen(name) < dir_ret_size)
		{
			strCopy(dir_ret,name,dir_ret_size);
			return true;
		}
	}
	return false;
}

vboolean	V_CALL
archSetCurrentDirectory(char *new_dir,vboolean *dir_exist_ret_opt)
{
	ARCH_CALLER_NAME(archSetCurrentDirectory)
	DWORD errcode;
	char name[_MAX_PATH];
	unix_name_to_dos_name(name,new_dir);
	if(SetCurrentDirectory(name))
	{
		if(dir_exist_ret_opt)
			*dir_exist_ret_opt = true;
		return true;
	}
	errcode = GetLastError();
	if(errcode != ERROR_PATH_NOT_FOUND && 
		errcode != ERROR_FILE_NOT_FOUND)
	{
		SYSCALL_FAILED_1STR("SetCurrentDirectory",name);
		if(dir_exist_ret_opt)
			*dir_exist_ret_opt = true;
		return false;
	}
	if(dir_exist_ret_opt)
		*dir_exist_ret_opt = false;
	return false;
}

vboolean	V_CALL
archMakeDirectory(char *new_dir)
{
	char name[_MAX_PATH];
	unix_name_to_dos_name(name,new_dir);
	return CreateDirectory(name,NULL) ? true : false;
}

vboolean V_CALL
archGetTempDirectory(char *dest,u32_t dest_len)
{
	ARCH_CALLER_NAME(archGetTempDirectory)
	char *buf;
	DWORD retlen;
	retlen = GetTempPath(0,NULL);
	if(!retlen)
	{
		SYSCALL_FAILED("GetTempPath");
		return false;
	}
	/* extra byte required for 'c:\' -> '//c/' conversion */
	if((retlen+1) > dest_len)
	{
#ifdef	ARCH_VERBOSE
		conlPrintf(LOG_ERROR,
			"%s(%08x,%u): required %u bytes, got %u.\n",
			arch_caller_name,dest,dest_len,retlen+1,dest_len);
#endif	/* ARCH_VERBOSE */
		return false;
	}
	buf = (char*)memAlloc(INT_ALIGN(retlen));
	GetTempPath(retlen,buf);
	dos_name_to_unix_name(dest,buf);
	memFree(buf);
	return true;
}

vboolean	V_CALL
archFindFirst(char *dir_name,struct arch_dir_s *dir)
{
	char name[_MAX_PATH];
	unix_name_to_dos_name(name,dir_name);
	strCat(name,"\\*.*",_MAX_PATH - strlen(name));
	dir->sh = FindFirstFile(name,&(dir->ffd));
	if(dir->sh == INVALID_HANDLE_VALUE)	/* will never happen ? */
		return false;
	if(dir->ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
	{
		dir->is_dir = true;
		dir->ftype = file_type_unknown;
	}
	else
	{
		dir->is_dir = false;
		dir->ftype = file_type_reg;
	}
	dos_name_to_unix_name(dir->name, dir->ffd.cFileName);
	return true;
}

vboolean	V_CALL
archFindNext(struct arch_dir_s *dir)
{
	if(FindNextFile(dir->sh,&(dir->ffd)))
	{
		if(dir->ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		{
			dir->is_dir = true;
			dir->ftype = file_type_unknown;
		}
		else
		{
			dir->is_dir = false;
			dir->ftype = file_type_reg;
		}
		dos_name_to_unix_name(dir->name, dir->ffd.cFileName);
		return true;
	}
	return false;
}

void		V_CALL
archFindClose(struct arch_dir_s *dir)
{
	FindClose(dir->sh);
}

#ifdef	ARCH_MAPPED_IO_SUPPORTED

void_p		V_CALL
archMapRead(arch_file_t file,vsize_t start,vsize_t len,
	struct arch_filemap_s *map)
{
	ARCH_CALLER_NAME(archMapRead)
	DWORD size = GetFileSize(file,NULL);
	if(!size)
	{
		ARCH_ERROR("empty file, couldn't mmap()");
		return NULL;
	}
	if(!len)
		len = size - start;
	if(start+len > size)
	{
#ifdef	ARCH_VERBOSE
		conlPrintf(LOG_ERROR,"%s(): file too small (%u), couldn't mmap()"
			" %u bytes at 0x%08X.\n",arch_caller_name,size,len,start);
#endif	/* ARCH_VERBOSE */
		return NULL;
	}
	map->mapping_handle = 
		CreateFileMapping(file,NULL,PAGE_READONLY,0,size,NULL);
	if(map->mapping_handle == INVALID_HANDLE_VALUE)
	{
		SYSCALL_FAILED("CreateFileMapping");
		map->base_addr = NULL;
		return NULL;
	}
	map->base_addr = 
		MapViewOfFile(map->mapping_handle,FILE_MAP_READ,0,start,len);
	if(!map->base_addr)
	{
		SYSCALL_FAILED("MapViewOfFile");
		CloseHandle(map->mapping_handle);
		return NULL;
	}
	return map->base_addr;
}

void_p		V_CALL
archMapReadWrite(arch_file_t file,vsize_t start,vsize_t len,
	struct arch_filemap_s *map)
{
	ARCH_CALLER_NAME(archMapReadWrite)
	DWORD size = archGetFileSize(file);
	if(!len)
		return NULL;
	if(size < (start + len))
		size = start + len;
	map->mapping_handle = 
		CreateFileMapping(file,NULL,PAGE_READWRITE,0,size,NULL);
	if(map->mapping_handle == INVALID_HANDLE_VALUE)
	{
		SYSCALL_FAILED("CreateFileMapping");
		map->base_addr = NULL;
		return NULL;
	}
	map->base_addr = MapViewOfFile(map->mapping_handle,
		FILE_MAP_WRITE,0,start,len);
	if(!map->base_addr)
	{
		SYSCALL_FAILED("MapViewOfFile");
		CloseHandle(map->mapping_handle);
		return NULL;
	}
	return map->base_addr;
}

void		V_CALL
archUnmap(struct arch_filemap_s *map)
{
	if(map->mapping_handle != INVALID_HANDLE_VALUE)
	{
		if(map->base_addr)
		{
			UnmapViewOfFile(map->base_addr);
			map->base_addr = NULL;
		}
		CloseHandle(map->mapping_handle);
	}
	return;
}

#endif	/* ARCH_MAPPED_IO_SUPPORTED */
