{-|
Module      : Filesystem.CanonicalPath.Directory
Copyright   : (c) Boris Buliga, 2014
License     : MIT
Maintainer  : d12frosted@icloud.com
Stability   : experimental
Portability : portable

Redefinition of some functions from @System.Directory@ module. Some of them have different signature, because they need to work with @'CanonicalPath'@. For example, we can't create functions @createDirectory :: 'CanonicalPath' -> IO ()@, because it has no sense. How can we create directory that already exists? Instead we have function @createDirectory :: 'CanonicalPath' -> 'FilePath' -> IO 'CanonicalPath'@, that creates new directory in base existing directory with provided name. And also it returns @'CanonicalPath'@ of newly created directory. Isn't it nice?

A lot of functions come in two variants: one that returns resulting @'CanonicalPath' and second that ignores result (they end with '_' symbol).

Happy Haskell Hacking!
-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoImplicitPrelude #-}

module Filesystem.CanonicalPath.Directory where

import           BasicPrelude
import           Data.Text ()
import           Filesystem.CanonicalPath
import           Filesystem.CanonicalPath.Internal
import           Filesystem.Path
import qualified System.Directory as Directory

{-|
@'createDirectory' base dir@ creates new directory @dir@ in existing @base@ directory and returns @'CanonicalPath'@ of created directory.

For more information look for documentation of @'System.Directory.createDirectory'@.

/Since 0.1.1.0/
-}
createDirectory :: MonadIO m
                => CanonicalPath -- ^ base directory
                -> FilePath -- ^ name of new directory
                -> m CanonicalPath -- ^ @'CanonicalPath'@ of created directory
createDirectory cp dir =
  do liftIO . Directory.createDirectory $ toPrelude path
     return $ CanonicalPath path
  where path = unsafePath cp </> dir

{-|
Variant of @'createDirectory' that ignores resulting @'CanonicalPath'.

/Since 0.2.2.0/
-}
createDirectory_ :: MonadIO m => CanonicalPath -> FilePath -> m ()
createDirectory_ cp dir = voidM $ createDirectory cp dir

{-|
@'createDirectoryIfMissing' parents dir@ creates a new directory @dir@ in @base@ directory. If the first argument is 'True' the function will also create all parent directories if they are missing. Function returns @'CanonicalPath'@ of created directory.

For more information look for documentation of @'System.Directory.createDirectoryIfMissing'@.

/Since 0.1.1.0/
-}
createDirectoryIfMissing :: MonadIO m
                         => Bool -- ^ Create its parents too?
                         -> CanonicalPath -- ^ base directory
                         -> FilePath -- ^ name of new directory
                         -> m CanonicalPath -- ^ @'CanonicalPath'@ of created directory
createDirectoryIfMissing flag cp dir =
  do liftIO . Directory.createDirectoryIfMissing flag $ toPrelude path
     return $ CanonicalPath path
  where path = unsafePath cp </> dir

{-|
Variant of @'createDirectoryIfMissing' that ignores resulting @'CanonicalPath'.

/Since 0.2.2.0/
-}
createDirectoryIfMissing_ :: MonadIO m => Bool -> CanonicalPath -> FilePath -> m ()
createDirectoryIfMissing_ flag cp dir = voidM $ createDirectoryIfMissing flag cp dir

{-|
@'removeDirectory' dir@ removes an existing directory /dir/.

For more information look for documentation of @'System.Directory.removeDirectory'@.

/Since 0.1.1.0/
-}
removeDirectory :: MonadIO m => CanonicalPath -> m ()
removeDirectory = liftIO . preludeMap Directory.removeDirectory

{-|
@'removeDirectoryRecursive' dir@  removes an existing directory /dir/ together with its content and all subdirectories. Be careful, if the directory contains symlinks, the function will follow them.

For more information look for documentation of @'System.Directory.removeDirectoryRecursive'@.

/Since 0.1.1.0/
-}
removeDirectoryRecursive :: MonadIO m => CanonicalPath -> m ()
removeDirectoryRecursive = liftIO . preludeMap Directory.removeDirectoryRecursive

{-|
@'renameDirectory' old new@ changes the name of an existing directory from /old/ to /new/ and returns @'CanonicalPath'@ of new directory.

For more information look for documentation of @'System.Directory.renameDirectory'@.

/Since 0.1.1.0/
-}
renameDirectory :: MonadIO m
                => CanonicalPath -- ^ old directory
                -> FilePath -- ^ new directory (should be just name of directory)
                -> m CanonicalPath -- ^ @'CanonicalPath'@ of new directory
renameDirectory cp p =
  do newPath <- canonicalPath $ parent p
     liftIO $ Directory.renameDirectory (toPrelude . unsafePath $ cp) (toPrelude p)
     return . CanonicalPath $ unsafePath newPath </> dirname (p </> "")

{-|
Variant of @'renameDirectory' that ignores resulting @'CanonicalPath'.

/Since 0.2.2.0/
-}
renameDirectory_ :: MonadIO m => CanonicalPath -> FilePath -> m ()
renameDirectory_ cp p = voidM $ renameDirectory cp p

{-|
@'getDirectoryContents' dir@ returns a list of /all/ entries in /dir/. If you want to have list of @'CanonicalPath'@ instead use function @'getDirectoryContents''@.

For more information look for documentation of @'System.Directory.getDirectoryContents'@.

/Since 0.1.1.0/
-}
getDirectoryContents :: MonadIO m => CanonicalPath -> m [FilePath]
getDirectoryContents cp = liftIO . liftM (fromPrelude <$>) $ preludeMap Directory.getDirectoryContents cp

{-|
The same as @'getDirectoryContents'@, but returns list of @'CanonicalPath'@ instead of @'FilePath'@.

/Since 0.1.1.0/
-}
getDirectoryContents' :: MonadIO m => CanonicalPath -> m [CanonicalPath]
getDirectoryContents' cp = liftIO . liftM (CanonicalPath . (</> ) up <$>) $ getDirectoryContents cp
  where up = unsafePath cp

{-|
The same as @'getDirectoryContents'@, but returns list of @'Text'@ instead of @'FilePath'@.

/Since 0.2.2.0/
-}
getDirectoryContents'' :: MonadIO m => CanonicalPath -> m [Text]
getDirectoryContents'' cp = liftIO . liftM (fromString <$>) $ preludeMap Directory.getDirectoryContents cp

{- |If the operating system has a notion of current directories, 'getCurrentDirectory' returns an @'CanonicalPath'@ to the current directory of the calling process.

For more information look for documentation of @'System.Directory.getCurrentDirectory'@.

/Since 0.1.1.0/
-}
getCurrentDirectory :: MonadIO m => m CanonicalPath
getCurrentDirectory = liftIO $ liftM (CanonicalPath . fromPrelude) Directory.getCurrentDirectory

{-|
If the operating system has a notion of current directories, @'setCurrentDirectory' dir@ changes the current directory of the calling process to /dir/.

For more information look for documentation of @'System.Directory.setCurrentDirectory'@.

/Since 0.1.1.0/
-}
setCurrentDirectory :: MonadIO m => CanonicalPath -> m ()
setCurrentDirectory = liftIO . preludeMap Directory.setCurrentDirectory

{-|
Returns the current user's home directory.

For more information look for documentation of @'System.Directory.getHomeDirectory'@.

/Since 0.1.1.0/
-}
getHomeDirectory :: MonadIO m => m CanonicalPath
getHomeDirectory = liftIO $ liftM (CanonicalPath . fromPrelude) Directory.getHomeDirectory

{-|
Returns the @'CanonicalPath'@ of a directory in which application-specific data for the current user can be stored.  The result of 'getAppUserDataDirectory' for a given application is specific to the current user.

For more information look for documentation of @'System.Directory.getAppUserDataDirectory'@.

/Since 0.1.1.0/
-}
getAppUserDataDirectory :: MonadIO m => Text -> m FilePath
getAppUserDataDirectory = liftIO . liftM fromPrelude . Directory.getAppUserDataDirectory . textToString

{-|
Returns the current user's document directory.

For more information look for documentation of @'System.Directory.getUserDocumentsDirectory'@.

/Since 0.1.1.0/
-}
getUserDocumentsDirectory :: MonadIO m => m CanonicalPath
getUserDocumentsDirectory = liftIO $ liftM (CanonicalPath . fromPrelude) Directory.getUserDocumentsDirectory

{-|
Returns the current directory for temporary files.

For more information look for documentation of @'System.Directory.getUserDocumentsDirectorygetTemporaryDirectory'@.

/Since 0.1.1.0/
-}
getTemporaryDirectory :: MonadIO m => m FilePath
getTemporaryDirectory = liftIO $ liftM fromPrelude Directory.getTemporaryDirectory

{-|
'removeFile' /file/ removes the directory entry for an existing file /file/, where /file/ is not itself a directory.

For more information look for documentation of @'System.Directory.removeFile'@.

/Since 0.1.1.0/
-}
removeFile :: MonadIO m => CanonicalPath -> m ()
removeFile = liftIO . preludeMap Directory.removeFile

{-|
@'renameFile' old new@ changes the name of an existing file system object from /old/ to /new/.

For more information look for documentation of @'System.Directory.renameFile'@.

/Since 0.1.1.0/
-}
renameFile :: MonadIO m
           => CanonicalPath -- ^ @'CanonicalPath'@ of file you want to rename
           -> FilePath -- ^ new name of file
           -> m CanonicalPath -- ^ @'CanonicalPath'@ of /new/ file
renameFile cp p =
  do newPath <- canonicalPath $ parent p
     liftIO $ Directory.renameFile (toPrelude . unsafePath $ cp) (toPrelude p)
     return . CanonicalPath $ unsafePath newPath </> filename p

{-|
Variant of @'renameFile' that ignores resulting @'CanonicalPath'.

/Since 0.2.2.0/
-}
renameFile_ :: MonadIO m => CanonicalPath -> FilePath -> m ()
renameFile_ cp p = voidM $ renameFile cp p

{-|
@'copyFile' old new@ copies the existing file from /old/ to /new/. If the /new/ file already exists, it is atomically replaced by the /old/ file. Neither path may refer to an existing directory.  The permissions of /old/ are copied to /new/, if possible.

For more information look for documentation of @'System.Directory.copyFile'@.

/Since 0.1.1.0/
-}
copyFile :: MonadIO m
         => CanonicalPath -- ^ @'CanonicalPath'@ of file you want to copy
         -> FilePath -- ^ name of new file (actually it can be path relative to directory of /old/
         -> m CanonicalPath -- ^ @'CanonicalPath'@ of /new/ file
copyFile oldFile newFile =
  do newPath <- canonicalPath $ parent newFile
     liftIO $ Directory.copyFile (toPrelude . unsafePath $ oldFile) (toPrelude newFile)
     return . CanonicalPath $ unsafePath newPath </> filename newFile

copyFile_ :: MonadIO m => CanonicalPath -> FilePath -> m ()
copyFile_ oldFile newFile = voidM $ copyFile oldFile newFile
