--------------------------------------------------------------------------------
-- |
-- Module       :   Access.System.IO
-- Copyright    :   (c) Aaron Stevens, 2014
-- License      :   GPL2
--
-- Maintainer   :   bheklilr2@gmail.com
--------------------------------------------------------------------------------

module Access.System.IO
    ( module System.IO

    , HandleWriteAccess(..)
    , HandleReadAccess(..)
    , HandleAccess(..)
    , StdInAccess(..)
    , StdOutAccess(..)
    , StdIOAccess(..)
    , FileReadAccess(..)
    , FileWriteAccess(..)
    , FileAccess(..)
    , TempFileAccess(..)
    , TextEncodingAccess(..)
    ) where


import           Foreign.Ptr (Ptr)
import           System.IO

import           Access.Core

-- | Provides access to 'Handle' write functions
class Access io => HandleWriteAccess io where
    -- | Wraps 'System.IO.hPutChar'
    --
    -- Computation 'hPutChar'' @hdl ch@ writes the character @ch@ to the
    -- file or channel managed by @hdl@.  Characters may be buffered if
    -- buffering is enabled for @hdl@.
    --
    -- This operation may fail with:
    --
    --  * 'System.IO.Error.isFullError' if the device is full; or
    --
    --  * 'System.IO.Error.isPermissionError' if another system resource limit would be exceeded
    hPutChar'           :: Handle -> Char -> io ()
    -- | Wraps 'System.IO.hPutStr'
    --
    -- Computation 'hPutStr'' @hdl s@ writes the string @s@ to the file or
    -- channel managed by @hdl@
    --
    -- This operation may fail with:
    --
    --  * 'System.IO.Error.isFullError' if the device is full; or
    --
    --  * 'System.IO.Error.isPermissionError' if another system resource limit would be exceeded
    hPutStr'            :: Handle -> String -> io ()
    -- | Wraps 'System.IO.hPutStrLn'
    --
    -- The same as 'hPutStr'', but adds a newline character
    hPutStrLn'          :: Handle -> String -> io ()
    -- | Wraps 'System.IO.hPrint'
    --
    -- Computation 'hPrint'' @hdl t@ writes the string representation of @t@
    -- given by the 'Text.Show.shows' function to the file or channel managed
    -- by @hdl@ and appends a newline.
    --
    -- This operation may fail with:
    --
    --  * 'System.IO.Error.isFullError' if the device is full; or
    --
    --  * 'System.IO.Error.isPermissionError' if another system resource limit would be exceeded
    hPrint'             :: Show a => Handle -> a -> io ()
    -- | Wraps 'System.IO.hPutBuf'
    --
    -- 'hPutBuf'' @hdl buf count@ writes @count@ 8-bit bytes from the
    -- buffer @buf@ to the handle @hdl@.  It returns ().
    --
    -- 'hPutBuf'' ignores any text encoding that applies to the 'Handle',
    -- writing the bytes directly to the underlying file or device.
    --
    -- 'hPutBuf'' ignores the prevailing 'TextEncoding' and
    -- 'NewlineMode' on the 'Handle', and writes bytes directly.
    --
    -- This operation may fail with:
    --
    --  * 'ResourceVanished' if the handle is a pipe or socket, and the
    --    reading end is closed.  (If this is a POSIX system, and the program
    --    has not asked to ignore SIGPIPE, then a SIGPIPE may be delivered
    --    instead, whose default action is to terminate the program).
    hPutBuf'            :: Handle -> Ptr a -> Int -> io ()
    -- | Wraps 'System.IO.hPutBufNonBlocking'
    --
    -- 'hGetBufNonBlocking'' @hdl buf count@ reads data from the handle @hdl@
    -- into the buffer @buf@ until either EOF is reached, or
    -- @count@ 8-bit bytes have been read, or there is no more data available
    -- to read immediately.
    --
    -- 'hGetBufNonBlocking'' is identical to 'hGetBuf'', except that it will
    -- never block waiting for data to become available, instead it returns
    -- only whatever data is available.  To wait for data to arrive before
    -- calling 'hGetBufNonBlocking'', use 'hWaitForInput'.
    --
    -- If the handle is a pipe or socket, and the writing end
    -- is closed, 'hGetBufNonBlocking'' will behave as if EOF was reached.
    --
    -- 'hGetBufNonBlocking'' ignores the prevailing 'TextEncoding' and
    -- 'NewlineMode' on the 'Handle', and reads bytes directly.
    --
    -- NOTE: on Windows, this function does not work correctly; it
    -- behaves identically to 'hGetBuf''.
    hPutBufNonBlocking' :: Handle -> Ptr a -> Int -> io Int


