{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances #-}

{- |
    Module      :  System.IO.Classes
    Copyright   :  (c) Andrey Mulik 2020
    License     :  BSD-style
    Maintainer  :  work.a.mulik@gmail.com
    Portability :  portable
    
    @System.IO.Classes@ provides generalized path and file classes.
-}
module System.IO.Classes
(
  -- * Export
  module System.IO.Handle,
  module System.IO.Error,
  
  -- * Generalized path
  IsFilePath (..),
  
  -- * Generalized file
  IsFile (..), getContents, putContents, withFile, readFile, writeFile, appendFile,
  
  -- ** Text IO
  IsTextFile (..), getLine, putStr, putStrLn, gets, puts
)
where

import Prelude ()
import SDP.SafePrelude

import Data.FilePath

import qualified System.IO as IO
import System.IO.Handle
import System.IO.Error

import Control.Exception

default ()

--------------------------------------------------------------------------------

-- | 'IsFilePath' is type class of file path (data destination) representations.
class IsFilePath path
  where
    {- |
      @hOpen path mode@ open new text handle with block buffering and given
      @mode@ using @path@ identifier.
      
      This operation may fail with:
      
      * @isAlreadyInUseError@ if the file is already open and cannot be reopened
      * @isDoesNotExistError@ if the file does not exist
      * @isPermissionError@ if the user doesn't have permission to open the file
    -}
    hOpen :: (MonadIO io) => IOMode -> path -> io Handle
    hOpen IOMode
mode path
path = IOMode -> path -> BufferMode -> Bool -> io Handle
forall path (io :: * -> *).
(IsFilePath path, MonadIO io) =>
IOMode -> path -> BufferMode -> Bool -> io Handle
hOpenWith IOMode
mode path
path (Maybe Int -> BufferMode
BlockBuffering Maybe Int
forall a. Maybe a
Nothing) Bool
False
    
    {- |
      @hOpenWith path mode buf bin@ open new handle with buffering mode @buf@,
      binary mode @bin@ and 'IOMode' @mode@ using @path@ identifier.
      
      This operation may fail with:

      * 'isAlreadyInUseError' if the file is already open and cannot be reopened
      * 'isDoesNotExistError' if the file does not exist
      * 'isPermissionError' if the user doesn't have permission to open the file
    -}
    hOpenWith :: (MonadIO io) => IOMode -> path -> BufferMode -> Bool -> io Handle
    
    -- | Open new temp file 'Handle' by given source identifier.
    hOpenTemp :: (MonadIO io) => path -> io (path, Handle)

--------------------------------------------------------------------------------

{- |
  'IsFile' is a type class that represents the contents of a file.
  
  'IsFile' provides only basic read and write operations. It doesn't allow, for
  example, changing the 'IOMode' or handle the concatenation of the file
  contents with the appendable information.
-}
class IsFile file
  where
    {- |
      @hGetContents hdl@ reads the contents of the file. Reading may be both
      lazily (in this case @hdl@ should be semi-closed until the file end is
      reached) or strictly. After reading the file, @hdl@ should be closed.
      
      Once a semi-closed handle becomes closed, the contents becomes fixed.
      The contents of this final value is only partially specified: it will
      contain at least all the items of the stream that were evaluated prior to
      the handle becoming closed.
      
      This operation may fail with:
      
      * isEOFError if the end of file has been reached.
    -}
    hGetContents :: (MonadIO io) => Handle -> io file
    
    {- |
      @hPutContents hdl file@ writes the @file@ contents to @hdl@.
      
      This operation may fail with:
      
      * 'isFullError' if the device is full; or
      * 'isPermissionError' if another system resource limit would be exceeded.
      
      If 'hPutContents' changes the recording mode (buffering, binary/text),
      it should return the original Handle settings.
    -}
    hPutContents :: (MonadIO io) => Handle -> file -> io ()

-- | Just 'hGetContents' 'stdin'.
getContents :: (MonadIO io, IsFile file) => io file
getContents :: io file
getContents =  Handle -> io file
forall file (io :: * -> *).
(IsFile file, MonadIO io) =>
Handle -> io file
hGetContents Handle
stdin

-- | Just 'hPutContents' 'stdout'.
putContents :: (MonadIO io, IsFile file) => file -> io ()
putContents :: file -> io ()
putContents =  Handle -> file -> io ()
forall file (io :: * -> *).
(IsFile file, MonadIO io) =>
Handle -> file -> io ()
hPutContents Handle
stdout

--------------------------------------------------------------------------------

{- |
  @withFile path mode act@ opens a file using 'hOpen' 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 :: (MonadIO io, IsFilePath path) => path -> IOMode -> (Handle -> IO a) -> io a
withFile :: path -> IOMode -> (Handle -> IO a) -> io a
withFile path
path IOMode
mode = IO a -> io a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO a -> io a)
-> ((Handle -> IO a) -> IO a) -> (Handle -> IO a) -> io a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. IO Handle -> (Handle -> IO ()) -> (Handle -> IO a) -> IO a
forall a b c. IO a -> (a -> IO b) -> (a -> IO c) -> IO c
bracket (IOMode -> path -> IO Handle
forall path (io :: * -> *).
(IsFilePath path, MonadIO io) =>
IOMode -> path -> io Handle
hOpen IOMode
mode path
path) Handle -> IO ()
forall (io :: * -> *). MonadIO io => Handle -> io ()
hClose

{- |
  The 'readFile' function reads a file and returns its contents.
  
  The specifics of reading a file (laziness/strictness, possible exceptions)
  depend on the type of resource and the 'hGetContents' implementation.
-}
readFile :: (MonadIO io, IsFilePath p, IsFile f) => p -> io f
readFile :: p -> io f
readFile =  IOMode -> p -> io Handle
forall path (io :: * -> *).
(IsFilePath path, MonadIO io) =>
IOMode -> path -> io Handle
hOpen IOMode
ReadMode (p -> io Handle) -> (Handle -> io f) -> p -> io f
forall (m :: * -> *) a b c.
Monad m =>
(a -> m b) -> (b -> m c) -> a -> m c
>=> Handle -> io f
forall file (io :: * -> *).
(IsFile file, MonadIO io) =>
Handle -> io file
hGetContents

-- | @writeFile path file@ function writes the @file@ value, to the @path@.
writeFile :: (MonadIO io, IsFilePath p, IsFile f) => p -> f -> io ()
writeFile :: p -> f -> io ()
writeFile p
path f
file = p -> IOMode -> (Handle -> IO ()) -> io ()
forall (io :: * -> *) path a.
(MonadIO io, IsFilePath path) =>
path -> IOMode -> (Handle -> IO a) -> io a
withFile p
path IOMode
WriteMode (Handle -> f -> IO ()
forall file (io :: * -> *).
(IsFile file, MonadIO io) =>
Handle -> file -> io ()
`hPutContents` f
file)

-- | @appendFile path file@ appends the @file@ value, to the @path@.
appendFile :: (MonadIO io, IsFilePath p, IsFile f) => p -> f -> io ()
appendFile :: p -> f -> io ()
appendFile p
path f
file = p -> IOMode -> (Handle -> IO ()) -> io ()
forall (io :: * -> *) path a.
(MonadIO io, IsFilePath path) =>
path -> IOMode -> (Handle -> IO a) -> io a
withFile p
path IOMode
AppendMode (Handle -> f -> IO ()
forall file (io :: * -> *).
(IsFile file, MonadIO io) =>
Handle -> file -> io ()
`hPutContents` f
file)

--------------------------------------------------------------------------------

-- | 'IsTextFile' is a type class of text file representations.
class (IsFile text) => IsTextFile text
  where
    -- | Read one text line from handle.
    hGetLine :: (MonadIO io) => Handle -> io text
    
    -- | Put text to handle.
    hPutStr   :: (MonadIO io) => Handle -> text -> io ()
    
    -- | Put text line to handle.
    hPutStrLn :: (MonadIO io) => Handle -> text -> io ()
    hPutStrLn Handle
hdl text
text = do Handle -> text -> io ()
forall text (io :: * -> *).
(IsTextFile text, MonadIO io) =>
Handle -> text -> io ()
hPutStr Handle
hdl text
text; Handle -> Char -> io ()
forall (io :: * -> *). MonadIO io => Handle -> Char -> io ()
hPutChar Handle
hdl Char
'\n'

--------------------------------------------------------------------------------

-- | Same as @hGetLine stdin@.
getLine :: (MonadIO io, IsTextFile text) => io text
getLine :: io text
getLine =  Handle -> io text
forall text (io :: * -> *).
(IsTextFile text, MonadIO io) =>
Handle -> io text
hGetLine Handle
stdin

-- | Same as @hPutStr stdout@.
putStr :: (MonadIO io, IsTextFile text) => text -> io ()
putStr :: text -> io ()
putStr =  Handle -> text -> io ()
forall text (io :: * -> *).
(IsTextFile text, MonadIO io) =>
Handle -> text -> io ()
hPutStr Handle
stdout

-- | Same as @hPutStrLn stdout@.
putStrLn :: (MonadIO io, IsTextFile text) => text -> io ()
putStrLn :: text -> io ()
putStrLn =  Handle -> text -> io ()
forall text (io :: * -> *).
(IsTextFile text, MonadIO io) =>
Handle -> text -> io ()
hPutStrLn Handle
stdout

-- | Short version of 'getLine'.
gets :: (MonadIO io, IsTextFile text) => io text
gets :: io text
gets =  io text
forall (io :: * -> *) text.
(MonadIO io, IsTextFile text) =>
io text
getLine

-- | Short version of 'putStrLn'.
puts :: (MonadIO io, IsTextFile text) => text -> io ()
puts :: text -> io ()
puts =  text -> io ()
forall (io :: * -> *) text.
(MonadIO io, IsTextFile text) =>
text -> io ()
putStrLn

--------------------------------------------------------------------------------

instance IsFilePath FilePath
  where
    hOpen :: IOMode -> FilePath -> io Handle
hOpen = IO Handle -> io Handle
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO Handle -> io Handle)
-> (IOMode -> FilePath -> IO Handle)
-> IOMode
-> FilePath
-> io Handle
forall c d a b. (c -> d) -> (a -> b -> c) -> a -> b -> d
... (FilePath -> IOMode -> IO Handle)
-> IOMode -> FilePath -> IO Handle
forall a b c. (a -> b -> c) -> b -> a -> c
flip FilePath -> IOMode -> IO Handle
IO.openFile
    
    hOpenWith :: IOMode -> FilePath -> BufferMode -> Bool -> io Handle
