implementation module System.File

//StdEnv
import StdArray
import StdBool
import StdFile
import StdList
import StdMisc
import StdString

import Data.Func
import Data.Error
import System.OSError
import System._Pointer
import System.Time
import Text

CHUNK_SIZE :== 1024

from System._Windows import 
	::HANDLE, 
	:: LPWIN32_FIND_DATA, 
	WIN32_FIND_DATA_size_bytes, 
	INVALID_HANDLE_VALUE,
	deleteFileA, 
	findClose,
	findFirstFileA,
	:: DWORD,
	SYSTEMTIME_wYear_offset,
	SYSTEMTIME_wMinute_offset,
	SYSTEMTIME_wSecond_offset,
	SYSTEMTIME_wHour_offset,
	SYSTEMTIME_wMonth_offset,
	SYSTEMTIME_wDay_offset,
	SYSTEMTIME_wDayOfWeek_offset,
	SYSTEMTIME_size_bytes,
	WIN32_FIND_DATA_ftCreationTime_bytes_offset,
	WIN32_FIND_DATA_ftLastWriteTime_bytes_offset,
	WIN32_FIND_DATA_ftLastAccessTime_bytes_offset,
	FILETIME_size_bytes,
	FILE_ATTRIBUTE_DIRECTORY,
	moveFileA,
	:: FILETIME,
	fileTimeToSystemTime,
	:: LPSYSTEMTIME
	
instance toString FileError
where
	toString CannotOpen = "Cannot open"
	toString CannotClose = "Cannot close"
	toString IOError = "I/O error"

readFile :: !String !*env -> (!MaybeError FileError String, !*env) | FileSystem env
readFile filename env = withFile filename FReadData readAll env

readFileLines :: !String !*env -> (!MaybeError FileError [String], !*env) | FileSystem env
readFileLines filename env = withFile filename FReadData readAllLines env

readAllLines :: !*File -> (!MaybeError FileError [String], !*File)
readAllLines file
# (result, file) = rec file []
= case result of
	Error e	   = (Error e, file)
	Ok lines = (Ok $ reverse lines, file)
where	
	rec :: *File [String] -> (!MaybeError FileError [String], *File)
	rec file acc 
		# (string, file) = freadline file
		# (err,file)	 = ferror file
		| err			 = (Error IOError,file)			
		| string == ""   = (Ok acc, file)
		| otherwise      = rec file [string:acc]
		
readAll :: !*File -> (!MaybeError FileError String, !*File)
readAll file
# (result, file) = readAcc file []
= case result of
	Error e	   = (Error e, file)
	Ok contents = (Ok (concat (reverse contents)), file)
where
	readAcc :: !*File ![String] -> (!MaybeError FileError [String], !*File)
	readAcc file acc
		# (str,file)	= freads file CHUNK_SIZE
		# (err,file)	= ferror file
		| err			= (Error IOError,file)
		# (eof,file)	= fend file
		| eof			= (Ok [str:acc],file)
		| otherwise		= readAcc file [str:acc]			

writeFile :: !String !String !*env -> (!MaybeError FileError (), !*env) | FileSystem env
writeFile filename contents env = 
	withFile filename FWriteData (\file -> (Ok (), fwrites contents file)) env

withFile :: !String Int (*File -> (!MaybeError FileError a,!*File)) !*env 
			-> (!MaybeError FileError a, !*env) | FileSystem env
withFile filename filemode operation env
# (ok,file,env)	= fopen filename filemode env
| not ok			= (Error CannotOpen, env)
# (result,file)		= operation file
| isError result 	= (result, env)
# (ok,env)	 		= fclose file env
| not ok			= (Error CannotClose, env)
= (Ok (fromOk result), env)

fileExists ::  !String !*World -> (!Bool, !*World)
fileExists filename world
# win32FindData = createArray WIN32_FIND_DATA_size_bytes '\0'
# (handle, world) = findFirstFileA (packString filename) win32FindData world
| handle == INVALID_HANDLE_VALUE = (False, world)
# (_,world) = findClose handle world
= (True, world)

deleteFile :: !String !*World -> (!MaybeOSError (), !*World)
deleteFile filename world 
	# (ok, world) = deleteFileA (packString filename) world
	| ok == 0 = getLastOSError world
	= (Ok (), world)