-- | Provides access to 'Handle' read functions
class Access io => HandleReadAccess io where
    -- | Wraps 'System.IO.hWaitForInput'
    --
    -- Computation 'hWaitForInput'' @hdl t@
    -- waits until input is available on handle @hdl@.
    -- It returns 'True' as soon as input is available on @hdl@,
    -- or 'False' if no input is available within @t@ milliseconds.  Note that
    -- 'hWaitForInput'' waits until one or more full /characters/ are available,
    -- which means that it needs to do decoding, and hence may fail
    -- with a decoding error.
    --
    -- If @t@ is less than zero, then @hWaitForInput@ waits indefinitely.
    --
    -- This operation may fail with:
    --
    --  * 'System.IO.Error.isEOFError' if the end of file has been reached.
    --
    --  * a decoding error, if the input begins with an invalid byte sequence
    --    in this Handle's encoding.
    --
    -- NOTE for GHC users: unless you use the @-threaded@ flag,
    -- @hWaitForInput t@ where @t >= 0@ will block all other Haskell
    -- threads for the duration of the call.  It behaves like a
    -- @safe@ foreign call in this respect.
    hWaitForInput'      :: Handle -> Int -> io Bool
    -- | Wraps 'System.IO.hGetChar'
    --
    -- Computation 'hGetChar'' @hdl@ reads a character from the file or
    -- channel managed by @hdl@, blocking until a character is available.
    --
    -- This operation may fail with:
    --
    --  * 'System.IO.Error.isEOFError' if the end of file has been reached.
    hGetChar'           :: Handle -> io Char
    -- | Wraps 'System.IO.hGetLine'
    --
    -- Computation 'hGetLine'' @hdl@ reads a line from the file or
    -- channel managed by @hdl@.
    --
    -- This operation may fail with:
    --
    --  * 'System.IO.Error.isEOFError' if the end of file is encountered when reading
    --    the /first/ character of the line.
    --
    -- If 'hGetLine'' encounters end-of-file at any other point while reading
    -- in a line, it is treated as a line terminator and the (partial)
    -- line is returned.
    hGetLine'           :: Handle -> io String
    -- | Wraps 'System.IO.hLookAhead'
    --
    -- Computation 'hLookAhead' returns the next character from the handle
    -- without removing it from the input buffer, blocking until a character
    -- is available.
    --
    -- This operation may fail with:
    --
    --  * 'System.IO.Error.isEOFError' if the end of file has been reached.
    hLookAhead'         :: Handle -> io Char
    -- | Wraps 'System.IO.hGetContents'
    --
    -- Computation 'hGetContents'' @hdl@ returns the list of characters
    -- corresponding to the unread portion of the channel or file managed
    -- by @hdl@, which is put into an intermediate state, /semi-closed/.
    -- In this state, @hdl@ is effectively closed,
    -- but items are read from @hdl@ on demand and accumulated in a special
    -- list returned by 'hGetContents'' @hdl@.
    --
    -- Any operation that fails because a handle is closed,
    -- also fails if a handle is semi-closed.  The only exception is 'hClose''.
    -- A semi-closed handle becomes closed:
    --
    --  * if 'hClose'' is applied to it;
    --
    --  * if an I\/O error occurs when reading an item from the handle;
    --
    --  * or once the entire contents of the handle has been read.
    --
    -- Once a semi-closed handle becomes closed, the contents of the
    -- associated list becomes fixed.  The contents of this final list is
    -- only partially specified: it will contain at least all the items of
    -- the stream that were evaluated prior to the handle becoming closed.
    --
    -- Any I\/O errors encountered while a handle is semi-closed are simply
    -- discarded.
    --
    -- This operation may fail with:
    --
    --  * 'System.IO.Error.isEOFError' if the end of file has been reached.
    hGetContents'       :: Handle -> io String
    -- | Wraps 'System.IO.hGetBuf'
    --
    -- 'hGetBuf' @hdl buf count@ reads data from the handle @hdl@ into the
    -- buffer @buf@ until either EOF is reached or @count@ 8-bit bytes have been
    -- read.  It returns the number of bytes actually read.  This may be zero if
    -- EOF was reached before any data was read (or if @count@ is zero).
    --
    -- 'hGetBuf' never raises an EOF exception, instead it returns a value
    -- smaller than @count@.
    --
    -- If the handle is a pipe or socket, and the writing end
    -- is closed, 'hGetBuf' will behave as if EOF was reached.
    --
    -- 'hGetBuf' ignores the prevailing 'TextEncoding' and 'NewlineMode'
    -- on the 'Handle', and reads bytes directly.
    hGetBuf'            :: Handle -> Ptr a -> Int -> io Int
    -- | Wraps 'System.IO.hGetBufSome'
    --
    --'hGetBufSome' @hdl buf count@ reads data from the handle @hdl@
    -- into the buffer @buf@.  If there is any data available to read,
    -- then 'hGetBufSome' returns it immediately; it only blocks if there
    -- is no data to be read.
    --
    -- It returns the number of bytes actually read.  This may be zero if
    -- EOF was reached before any data was read (or if @count@ is zero).
    --
    -- 'hGetBufSome' never raises an EOF exception, instead it returns a value
    -- smaller than @count@.
    --
    -- If the handle is a pipe or socket, and the writing end
    -- is closed, 'hGetBufSome' will behave as if EOF was reached.
    --
    -- 'hGetBufSome' ignores the prevailing 'TextEncoding' and 'NewlineMode'
    -- on the 'Handle', and reads bytes directly.
    hGetBufSome'        :: Handle -> Ptr a -> Int -> io Int
    -- | Wraps 'System.IO.hGetBufNonBlocking'
    --
    -- 'hGetBufNonBlocking' @hdl buf count@ reads data from the handle @hdl@
    -- into the buffer @buf@ until either EOF is reached, or
    -- @count@ 8-bit bytes have been read, or there is no more data available
    -- to read immediately.
    --
    -- 'hGetBufNonBlocking' is identical to 'hGetBuf', except that it will
    -- never block waiting for data to become available, instead it returns
    -- only whatever data is available.  To wait for data to arrive before
    -- calling 'hGetBufNonBlocking', use 'hWaitForInput'.
    --
    -- If the handle is a pipe or socket, and the writing end
    -- is closed, 'hGetBufNonBlocking' will behave as if EOF was reached.
    --
    -- 'hGetBufNonBlocking' ignores the prevailing 'TextEncoding' and
    -- 'NewlineMode' on the 'Handle', and reads bytes directly.
    --
    -- NOTE: on Windows, this function does not work correctly; it
    -- behaves identically to 'hGetBuf'.
    hGetBufNonBlocking' :: Handle -> Ptr a -> Int -> io Int