hOpenWith IOMode
mode FilePath
path BufferMode
buf Bool
bin = do
      Handle
h <- IOMode -> FilePath -> io Handle
forall path (io :: * -> *).
(IsFilePath path, MonadIO io) =>
IOMode -> path -> io Handle
hOpen IOMode
mode FilePath
path
      Handle -> BufferMode -> io ()
forall (io :: * -> *). MonadIO io => Handle -> BufferMode -> io ()
hSetBuffering Handle
h BufferMode
buf
      Handle -> Bool -> io ()
forall (io :: * -> *). MonadIO io => Handle -> Bool -> io ()
hSetBinaryMode Handle
h Bool
bin
      Handle -> io Handle
forall (m :: * -> *) a. Monad m => a -> m a
return Handle
h
    
    hOpenTemp :: FilePath -> io (FilePath, Handle)
hOpenTemp (FilePath
dir :/ FilePath
name) = IO (FilePath, Handle) -> io (FilePath, Handle)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (FilePath, Handle) -> io (FilePath, Handle))
-> IO (FilePath, Handle) -> io (FilePath, Handle)
forall a b. (a -> b) -> a -> b
$ (FilePath -> FilePath) -> (FilePath, Handle) -> (FilePath, Handle)
forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first (FilePath
dir FilePath -> FilePath -> FilePath
:/) ((FilePath, Handle) -> (FilePath, Handle))
-> IO (FilePath, Handle) -> IO (FilePath, Handle)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> FilePath -> FilePath -> IO (FilePath, Handle)
IO.openTempFile FilePath
dir FilePath
name