getFileInfo :: !String !*World -> (!MaybeOSError FileInfo, !*World)
getFileInfo filename world
	# win32FindData = createArray WIN32_FIND_DATA_size_bytes '\0'
	# (handle, world) = findFirstFileA (packString filename) win32FindData world
	| handle == INVALID_HANDLE_VALUE = getLastOSError world
	# (res, world) = filetimeToTm (win32FindData % (WIN32_FIND_DATA_ftCreationTime_bytes_offset, WIN32_FIND_DATA_ftCreationTime_bytes_offset + FILETIME_size_bytes)) world
	| isError res = (liftError res, world)
	# creationTime = fromOk res
	# (res, world) = filetimeToTm (win32FindData % (WIN32_FIND_DATA_ftLastWriteTime_bytes_offset, WIN32_FIND_DATA_ftLastWriteTime_bytes_offset + FILETIME_size_bytes)) world
	| isError res = (liftError res, world)
	# lastModifiedTime = fromOk res
	# (res, world) = filetimeToTm (win32FindData % (WIN32_FIND_DATA_ftLastAccessTime_bytes_offset, WIN32_FIND_DATA_ftLastAccessTime_bytes_offset + FILETIME_size_bytes)) world
	| isError res = (liftError res, world)
	# lastAccessedTime = fromOk res
	# info = creationTime
	# info =	{ directory			= toDWORD win32FindData bitand FILE_ATTRIBUTE_DIRECTORY > 0
				, creationTime		= creationTime
				, lastModifiedTime	= lastModifiedTime
				, lastAccessedTime	= lastAccessedTime
				, sizeHigh			= 0
				, sizeLow			= size (win32FindData % (WIN32_FIND_DATA_ftCreationTime_bytes_offset, WIN32_FIND_DATA_ftCreationTime_bytes_offset + FILETIME_size_bytes))
				}
	= (Ok info, world)
where
	toDWORD :: !{#Char} -> DWORD
	toDWORD s = toInt s.[3] << 24 bitor toInt s.[2] << 16 bitor toInt s.[1] << 8 bitor toInt s.[0] //little-endian

filetimeToTm :: !FILETIME !*World -> (!MaybeOSError Tm, !*World)
filetimeToTm filetime world
	# systemtime = createArray SYSTEMTIME_size_bytes '\0'
	# (ok, world) = fileTimeToSystemTime filetime systemtime world
	| ok <> ok = undef
	| not ok = getLastOSError world
	# tm=	{ sec	= toInt systemtime.[SYSTEMTIME_wSecond_offset]		+ (toInt systemtime.[SYSTEMTIME_wSecond_offset + 1]		<< 8)
			, min	= toInt systemtime.[SYSTEMTIME_wMinute_offset]		+ (toInt systemtime.[SYSTEMTIME_wMinute_offset + 1]		<< 8)
			, hour	= toInt systemtime.[SYSTEMTIME_wHour_offset]		+ (toInt systemtime.[SYSTEMTIME_wHour_offset + 1]		<< 8)
			, mday	= toInt systemtime.[SYSTEMTIME_wDay_offset]			+ (toInt systemtime.[SYSTEMTIME_wDay_offset + 1]		<< 8)
			, mon	= toInt systemtime.[SYSTEMTIME_wMonth_offset]		+ (toInt systemtime.[SYSTEMTIME_wMonth_offset + 1]		<< 8) - 1
			, year	= toInt systemtime.[SYSTEMTIME_wYear_offset]		+ (toInt systemtime.[SYSTEMTIME_wYear_offset + 1]		<< 8) - 1900
			, wday	= toInt systemtime.[SYSTEMTIME_wDayOfWeek_offset]	+ (toInt systemtime.[SYSTEMTIME_wDayOfWeek_offset + 1]	<< 8)
			, yday	= -1	//Not implemented
			, isdst	= 0     //Not implemented
			}
	= (Ok tm, world)

moveFile :: !String !String !*World -> (!MaybeOSError (), !*World)
moveFile oldpath newpath world
	# (ok,world)	= moveFileA (packString oldpath) (packString newpath) world
	| ok
		= (Ok (), world)
	| otherwise
		= getLastOSError world