-- | Combines the 'HandleWriteAccess' and 'HandleReadAccess' classes and adds
-- additional miscellaneous functions for 'Handle' manipulation
class (HandleWriteAccess io, HandleReadAccess io) => HandleAccess io where
    -- | Wraps 'System.IO.hClose'
    --
    -- Computation 'hClose'' @hdl@ makes handle @hdl@ closed.  Before the
    -- computation finishes, if @hdl@ is writable its buffer is flushed as
    -- for 'hFlush'.
    -- Performing 'hClose'' on a handle that has already been closed has no
    -- effect; doing so is not an error.  All other operations on a closed
    -- handle will fail.  If 'hClose'' fails for any reason, any further
    -- operations (apart from 'hClose'') on the handle will still fail as if
    -- @hdl@ had been successfully closed.
    hClose'             :: Handle -> io ()
    -- | Wraps 'System.IO.hFileSize'
    --
    -- For a handle @hdl@ which attached to a physical file, 'hFileSize' @hdl@
    -- returns the size of that file in 8-bit bytes.
    hFileSize'          :: Handle -> io Integer
    -- | Wraps 'System.IO.hSetFileSize'
    --
    -- 'hSetFileSize'' @hdl@ @size@ truncates the physical file with handle
    -- @hdl@ to @size@ bytes.
    hSetFileSize'       :: Handle -> Integer -> io ()
    -- | Wraps 'System.IO.hIsEOF'
    --
    -- For a readable handle @hdl@, 'hIsEOF'' @hdl@ returns
    -- 'True' if no further input can be taken from @hdl@ or for a
    -- physical file, if the current I\/O position is equal to the length of
    -- the file.  Otherwise, it returns 'False'.
    --
    -- NOTE: 'hIsEOF'' may block, because it has to attempt to read from
    -- the stream to determine whether there is any more data to be read.
    hIsEOF'             :: Handle -> io Bool
    -- | Wraps 'System.IO.hSetBuffering'
    --
    -- Computation 'hSetBuffering'' @hdl mode@ sets the mode of buffering for
    -- handle @hdl@ on subsequent reads and writes.
    --
    -- If the buffer mode is changed from 'BlockBuffering' or
    -- 'LineBuffering' to 'NoBuffering', then
    --
    --  * if @hdl@ is writable, the buffer is flushed as for 'hFlush'';
    --
    --  * if @hdl@ is not writable, the contents of the buffer is discarded.
    --
    -- This operation may fail with:
    --
    --  * 'System.IO.Error.isPermissionError' if the handle has already been
    --    used for reading or writing and the implementation does not allow the
    --    buffering mode to be changed.
    hSetBuffering'      :: Handle -> BufferMode -> io ()
    -- | Wraps 'System.IO.hGetBuffering'
    --
    -- Computation 'hGetBuffering'' @hdl@ returns the current buffering mode
    hGetBuffering'      :: Handle -> io BufferMode
    -- | Wraps 'System.IO.hFlush'
    --
    -- The action 'hFlush'' @hdl@ causes any items buffered for output
    -- in handle @hdl@ to be sent immediately to the operating system.
    --
    -- This operation may fail with:
    --
    --  * 'System.IO.Error.isFullError' if the device is full;
    --
    --  * 'System.IO.Error.isPermissionError' if a system resource limit would
    --    be exceeded.  It is unspecified whether the characters in the buffer
    --    are discarded or retained under these circumstances.
    hFlush'             :: Handle -> io ()
    -- | Wraps 'System.IO.hGetPosn'
    --
    -- Computation 'hGetPosn'' @hdl@ returns the current I\/O position of
    -- @hdl@ as a value of the abstract type 'HandlePosn'.
    hGetPosn'           :: Handle -> io HandlePosn
    -- | Wraps 'System.IO.hSetPosn'
    --
    -- If a call to 'hGetPosn'' @hdl@ returns a position @p@,
    -- then computation 'hSetPosn'' @p@ sets the position of @hdl@
    -- to the position it held at the time of the call to 'hGetPosn''.
    --
    -- This operation may fail with:
    --
    --  * 'System.IO.Error.isPermissionError' if a system resource limit would
    --    be exceeded.
    hSetPosn'           :: HandlePosn -> io ()
    -- | Wraps 'System.IO.hSeek'
    --
    -- Computation 'hSeek'' @hdl mode i@ sets the position of handle
    -- @hdl@ depending on @mode@.
    -- The offset @i@ is given in terms of 8-bit bytes.
    --
    -- If @hdl@ is block- or line-buffered, then seeking to a position which is
    -- not in the current buffer will first cause any items in the output buffer
    -- to be written to the device, and then cause the input buffer to be
    -- discarded. Some handles may not be seekable (see 'hIsSeekable''), or only
    -- support a subset of the possible positioning operations (for instance, it
    -- may only be possible to seek to the end of a tape, or to a positive
    -- offset from the beginning or current position).
    -- It is not possible to set a negative I\/O position, or for
    -- a physical file, an I\/O position beyond the current end-of-file.
    --
    -- This operation may fail with:
    --
    --  * 'System.IO.Error.isIllegalOperationError' if the Handle is not
    --    seekable, or does not support the requested seek mode.
    --
    --  * 'System.IO.Error.isPermissionError' if a system resource limit would
    --    be exceeded.
    hSeek'              :: Handle -> SeekMode -> Integer -> io ()
    -- | Wraps 'System.IO.hTell'
    --
    -- Computation 'hTell'' @hdl@ returns the current position of the
    -- handle @hdl@, as the number of bytes from the beginning of
    -- the file.  The value returned may be subsequently passed to
    -- 'hSeek' to reposition the handle to the current position.
    --
    -- This operation may fail with:
    --
    --  * 'System.IO.Error.isIllegalOperationError' if the Handle is not seekable.
    hTell'              :: Handle -> io Integer
    -- | Wraps 'System.IO.hIsOpen'
    hIsOpen'            :: Handle -> io Bool
    -- | Wraps 'System.IO.hIsClosed'
    hIsClosed'          :: Handle -> io Bool
    -- | Wraps 'System.IO.hIsReadable'
    hIsReadable'        :: Handle -> io Bool
    -- | Wraps 'System.IO.hIsWritable'
    hIsWritable'        :: Handle -> io Bool
    -- | Wraps 'System.IO.hIsSeekable'
    hIsSeekable'        :: Handle -> io Bool
    -- | Wraps 'System.IO.hIsTerminalDevice'
    --
    -- Is the handle connected to a terminal?
    hIsTerminalDevice'  :: Handle -> io Bool
    -- | Wraps 'System.IO.hSetEcho'
    --
    -- Set the echoing status of a handle connected to a terminal.
    hSetEcho'           :: Handle -> Bool -> io ()
    -- | Wraps 'System.IO.hGetEcho'
    --
    -- Get the echoing status of a handle connected to a terminal.
    hGetEcho'           :: Handle -> io Bool
    -- | Wraps 'System.IO.hShow'
    --
    -- 'hShow'' is in the 'IO' monad, and gives more comprehensive output
    -- than the (pure) instance of 'Show' for 'Handle'.
    hShow'              :: Handle -> io String
    -- | Wraps 'System.IO.hReady'
    --
    --  Computation 'hReady'' @hdl@ indicates whether at least one item is
    -- available for input from handle @hdl@.
    --
    -- This operation may fail with:
    --
    --  * 'System.IO.Error.isEOFError' if the end of file has been reached.
    hReady'             :: Handle -> io Bool
    -- | Wraps 'System.IO.hSetEncoding'
    --
    -- The action 'hSetEncoding'' @hdl@ @encoding@ changes the text encoding
    -- for the handle @hdl@ to @encoding@.  The default encoding when a 'Handle'
    -- is created is 'localeEncoding', namely the default encoding for the
    -- current locale.
    --
    -- To create a 'Handle' with no encoding at all, use 'openBinaryFile''.  To
    -- stop further encoding or decoding on an existing 'Handle', use
    -- 'hSetBinaryMode''.
    --
    -- 'hSetEncoding'' may need to flush buffered data in order to change
    -- the encoding.
    hSetEncoding'       :: Handle -> TextEncoding -> io ()
    -- | Wraps 'System.IO.hGetEncoding'
    --
    -- Return the current 'TextEncoding' for the specified 'Handle', or
    -- 'Nothing' if the 'Handle' is in binary mode.
    --
    -- Note that the 'TextEncoding' remembers nothing about the state of
    -- the encoder/decoder in use on this 'Handle'.  For example, if the
    -- encoding in use is UTF-16, then using 'hGetEncoding'' and
    -- 'hSetEncoding'' to save and restore the encoding may result in an
    -- extra byte-order-mark being written to the file.
    hGetEncoding'       :: Handle -> io (Maybe TextEncoding)
    -- | Wraps 'System.IO.hSetNewlineMode'
    --
    -- Set the 'NewlineMode' on the specified 'Handle'.  All buffered data is
    -- flushed first.
    hSetNewlineMode'    :: Handle -> NewlineMode -> io ()


-- | Provides access to functions to read from 'stdin'
class Access io => StdInAccess io where
    -- | Wraps 'System.IO.getChar'
    --
    -- Read a character from the standard input device
    -- (same as 'hGetChar'' 'stdin').
    getChar'     :: io Char
    -- | Wraps 'System.IO.getLine'
    --
    -- Read a line from the standard input device (same as 'hGetLine'' 'stdin').
    getLine'     :: io String
    -- | Wraps 'System.IO.getContents'
    --
    -- The 'getContents' operation returns all user input as a single string,
    -- which is read lazily as it is needed (same as 'hGetContents'' 'stdin').
    getContents' :: io String
    -- | Wraps 'System.IO.readLn'
    --
    -- The 'readLn'' function combines 'getLine'' and 'readIO'.
    readLn'      :: Read a => io a
    -- | Wraps 'System.IO.interact'
    --
    -- The 'interact'' function takes a function of type @String->String@
    -- as its argument.  The entire input from the standard input device is
    -- passed to this function as its argument, and the resulting string is
    -- output on the standard output device.
    interact'    :: (String -> String) -> io ()


-- | Provides access to functions to write to 'stdout'
class Access io => StdOutAccess io where
    -- | Wraps 'System.IO.putChar'
    --
    -- Write a character to the standard output device
    -- (same as 'hPutChar'' 'stdout').
    putChar'  :: Char -> io ()
    -- | Wraps 'System.IO.putStr'
    --
    -- Write a string to the standard output device
    -- (same as 'hPutStr'' 'stdout').
    putStr'   :: String -> io ()
    -- | Wraps 'System.IO.putStrLn'
    --
    -- The same as 'putStr'', but adds a newline character.
    putStrLn' :: String -> io ()
    -- | Wraps 'System.IO.print'
    --
    -- The 'print'' function outputs a value of any printable type to the
    -- standard output device.
    -- Printable types are those that are instances of class 'Show'; 'print''
    -- converts values to strings for output using the 'show' operation and
    -- adds a newline.
    --
    -- For example, a program to print' the first 20 integers and their
    -- powers of 2 could be written as:
    --
    -- > main = print' ([(n, 2^n) | n <- [0..19]])
    print'    :: Show a => a -> io ()


-- | Combines the 'StdInAccess' and 'StdOutAccess' into a single class
class (StdInAccess io, StdOutAccess io) => StdIOAccess io where
    -- | Wraps 'System.IO.isEOF'
    --
    -- For a readable handle @hdl@, 'hIsEOF'' @hdl@ returns
    -- 'True' if no further input can be taken from @hdl@ or for a
    -- physical file, if the current I\/O position is equal to the length of
    -- the file.  Otherwise, it returns 'False'.
    --
    -- NOTE: 'hIsEOF'' may block, because it has to attempt to read from
    -- the stream to determine whether there is any more data to be read.
    isEOF' :: io Bool


-- | Provides the function 'readFile'' for reading the contents of a file
class FileReadAccess io where
    -- | Wraps 'System.IO.readFile'
    --
    -- The 'readFile'' function reads a file and
    -- returns the contents of the file as a string.
    -- The file is read lazily, on demand, as with 'getContents''.
    readFile' :: FilePath -> io String


-- | Provides functions for writing to files
class Access io => FileWriteAccess io where
    -- | Wraps 'System.IO.writeFile'
    --
    -- The computation 'writeFile'' @file str@ function writes the string @str@,
    -- to the file @file@.
    writeFile'  :: FilePath -> String -> io ()
    -- | Wraps 'System.IO.appendFile'
    --
    -- The computation 'appendFile'' @file str@ function appends the string
    -- @str@, to the file @file@.
    --
    -- Note that 'writeFile'' and 'appendFile'' write a literal string
    -- to a file.  To write a value of any printable type, as with 'print'',
    -- use the 'show' function to convert the value to a string first.
    --
    -- > main = appendFile' "squares" (show [(x,x*x) | x <- [0,0.1..2]])
    appendFile' :: FilePath -> String -> io ()


-- | Combines 'HandleAccess', 'FileReadAccess', and 'FileWriteAccess' for
-- manipulating files (this does allow for general 'Handle' access and should be
-- considered unsafe)
class (HandleAccess io, FileReadAccess io, FileWriteAccess io) => FileAccess io where
    -- | Wraps 'System.IO.withFile'
    --
    -- @'withFile'' name mode act@ opens a file using 'openFile'' and passes
    -- the resulting handle to the computation @act@.  The handle will be
    -- closed on exit from 'withFile'', whether by normal termination or by
    -- raising an exception.  If closing the handle raises an exception, then
    -- this exception will be raised by 'withFile'' rather than any exception
    -- raised by 'act'.
    withFile'       :: FilePath -> IOMode -> (Handle -> io r) -> io r
    -- | Wraps 'System.IO.openFile'
    --
    -- Computation 'openFile'' @file mode@ allocates and returns a new, open
    -- handle to manage the file @file@.  It manages input if @mode@
    -- is 'ReadMode', output if @mode@ is 'WriteMode' or 'AppendMode',
    -- and both input and output if mode is 'ReadWriteMode'.
    --
    -- If the file does not exist and it is opened for output, it should be
    -- created as a new file.  If @mode@ is 'WriteMode' and the file
    -- already exists, then it should be truncated to zero length.
    -- Some operating systems delete empty files, so there is no guarantee
    -- that the file will exist following an 'openFile'' with @mode@
    -- 'WriteMode' unless it is subsequently written to successfully.
    -- The handle is positioned at the end of the file if @mode@ is
    -- 'AppendMode', and otherwise at the beginning (in which case its
    -- internal position is 0).
    -- The initial buffer mode is implementation-dependent.
    --
    -- This operation may fail with:
    --
    --  * 'System.IO.Error.isAlreadyInUseError' if the file is already open and
    --    cannot be reopened;
    --
    --  * 'System.IO.Error.isDoesNotExistError' if the file does not exist; or
    --
    --  * 'System.IO.Error.isPermissionError' if the user does not have
    --    permission to open the file.
    --
    -- Note: if you will be working with files containing binary data, you'll want to
    -- be using 'openBinaryFile''.
    openFile'       :: FilePath -> IOMode -> io Handle
    -- | Wraps 'System.IO.withBinaryFile'
    --
    -- @'withBinaryFile'' name mode act@ opens a file using 'openBinaryFile''
    -- and passes the resulting handle to the computation @act@.  The handle
    -- will be closed on exit from 'withBinaryFile'', whether by normal
    -- termination or by raising an exception.
    withBinaryFile' :: FilePath -> IOMode -> (Handle -> io r) -> io r
    -- | Wraps 'System.IO.openBinaryFile'
    --
    -- Like 'openFile'', but open the file in binary mode.
    -- On Windows, reading a file in text mode (which is the default)
    -- will translate CRLF to LF, and writing will translate LF to CRLF.
    -- This is usually what you want with text files.  With binary files
    -- this is undesirable; also, as usual under Microsoft operating systems,
    -- text mode treats control-Z as EOF.  Binary mode turns off all special
    -- treatment of end-of-line and end-of-file characters.
    -- (See also 'hSetBinaryMode''.)
    openBinaryFile' :: FilePath -> IOMode -> io Handle


-- | Provides access to functions for opening temporary file 'Handle's
class Access io => TempFileAccess io where
    -- | Wraps 'System.IO.openTempFile'
    --
    -- The function creates a temporary file in 'ReadWrite' mode.
    -- The created file isn\'t deleted automatically, so you need to delete it
    -- manually.
    --
    -- The file is creates with permissions such that only the current
    -- user can read\/write it.
    --
    -- With some exceptions (see below), the file will be created securely
    -- in the sense that an attacker should not be able to cause
    -- 'openTempFile'' to overwrite another file on the filesystem using your
    -- credentials, by putting symbolic links (on Unix) in the place where
    -- the temporary file is to be created.  On Unix the @O_CREAT@ and
    -- @O_EXCL@ flags are used to prevent this attack, but note that
    -- @O_EXCL@ is sometimes not supported on NFS filesystems, so if you
    -- rely on this behaviour it is best to use local filesystems only.
    openTempFile'       :: FilePath -> String -> io (FilePath, Handle)
    -- | Wraps 'System.IO.openBinaryTempFile'
    --
    -- Like 'openTempFile'', but opens the file in binary mode.
    -- See 'openBinaryFile'' for more comments.
    openBinaryTempFile' :: FilePath -> String -> io (FilePath, Handle)
    -- | Wraps 'System.IO.openTempFileWithDefaultPermissions'
    --
    -- Like 'openTempFile'', but uses the default file permissions
    openTempFileWithDefaultPermissions'         :: FilePath -> String -> io (FilePath, Handle)
    -- | Wraps 'System.IO.openBinaryTempFileWithDefaultPermissions'
    --
    -- Like 'openBinaryTempFile'', but uses the default file permissions
    openBinaryTempFileWithDefaultPermissions'   :: FilePath -> String -> io (FilePath, Handle)


-- | Provides access to 'mkTextEncoding''
class Access io => TextEncodingAccess io where
    -- | Wraps 'System.IO.mkTextEncoding'
    --
    -- Look up the named Unicode encoding.  May fail with
    --
    --  * 'System.IO.Error.isDoesNotExistError' if the encoding is unknown
    --
    -- The set of known encodings is system-dependent, but includes at least:
    --
    --  * @UTF-8@
    --
    --  * @UTF-16@, @UTF-16BE@, @UTF-16LE@
    --
    --  * @UTF-32@, @UTF-32BE@, @UTF-32LE@
    --
    -- On systems using GNU iconv (e.g. Linux), there is additional
    -- notation for specifying how illegal characters are handled:
    --
    --  * a suffix of @\/\/IGNORE@, e.g. @UTF-8\/\/IGNORE@, will cause
    --    all illegal sequences on input to be ignored, and on output
    --    will drop all code points that have no representation in the
    --    target encoding.
    --
    --  * a suffix of @\/\/TRANSLIT@ will choose a replacement character
    --    for illegal sequences or code points.
    --
    -- On Windows, you can access supported code pages with the prefix
    -- @CP@; for example, @\"CP1250\"@.
    mkTextEncoding' :: String -> io TextEncoding


instance HandleWriteAccess IO where
    hPutChar'           = hPutChar
    hPutStr'            = hPutStr
    hPutStrLn'          = hPutStrLn
    hPrint'             = hPrint
    hPutBuf'            = hPutBuf
    hPutBufNonBlocking' = hPutBufNonBlocking

instance HandleReadAccess IO where
    hWaitForInput'      = hWaitForInput
    hGetChar'           = hGetChar
    hGetLine'           = hGetLine
    hLookAhead'         = hLookAhead
    hGetContents'       = hGetContents
    hGetBuf'            = hGetBuf
    hGetBufSome'        = hGetBufSome
    hGetBufNonBlocking' = hGetBufNonBlocking

instance HandleAccess IO where
    hClose'             = hClose
    hFileSize'          = hFileSize
    hSetFileSize'       = hSetFileSize
    hIsEOF'             = hIsEOF
    hSetBuffering'      = hSetBuffering
    hGetBuffering'      = hGetBuffering
    hFlush'             = hFlush
    hGetPosn'           = hGetPosn
    hSetPosn'           = hSetPosn
    hSeek'              = hSeek
    hTell'              = hTell
    hIsOpen'            = hIsOpen
    hIsClosed'          = hIsClosed
    hIsReadable'        = hIsReadable
    hIsWritable'        = hIsWritable
    hIsSeekable'        = hIsSeekable
    hIsTerminalDevice'  = hIsTerminalDevice
    hSetEcho'           = hSetEcho
    hGetEcho'           = hGetEcho
    hShow'              = hShow
    hReady'             = hReady
    hSetEncoding'       = hSetEncoding
    hGetEncoding'       = hGetEncoding
    hSetNewlineMode'    = hSetNewlineMode

instance StdInAccess IO where
    interact'    = interact
    getChar'     = getChar
    getLine'     = getLine
    getContents' = getContents
    readLn'      = readLn

instance StdOutAccess IO where
    putChar'     = putChar
    putStr'      = putStr
    putStrLn'    = putStrLn
    print'       = print

instance StdIOAccess IO where
    isEOF'       = isEOF

instance FileReadAccess IO where
    readFile'       = readFile

instance FileWriteAccess IO where
    writeFile'      = writeFile
    appendFile'     = appendFile

instance FileAccess IO where
    withFile'       = withFile
    openFile'       = openFile
    withBinaryFile' = withBinaryFile
    openBinaryFile' = openBinaryFile

instance TempFileAccess IO where
    openTempFile'                             = openTempFile
    openBinaryTempFile'                       = openBinaryTempFile
    openTempFileWithDefaultPermissions'       = openTempFileWithDefaultPermissions
    openBinaryTempFileWithDefaultPermissions' = openBinaryTempFileWithDefaultPermissions

instance TextEncodingAccess IO where
    mkTextEncoding' = mkTextEncoding