instance IsFile String
  where
    hGetContents :: Handle -> io FilePath
hGetContents = IO FilePath -> io FilePath
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO  (IO FilePath -> io FilePath)
-> (Handle -> IO FilePath) -> Handle -> io FilePath
forall b c a. (b -> c) -> (a -> b) -> a -> c
.  Handle -> IO FilePath
IO.hGetContents
    hPutContents :: Handle -> FilePath -> io ()
hPutContents = IO () -> io ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> io ())
-> (Handle -> FilePath -> IO ()) -> Handle -> FilePath -> io ()
forall c d a b. (c -> d) -> (a -> b -> c) -> a -> b -> d
... Handle -> FilePath -> IO ()
IO.hPutStr

instance IsTextFile String
  where
    hPutStrLn :: Handle -> FilePath -> io ()
hPutStrLn = IO () -> io ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> io ())
-> (Handle -> FilePath -> IO ()) -> Handle -> FilePath -> io ()
forall c d a b. (c -> d) -> (a -> b -> c) -> a -> b -> d
... Handle -> FilePath -> IO ()
IO.hPutStrLn
    hGetLine :: Handle -> io FilePath
hGetLine  = IO FilePath -> io FilePath
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO  (IO FilePath -> io FilePath)
-> (Handle -> IO FilePath) -> Handle -> io FilePath
forall b c a. (b -> c) -> (a -> b) -> a -> c
.  Handle -> IO FilePath
IO.hGetLine
    hPutStr :: Handle -> FilePath -> io ()
hPutStr   = IO () -> io ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> io ())
-> (Handle -> FilePath -> IO ()) -> Handle -> FilePath -> io ()
forall c d a b. (c -> d) -> (a -> b -> c) -> a -> b -> d
... Handle -> FilePath -> IO ()
IO.hPutStr