{-# LANGUAGE TypeApplications #-}
-- This template expects CPP definitions for:
--
--     WINDOWS defined? = no            | yes              | no
--     POSIX   defined? = yes           | no               | no
--
--     FILEPATH_NAME    = PosixPath     | WindowsPath  | OsPath
--     OSSTRING_NAME    = PosixString   | WindowsString    | OsString
--     WORD_NAME        = PosixChar     | WindowsChar      | OsChar

-- For (native) abstract file paths we document both platforms, so people can
-- understand how their code is compiled no matter what. But for the
-- platform-specific types we only want to document the behavior on that
-- platform.
#if defined(WINDOWS)
#define WINDOWS_DOC
#elif defined(POSIX)
#define POSIX_DOC
#endif

#ifdef WINDOWS
module System.OsPath.Windows
#elif defined(POSIX)
module System.OsPath.Posix
#else
module System.OsPath
#endif
  (
  -- * Types
#ifdef WINDOWS
    WindowsString
  , WindowsChar
  , WindowsPath
#elif defined(POSIX)
    PosixString
  , PosixChar
  , PosixPath
#else
    OsPath
  , OsString
  , OsChar
#endif
  -- * Filepath construction
  , PS.encodeUtf
  , PS.unsafeEncodeUtf
  , PS.encodeWith
  , encodeFS
#if defined(WINDOWS) || defined(POSIX)
  , pstr
#else
  , osp
#endif
  , PS.pack

  -- * Filepath deconstruction
  , PS.decodeUtf
  , PS.decodeWith
  , decodeFS
  , PS.unpack

  -- * Word construction
  , unsafeFromChar

  -- * Word deconstruction
  , toChar

  -- * Separator predicates
  , pathSeparator
  , pathSeparators
  , isPathSeparator
  , searchPathSeparator
  , isSearchPathSeparator
  , extSeparator
  , isExtSeparator

  -- * $PATH methods
  , splitSearchPath,

  -- * Extension functions
    splitExtension,
    takeExtension, replaceExtension, (-<.>), dropExtension, addExtension, hasExtension, (<.>),
    splitExtensions, dropExtensions, takeExtensions, replaceExtensions, isExtensionOf,
    stripExtension,

    -- * Filename\/directory functions
    splitFileName,
    takeFileName, replaceFileName, dropFileName,
    takeBaseName, replaceBaseName,
    takeDirectory, replaceDirectory,
    combine, (</>),
    splitPath, joinPath, splitDirectories,

    -- * Drive functions
    splitDrive, joinDrive,
    takeDrive, hasDrive, dropDrive, isDrive,

    -- * Trailing slash functions
    hasTrailingPathSeparator,
    addTrailingPathSeparator,
    dropTrailingPathSeparator,

    -- * File name manipulations
    normalise, equalFilePath,
    makeRelative,
    isRelative, isAbsolute,
    isValid, makeValid
  )
where


#ifdef WINDOWS
import System.OsPath.Types
import System.OsString.Windows as PS
    ( unsafeFromChar
    , toChar
    , decodeUtf
    , decodeWith
    , pack
    , encodeUtf
    , unsafeEncodeUtf
    , encodeWith
    , unpack
    )
import Data.Bifunctor ( bimap )
import qualified System.OsPath.Windows.Internal as C
import GHC.IO.Encoding.UTF16 ( mkUTF16le )
import Language.Haskell.TH.Quote
    ( QuasiQuoter (..) )
import Language.Haskell.TH.Syntax
    ( Lift (..), lift )
import GHC.IO.Encoding.Failure ( CodingFailureMode(..) )
import Control.Monad ( when )

#elif defined(POSIX)
import GHC.IO.Encoding.Failure ( CodingFailureMode(..) )
import Control.Monad ( when )
import Language.Haskell.TH.Quote
    ( QuasiQuoter (..) )
import Language.Haskell.TH.Syntax
    ( Lift (..), lift )

import GHC.IO.Encoding.UTF8 ( mkUTF8 )
import System.OsPath.Types
import System.OsString.Posix as PS
    ( unsafeFromChar
    , toChar
    , decodeUtf
    , decodeWith
    , pack
    , encodeUtf
    , unsafeEncodeUtf
    , encodeWith
    , unpack
    )
import Data.Bifunctor ( bimap )
import qualified System.OsPath.Posix.Internal as C

#else

import System.OsPath.Internal as PS
    ( osp
    , decodeUtf
    , decodeWith
    , pack
    , encodeUtf
    , unsafeEncodeUtf
    , encodeWith
    , unpack
    )
import System.OsPath.Types
    ( OsPath )
import System.OsString ( unsafeFromChar, toChar )

#if defined(mingw32_HOST_OS) || defined(__MINGW32__)
import qualified System.OsPath.Windows as C
#else
import qualified System.OsPath.Posix as C
#endif

import Data.Bifunctor
    ( bimap )
#endif
import System.OsString.Internal.Types
import System.OsString.Encoding.Internal


------------------------
-- Separator predicates


#ifdef WINDOWS_DOC
-- | The character that separates directories. In the case where more than
--   one character is possible, 'pathSeparator' is the \'ideal\' one.
--
-- > pathSeparator == '\\'S
#elif defined(POSIX_DOC)
-- | The character that separates directories. In the case where more than
--   one character is possible, 'pathSeparator' is the \'ideal\' one.
--
-- > pathSeparator ==  '/'
#else
-- | The character that separates directories. In the case where more than
--   one character is possible, 'pathSeparator' is the \'ideal\' one.
--
-- > Windows: pathSeparator == '\\'S
-- > Posix:   pathSeparator ==  '/'
#endif
pathSeparator :: WORD_NAME
pathSeparator = WORD_NAME C.pathSeparator

#ifdef WINDOWS_DOC
-- | The list of all possible separators.
--
-- > pathSeparators == ['\\', '/']
-- > pathSeparator `elem` pathSeparators
#elif defined(POSIX_DOC)
-- | The list of all possible separators.
--
-- > pathSeparators == ['/']
-- > pathSeparator `elem` pathSeparators
#else
-- | The list of all possible separators.
--
-- > Windows: pathSeparators == ['\\', '/']
-- > Posix:   pathSeparators == ['/']
-- > pathSeparator `elem` pathSeparators
#endif
pathSeparators :: [WORD_NAME]
pathSeparators = WORD_NAME <$> C.pathSeparators

-- | Rather than using @(== 'pathSeparator')@, use this. Test if something
--   is a path separator.
--
-- > isPathSeparator a == (a `elem` pathSeparators)
isPathSeparator :: WORD_NAME -> Bool
isPathSeparator (WORD_NAME w) = C.isPathSeparator w

#ifdef WINDOWS_DOC
-- | The character that is used to separate the entries in the $PATH environment variable.
--
-- > searchPathSeparator == ';'
#elif defined(POSIX_DOC)
-- | The character that is used to separate the entries in the $PATH environment variable.
--
-- > searchPathSeparator == ':'
#else
-- | The character that is used to separate the entries in the $PATH environment variable.
--
-- > Posix:   searchPathSeparator == ':'
-- > Windows: searchPathSeparator == ';'
#endif
searchPathSeparator :: WORD_NAME
searchPathSeparator = WORD_NAME C.searchPathSeparator

-- | Is the character a file separator?
--
-- > isSearchPathSeparator a == (a == searchPathSeparator)
isSearchPathSeparator :: WORD_NAME -> Bool
isSearchPathSeparator (WORD_NAME w) = C.isSearchPathSeparator w


-- | File extension character
--
-- > extSeparator == '.'
extSeparator :: WORD_NAME
extSeparator = WORD_NAME C.extSeparator


-- | Is the character an extension character?
--
-- > isExtSeparator a == (a == extSeparator)
isExtSeparator :: WORD_NAME -> Bool
isExtSeparator (WORD_NAME w) = C.isExtSeparator w


---------------------------------------------------------------------
-- Path methods (environment $PATH)

#ifdef WINDOWS_DOC
-- | Take a string, split it on the 'searchPathSeparator' character.
--
--   Blank items are ignored and path elements are stripped of quotes.
--
-- > splitSearchPath "File1;File2;File3"  == ["File1","File2","File3"]
-- > splitSearchPath "File1;;File2;File3" == ["File1","File2","File3"]
-- > splitSearchPath "File1;\"File2\";File3" == ["File1","File2","File3"]
#elif defined(POSIX_DOC)
-- | Take a string, split it on the 'searchPathSeparator' character.
--
--   Blank items are converted to @.@ on , and quotes are not
--   treated specially.
--
--   Follows the recommendations in
--   <http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html>
--
-- > splitSearchPath "File1:File2:File3"  == ["File1","File2","File3"]
-- > splitSearchPath "File1::File2:File3" == ["File1",".","File2","File3"]
#else
-- | Take a string, split it on the 'searchPathSeparator' character.
--
--   On Windows, blank items are ignored on Windows, and path elements are
--   stripped of quotes.
--
--   On Posix, blank items are converted to @.@ on Posix, and quotes are not
--   treated specially.
--
--   Follows the recommendations in
--   <http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html>
--
-- > Windows: splitSearchPath "File1;File2;File3"  == ["File1","File2","File3"]
-- > Windows: splitSearchPath "File1;;File2;File3" == ["File1","File2","File3"]
-- > Windows: splitSearchPath "File1;\"File2\";File3" == ["File1","File2","File3"]
-- > Posix:   splitSearchPath "File1:File2:File3"  == ["File1","File2","File3"]
-- > Posix:   splitSearchPath "File1::File2:File3" == ["File1",".","File2","File3"]
#endif
splitSearchPath :: OSSTRING_NAME -> [FILEPATH_NAME]
splitSearchPath (OSSTRING_NAME x) = fmap OSSTRING_NAME . C.splitSearchPath $ x



------------------------
-- Extension functions

-- | Split on the extension. 'addExtension' is the inverse.
--
-- > splitExtension "/directory/path.ext" == ("/directory/path",".ext")
-- > uncurry (<>) (splitExtension x) == x
-- > Valid x => uncurry addExtension (splitExtension x) == x
-- > splitExtension "file.txt" == ("file",".txt")
-- > splitExtension "file" == ("file","")
-- > splitExtension "file/file.txt" == ("file/file",".txt")
-- > splitExtension "file.txt/boris" == ("file.txt/boris","")
-- > splitExtension "file.txt/boris.ext" == ("file.txt/boris",".ext")
-- > splitExtension "file/path.txt.bob.fred" == ("file/path.txt.bob",".fred")
-- > splitExtension "file/path.txt/" == ("file/path.txt/","")
splitExtension :: FILEPATH_NAME -> (FILEPATH_NAME, OSSTRING_NAME)
splitExtension (OSSTRING_NAME x) = bimap OSSTRING_NAME OSSTRING_NAME $ C.splitExtension x


-- | Get the extension of a file, returns @\"\"@ for no extension, @.ext@ otherwise.
--
-- > takeExtension "/directory/path.ext" == ".ext"
-- > takeExtension x == snd (splitExtension x)
-- > Valid x => takeExtension (addExtension x "ext") == ".ext"
-- > Valid x => takeExtension (replaceExtension x "ext") == ".ext"
takeExtension :: FILEPATH_NAME -> OSSTRING_NAME
takeExtension (OSSTRING_NAME x) = OSSTRING_NAME $ C.takeExtension x


-- | Remove the current extension and add another, equivalent to 'replaceExtension'.
--
-- > "/directory/path.txt" -<.> "ext" == "/directory/path.ext"
-- > "/directory/path.txt" -<.> ".ext" == "/directory/path.ext"
-- > "foo.o" -<.> "c" == "foo.c"
(-<.>) :: FILEPATH_NAME -> OSSTRING_NAME -> FILEPATH_NAME
(-<.>) = replaceExtension

-- | Set the extension of a file, overwriting one if already present, equivalent to '-<.>'.
--
-- > replaceExtension "/directory/path.txt" "ext" == "/directory/path.ext"
-- > replaceExtension "/directory/path.txt" ".ext" == "/directory/path.ext"
-- > replaceExtension "file.txt" ".bob" == "file.bob"
-- > replaceExtension "file.txt" "bob" == "file.bob"
-- > replaceExtension "file" ".bob" == "file.bob"
-- > replaceExtension "file.txt" "" == "file"
-- > replaceExtension "file.fred.bob" "txt" == "file.fred.txt"
-- > replaceExtension x y == addExtension (dropExtension x) y
replaceExtension :: FILEPATH_NAME -> OSSTRING_NAME -> FILEPATH_NAME
replaceExtension (OSSTRING_NAME path) (OSSTRING_NAME ext) = OSSTRING_NAME (C.replaceExtension path ext)


-- | Add an extension, even if there is already one there, equivalent to 'addExtension'.
--
-- > "/directory/path" <.> "ext" == "/directory/path.ext"
-- > "/directory/path" <.> ".ext" == "/directory/path.ext"
(<.>) :: FILEPATH_NAME -> OSSTRING_NAME -> FILEPATH_NAME
(<.>) = addExtension

-- | Remove last extension, and the \".\" preceding it.
--
-- > dropExtension "/directory/path.ext" == "/directory/path"
-- > dropExtension x == fst (splitExtension x)
dropExtension :: FILEPATH_NAME -> FILEPATH_NAME
dropExtension (OSSTRING_NAME x) = OSSTRING_NAME $ C.dropExtension x


-- | Add an extension, even if there is already one there, equivalent to '<.>'.
--
-- > addExtension "/directory/path" "ext" == "/directory/path.ext"
-- > addExtension "file.txt" "bib" == "file.txt.bib"
-- > addExtension "file." ".bib" == "file..bib"
-- > addExtension "file" ".bib" == "file.bib"
-- > addExtension "/" "x" == "/.x"
-- > addExtension x "" == x
-- > Valid x => takeFileName (addExtension (addTrailingPathSeparator x) "ext") == ".ext"
-- > Windows: addExtension "\\\\share" ".txt" == "\\\\share\\.txt"

#ifdef WINDOWS_DOC
-- | Add an extension, even if there is already one there, equivalent to '<.>'.
--
-- > addExtension "/directory/path" "ext" == "/directory/path.ext"
-- > addExtension "file.txt" "bib" == "file.txt.bib"
-- > addExtension "file." ".bib" == "file..bib"
-- > addExtension "file" ".bib" == "file.bib"
-- > addExtension "/" "x" == "/.x"
-- > addExtension x "" == x
-- > Valid x => takeFileName (addExtension (addTrailingPathSeparator x) "ext") == ".ext"
-- > addExtension "\\\\share" ".txt" == "\\\\share\\.txt"
#elif defined(POSIX_DOC)
-- | Add an extension, even if there is already one there, equivalent to '<.>'.
--
-- > addExtension "/directory/path" "ext" == "/directory/path.ext"
-- > addExtension "file.txt" "bib" == "file.txt.bib"
-- > addExtension "file." ".bib" == "file..bib"
-- > addExtension "file" ".bib" == "file.bib"
-- > addExtension "/" "x" == "/.x"
-- > addExtension x "" == x
-- > Valid x => takeFileName (addExtension (addTrailingPathSeparator x) "ext") == ".ext"
#else
-- | Add an extension, even if there is already one there, equivalent to '<.>'.
--
-- > addExtension "/directory/path" "ext" == "/directory/path.ext"
-- > addExtension "file.txt" "bib" == "file.txt.bib"
-- > addExtension "file." ".bib" == "file..bib"
-- > addExtension "file" ".bib" == "file.bib"
-- > addExtension "/" "x" == "/.x"
-- > addExtension x "" == x
-- > Valid x => takeFileName (addExtension (addTrailingPathSeparator x) "ext") == ".ext"
-- > Windows: addExtension "\\\\share" ".txt" == "\\\\share\\.txt"
#endif
addExtension :: FILEPATH_NAME -> OSSTRING_NAME -> FILEPATH_NAME
addExtension (OSSTRING_NAME bs) (OSSTRING_NAME ext) = OSSTRING_NAME $ C.addExtension bs ext


-- | Does the given filename have an extension?
--
-- > hasExtension "/directory/path.ext" == True
-- > hasExtension "/directory/path" == False
-- > null (takeExtension x) == not (hasExtension x)
hasExtension :: FILEPATH_NAME -> Bool
hasExtension (OSSTRING_NAME x) = C.hasExtension x

-- | Does the given filename have the specified extension?
--
-- > "png" `isExtensionOf` "/directory/file.png" == True
-- > ".png" `isExtensionOf` "/directory/file.png" == True
-- > ".tar.gz" `isExtensionOf` "bar/foo.tar.gz" == True
-- > "ar.gz" `isExtensionOf` "bar/foo.tar.gz" == False
-- > "png" `isExtensionOf` "/directory/file.png.jpg" == False
-- > "csv/table.csv" `isExtensionOf` "/data/csv/table.csv" == False
isExtensionOf :: OSSTRING_NAME -> FILEPATH_NAME -> Bool
isExtensionOf (OSSTRING_NAME x) (OSSTRING_NAME y) = C.isExtensionOf x y

-- | Drop the given extension from a filepath, and the @\".\"@ preceding it.
--   Returns 'Nothing' if the filepath does not have the given extension, or
--   'Just' and the part before the extension if it does.
--
--   This function can be more predictable than 'dropExtensions', especially if the filename
--   might itself contain @.@ characters.
--
-- > stripExtension "hs.o" "foo.x.hs.o" == Just "foo.x"
-- > stripExtension "hi.o" "foo.x.hs.o" == Nothing
-- > dropExtension x == fromJust (stripExtension (takeExtension x) x)
-- > dropExtensions x == fromJust (stripExtension (takeExtensions x) x)
-- > stripExtension ".c.d" "a.b.c.d"  == Just "a.b"
-- > stripExtension ".c.d" "a.b..c.d" == Just "a.b."
-- > stripExtension "baz"  "foo.bar"  == Nothing
-- > stripExtension "bar"  "foobar"   == Nothing
-- > stripExtension ""     x          == Just x
stripExtension :: OSSTRING_NAME -> FILEPATH_NAME -> Maybe FILEPATH_NAME
stripExtension (OSSTRING_NAME bs) (OSSTRING_NAME x) = OSSTRING_NAME <$> C.stripExtension bs x

-- | Split on all extensions.
--
-- > splitExtensions "/directory/path.ext" == ("/directory/path",".ext")
-- > splitExtensions "file.tar.gz" == ("file",".tar.gz")
-- > uncurry (<>) (splitExtensions x) == x
-- > Valid x => uncurry addExtension (splitExtensions x) == x
-- > splitExtensions "file.tar.gz" == ("file",".tar.gz")
splitExtensions :: FILEPATH_NAME -> (FILEPATH_NAME, OSSTRING_NAME)
splitExtensions (OSSTRING_NAME x) = bimap OSSTRING_NAME OSSTRING_NAME $ C.splitExtensions x


-- | Drop all extensions.
--
-- > dropExtensions "/directory/path.ext" == "/directory/path"
-- > dropExtensions "file.tar.gz" == "file"
-- > not $ hasExtension $ dropExtensions x
-- > not $ any isExtSeparator $ takeFileName $ dropExtensions x
dropExtensions :: FILEPATH_NAME -> FILEPATH_NAME
dropExtensions (OSSTRING_NAME x) = OSSTRING_NAME $ C.dropExtensions x


-- | Get all extensions.
--
-- > takeExtensions "/directory/path.ext" == ".ext"
-- > takeExtensions "file.tar.gz" == ".tar.gz"
takeExtensions :: FILEPATH_NAME -> OSSTRING_NAME
takeExtensions (OSSTRING_NAME x) = OSSTRING_NAME $ C.takeExtensions x

-- | Replace all extensions of a file with a new extension. Note
--   that 'replaceExtension' and 'addExtension' both work for adding
--   multiple extensions, so only required when you need to drop
--   all extensions first.
--
-- > replaceExtensions "file.fred.bob" "txt" == "file.txt"
-- > replaceExtensions "file.fred.bob" "tar.gz" == "file.tar.gz"
replaceExtensions :: FILEPATH_NAME -> OSSTRING_NAME -> FILEPATH_NAME
replaceExtensions (OSSTRING_NAME x) (OSSTRING_NAME y) = OSSTRING_NAME $ C.replaceExtensions x y


------------------------
-- Drive functions


#ifdef WINDOWS_DOC
-- | Split a path into a drive and a path.
--
-- > uncurry (<>) (splitDrive x) == x
-- > splitDrive "file" == ("","file")
-- > splitDrive "c:/file" == ("c:/","file")
-- > splitDrive "c:\\file" == ("c:\\","file")
-- > splitDrive "\\\\shared\\test" == ("\\\\shared\\","test")
-- > splitDrive "\\\\shared" == ("\\\\shared","")
-- > splitDrive "\\\\?\\UNC\\shared\\file" == ("\\\\?\\UNC\\shared\\","file")
-- > splitDrive "\\\\?\\UNCshared\\file" == ("\\\\?\\","UNCshared\\file")
-- > splitDrive "\\\\?\\d:\\file" == ("\\\\?\\d:\\","file")
-- > splitDrive "/d" == ("","/d")
#elif defined(POSIX_DOC)
-- | Split a path into a drive and a path.
--   \/ is a Drive.
--
-- > uncurry (<>) (splitDrive x) == x
-- > splitDrive "/test" == ("/","test")
-- > splitDrive "//test" == ("//","test")
-- > splitDrive "test/file" == ("","test/file")
-- > splitDrive "file" == ("","file")
#else
-- | Split a path into a drive and a path.
--   On Posix, \/ is a Drive.
--
-- > uncurry (<>) (splitDrive x) == x
-- > Windows: splitDrive "file" == ("","file")
-- > Windows: splitDrive "c:/file" == ("c:/","file")
-- > Windows: splitDrive "c:\\file" == ("c:\\","file")
-- > Windows: splitDrive "\\\\shared\\test" == ("\\\\shared\\","test")
-- > Windows: splitDrive "\\\\shared" == ("\\\\shared","")
-- > Windows: splitDrive "\\\\?\\UNC\\shared\\file" == ("\\\\?\\UNC\\shared\\","file")
-- > Windows: splitDrive "\\\\?\\UNCshared\\file" == ("\\\\?\\","UNCshared\\file")
-- > Windows: splitDrive "\\\\?\\d:\\file" == ("\\\\?\\d:\\","file")
-- > Windows: splitDrive "/d" == ("","/d")
-- > Posix:   splitDrive "/test" == ("/","test")
-- > Posix:   splitDrive "//test" == ("//","test")
-- > Posix:   splitDrive "test/file" == ("","test/file")
-- > Posix:   splitDrive "file" == ("","file")
#endif
splitDrive :: FILEPATH_NAME -> (FILEPATH_NAME, FILEPATH_NAME)
splitDrive (OSSTRING_NAME p) = bimap OSSTRING_NAME OSSTRING_NAME $ C.splitDrive p


-- | Join a drive and the rest of the path.
--
-- > Valid x => uncurry joinDrive (splitDrive x) == x
-- > Windows: joinDrive "C:" "foo" == "C:foo"
-- > Windows: joinDrive "C:\\" "bar" == "C:\\bar"
-- > Windows: joinDrive "\\\\share" "foo" == "\\\\share\\foo"
-- > Windows: joinDrive "/:" "foo" == "/:\\foo"

#ifdef WINDOWS_DOC
-- | Join a drive and the rest of the path.
--
-- > Valid x => uncurry joinDrive (splitDrive x) == x
-- > joinDrive "C:" "foo" == "C:foo"
-- > joinDrive "C:\\" "bar" == "C:\\bar"
-- > joinDrive "\\\\share" "foo" == "\\\\share\\foo"
-- > joinDrive "/:" "foo" == "/:\\foo"
#elif defined(POSIX_DOC)
-- | Join a drive and the rest of the path.
--
-- > Valid x => uncurry joinDrive (splitDrive x) == x
#else
-- | Join a drive and the rest of the path.
--
-- > Valid x => uncurry joinDrive (splitDrive x) == x
-- > Windows: joinDrive "C:" "foo" == "C:foo"
-- > Windows: joinDrive "C:\\" "bar" == "C:\\bar"
-- > Windows: joinDrive "\\\\share" "foo" == "\\\\share\\foo"
-- > Windows: joinDrive "/:" "foo" == "/:\\foo"
#endif
joinDrive :: FILEPATH_NAME -> FILEPATH_NAME -> FILEPATH_NAME
joinDrive (OSSTRING_NAME a) (OSSTRING_NAME b) = OSSTRING_NAME $ C.joinDrive a b


-- | Get the drive from a filepath.
--
-- > takeDrive x == fst (splitDrive x)
takeDrive :: FILEPATH_NAME -> FILEPATH_NAME
takeDrive (OSSTRING_NAME x) = OSSTRING_NAME $ C.takeDrive x


-- | Delete the drive, if it exists.
--
-- > dropDrive x == snd (splitDrive x)
dropDrive :: FILEPATH_NAME -> FILEPATH_NAME
dropDrive (OSSTRING_NAME x) = OSSTRING_NAME $ C.dropDrive x


#ifdef WINDOWS_DOC
-- | Does a path have a drive.
--
-- > not (hasDrive x) == null (takeDrive x)
-- > hasDrive "C:\\foo" == True
-- > hasDrive "C:foo" == True
-- > hasDrive "foo" == False
-- > hasDrive "" == False
--
#elif defined(POSIX_DOC)
-- | Does a path have a drive.
--
-- > not (hasDrive x) == null (takeDrive x)
-- > hasDrive "/foo" == True
-- > hasDrive "foo" == False
-- > hasDrive "" == False
--
#else
-- | Does a path have a drive.
--
-- > not (hasDrive x) == null (takeDrive x)
-- > Posix:   hasDrive "/foo" == True
-- > Windows: hasDrive "C:\\foo" == True
-- > Windows: hasDrive "C:foo" == True
-- >          hasDrive "foo" == False
-- >          hasDrive "" == False
--
#endif
hasDrive :: FILEPATH_NAME -> Bool
hasDrive (OSSTRING_NAME x) = C.hasDrive x


#ifdef WINDOWS_DOC
-- | Is an element a drive
--
-- > isDrive "C:\\" == True
-- > isDrive "C:\\foo" == False
-- > isDrive "" == False
#elif defined(POSIX_DOC)
-- | Is an element a drive
--
-- > isDrive "/" == True
-- > isDrive "/foo" == False
-- > isDrive "" == False
#else
-- | Is an element a drive
--
-- > Posix:   isDrive "/" == True
-- > Posix:   isDrive "/foo" == False
-- > Windows: isDrive "C:\\" == True
-- > Windows: isDrive "C:\\foo" == False
-- >          isDrive "" == False
#endif
isDrive :: FILEPATH_NAME -> Bool
isDrive (OSSTRING_NAME x) = C.isDrive x


---------------------------------------------------------------------
-- Operations on a filepath, as a list of directories

#ifdef WINDOWS_DOC
-- | Split a filename into directory and file. '</>' is the inverse.
--   The first component will often end with a trailing slash.
--
-- > splitFileName "/directory/file.ext" == ("/directory/","file.ext")
-- > Valid x => uncurry (</>) (splitFileName x) == x || fst (splitFileName x) == "./"
-- > Valid x => isValid (fst (splitFileName x))
-- > splitFileName "file/bob.txt" == ("file/", "bob.txt")
-- > splitFileName "file/" == ("file/", "")
-- > splitFileName "bob" == ("./", "bob")
-- > splitFileName "c:" == ("c:","")
#elif defined(POSIX_DOC)
-- | Split a filename into directory and file. '</>' is the inverse.
--   The first component will often end with a trailing slash.
--
-- > splitFileName "/directory/file.ext" == ("/directory/","file.ext")
-- > Valid x => uncurry (</>) (splitFileName x) == x || fst (splitFileName x) == "./"
-- > Valid x => isValid (fst (splitFileName x))
-- > splitFileName "file/bob.txt" == ("file/", "bob.txt")
-- > splitFileName "file/" == ("file/", "")
-- > splitFileName "bob" == ("./", "bob")
-- > splitFileName "/" == ("/","")
#else
-- | Split a filename into directory and file. '</>' is the inverse.
--   The first component will often end with a trailing slash.
--
-- > splitFileName "/directory/file.ext" == ("/directory/","file.ext")
-- > Valid x => uncurry (</>) (splitFileName x) == x || fst (splitFileName x) == "./"
-- > Valid x => isValid (fst (splitFileName x))
-- > splitFileName "file/bob.txt" == ("file/", "bob.txt")
-- > splitFileName "file/" == ("file/", "")
-- > splitFileName "bob" == ("./", "bob")
-- > Posix:   splitFileName "/" == ("/","")
-- > Windows: splitFileName "c:" == ("c:","")
#endif
splitFileName :: FILEPATH_NAME -> (FILEPATH_NAME, FILEPATH_NAME)
splitFileName (OSSTRING_NAME x) = bimap OSSTRING_NAME OSSTRING_NAME $ C.splitFileName x


-- | Set the filename.
--
-- > replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext"
-- > Valid x => replaceFileName x (takeFileName x) == x
replaceFileName :: FILEPATH_NAME -> OSSTRING_NAME -> FILEPATH_NAME
replaceFileName (OSSTRING_NAME x) (OSSTRING_NAME y) = OSSTRING_NAME $ C.replaceFileName x y


-- | Drop the filename. Unlike 'takeDirectory', this function will leave
--   a trailing path separator on the directory.
--
-- > dropFileName "/directory/file.ext" == "/directory/"
-- > dropFileName x == fst (splitFileName x)
dropFileName :: FILEPATH_NAME -> FILEPATH_NAME
dropFileName (OSSTRING_NAME x) = OSSTRING_NAME $ C.dropFileName x


-- | Get the file name.
--
-- > takeFileName "/directory/file.ext" == "file.ext"
-- > takeFileName "test/" == ""
-- > takeFileName x `isSuffixOf` x
-- > takeFileName x == snd (splitFileName x)
-- > Valid x => takeFileName (replaceFileName x "fred") == "fred"
-- > Valid x => takeFileName (x </> "fred") == "fred"
-- > Valid x => isRelative (takeFileName x)
takeFileName :: FILEPATH_NAME -> FILEPATH_NAME
takeFileName (OSSTRING_NAME x) = OSSTRING_NAME $ C.takeFileName x


-- | Get the base name, without an extension or path.
--
-- > takeBaseName "/directory/file.ext" == "file"
-- > takeBaseName "file/test.txt" == "test"
-- > takeBaseName "dave.ext" == "dave"
-- > takeBaseName "" == ""
-- > takeBaseName "test" == "test"
-- > takeBaseName (addTrailingPathSeparator x) == ""
-- > takeBaseName "file/file.tar.gz" == "file.tar"
takeBaseName :: FILEPATH_NAME -> FILEPATH_NAME
takeBaseName (OSSTRING_NAME x) = OSSTRING_NAME $ C.takeBaseName x


-- | Set the base name.
--
-- > replaceBaseName "/directory/other.ext" "file" == "/directory/file.ext"
-- > replaceBaseName "file/test.txt" "bob" == "file/bob.txt"
-- > replaceBaseName "fred" "bill" == "bill"
-- > replaceBaseName "/dave/fred/bob.gz.tar" "new" == "/dave/fred/new.tar"
-- > Valid x => replaceBaseName x (takeBaseName x) == x
replaceBaseName :: FILEPATH_NAME -> OSSTRING_NAME -> FILEPATH_NAME
replaceBaseName (OSSTRING_NAME path) (OSSTRING_NAME name) = OSSTRING_NAME $ C.replaceBaseName path name


-- | Is an item either a directory or the last character a path separator?
--
-- > hasTrailingPathSeparator "test" == False
-- > hasTrailingPathSeparator "test/" == True
hasTrailingPathSeparator :: FILEPATH_NAME -> Bool
hasTrailingPathSeparator (OSSTRING_NAME x) = C.hasTrailingPathSeparator x


#ifdef WINDOWS_DOC
-- | Add a trailing file path separator if one is not already present.
--
-- > hasTrailingPathSeparator (addTrailingPathSeparator x)
-- > hasTrailingPathSeparator x ==> addTrailingPathSeparator x == x
#elif defined(POSIX_DOC)
-- | Add a trailing file path separator if one is not already present.
--
-- > hasTrailingPathSeparator (addTrailingPathSeparator x)
-- > hasTrailingPathSeparator x ==> addTrailingPathSeparator x == x
-- > addTrailingPathSeparator "test/rest" == "test/rest/"
#else
-- | Add a trailing file path separator if one is not already present.
--
-- > hasTrailingPathSeparator (addTrailingPathSeparator x)
-- > hasTrailingPathSeparator x ==> addTrailingPathSeparator x == x
-- > Posix:    addTrailingPathSeparator "test/rest" == "test/rest/"
#endif
addTrailingPathSeparator :: FILEPATH_NAME -> FILEPATH_NAME
addTrailingPathSeparator (OSSTRING_NAME bs) = OSSTRING_NAME $ C.addTrailingPathSeparator bs


#ifdef WINDOWS_DOC
-- | Remove any trailing path separators
--
-- > dropTrailingPathSeparator "file/test/" == "file/test"
-- > dropTrailingPathSeparator "/" == "/"
-- > dropTrailingPathSeparator "\\" == "\\"
#elif defined(POSIX_DOC)
-- | Remove any trailing path separators
--
-- > dropTrailingPathSeparator "file/test/" == "file/test"
-- > dropTrailingPathSeparator "/" == "/"
-- > not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive x
#else
-- | Remove any trailing path separators
--
-- > dropTrailingPathSeparator "file/test/" == "file/test"
-- >           dropTrailingPathSeparator "/" == "/"
-- > Windows:  dropTrailingPathSeparator "\\" == "\\"
-- > Posix:    not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive x
#endif
dropTrailingPathSeparator :: FILEPATH_NAME -> FILEPATH_NAME
dropTrailingPathSeparator (OSSTRING_NAME x) = OSSTRING_NAME $ C.dropTrailingPathSeparator x


#ifdef WINDOWS_DOC
-- | Get the directory name, move up one level.
--
-- > takeDirectory "/directory/other.ext" == "/directory"
-- > takeDirectory x `isPrefixOf` x || takeDirectory x == "."
-- > takeDirectory "foo" == "."
-- > takeDirectory "/" == "/"
-- > takeDirectory "/foo" == "/"
-- > takeDirectory "/foo/bar/baz" == "/foo/bar"
-- > takeDirectory "/foo/bar/baz/" == "/foo/bar/baz"
-- > takeDirectory "foo/bar/baz" == "foo/bar"
-- > takeDirectory "foo\\bar" == "foo"
-- > takeDirectory "foo\\bar\\\\" == "foo\\bar"
-- > takeDirectory "C:\\" == "C:\\"
#elif defined(POSIX_DOC)
-- | Get the directory name, move up one level.
--
-- >           takeDirectory "/directory/other.ext" == "/directory"
-- >           takeDirectory x `isPrefixOf` x || takeDirectory x == "."
-- >           takeDirectory "foo" == "."
-- >           takeDirectory "/" == "/"
-- >           takeDirectory "/foo" == "/"
-- >           takeDirectory "/foo/bar/baz" == "/foo/bar"
-- >           takeDirectory "/foo/bar/baz/" == "/foo/bar/baz"
-- >           takeDirectory "foo/bar/baz" == "foo/bar"
#else
-- | Get the directory name, move up one level.
--
-- >           takeDirectory "/directory/other.ext" == "/directory"
-- >           takeDirectory x `isPrefixOf` x || takeDirectory x == "."
-- >           takeDirectory "foo" == "."
-- >           takeDirectory "/" == "/"
-- >           takeDirectory "/foo" == "/"
-- >           takeDirectory "/foo/bar/baz" == "/foo/bar"
-- >           takeDirectory "/foo/bar/baz/" == "/foo/bar/baz"
-- >           takeDirectory "foo/bar/baz" == "foo/bar"
-- > Windows:  takeDirectory "foo\\bar" == "foo"
-- > Windows:  takeDirectory "foo\\bar\\\\" == "foo\\bar"
-- > Windows:  takeDirectory "C:\\" == "C:\\"
#endif
takeDirectory :: FILEPATH_NAME -> FILEPATH_NAME
takeDirectory (OSSTRING_NAME x) = OSSTRING_NAME $ C.takeDirectory x


-- | Set the directory, keeping the filename the same.
--
-- > replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext"
-- > Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` x
replaceDirectory :: FILEPATH_NAME -> FILEPATH_NAME -> FILEPATH_NAME
replaceDirectory (OSSTRING_NAME file) (OSSTRING_NAME dir) = OSSTRING_NAME $ C.replaceDirectory file dir


-- | An alias for '</>'.
combine :: FILEPATH_NAME -> FILEPATH_NAME -> FILEPATH_NAME
combine (OSSTRING_NAME a) (OSSTRING_NAME b) = OSSTRING_NAME $ C.combine a b

#ifdef WINDOWS_DOC
-- | Combine two paths with a path separator.
--   If the second path starts with a path separator or a drive letter, then it returns the second.
--   The intention is that @readFile (dir '</>' file)@ will access the same file as
--   @setCurrentDirectory dir; readFile file@.
--
-- > "/directory" </> "file.ext" == "/directory\\file.ext"
-- > "directory" </> "/file.ext" == "/file.ext"
-- > Valid x => (takeDirectory x </> takeFileName x) `equalFilePath` x
--
--   Combined:
--
-- > "C:\\foo" </> "bar" == "C:\\foo\\bar"
-- > "home" </> "bob" == "home\\bob"
--
--   Not combined:
--
-- > "home" </> "C:\\bob" == "C:\\bob"
--
--   Not combined (tricky):
--
--   If a filepath starts with a single slash, it is relative to the
--   root of the current drive. In [1], this is (confusingly) referred to as an
--   absolute path.
--   The current behavior of '</>' is to never combine these forms.
--
-- > "home" </> "/bob" == "/bob"
-- > "home" </> "\\bob" == "\\bob"
-- > "C:\\home" </> "\\bob" == "\\bob"
--
--   From [1]: "If a file name begins with only a disk designator
--   but not the backslash after the colon, it is interpreted as a relative path
--   to the current directory on the drive with the specified letter."
--   The current behavior of '</>' is to never combine these forms.
--
-- > "D:\\foo" </> "C:bar" == "C:bar"
-- > "C:\\foo" </> "C:bar" == "C:bar"
#elif defined(POSIX_DOC)
-- | Combine two paths with a path separator.
--   If the second path starts with a path separator or a drive letter, then it returns the second.
--   The intention is that @readFile (dir '</>' file)@ will access the same file as
--   @setCurrentDirectory dir; readFile file@.
--
-- > "/directory" </> "file.ext" == "/directory/file.ext"
-- > Valid x => (takeDirectory x </> takeFileName x) `equalFilePath` x
--
--   Combined:
--
-- > "/" </> "test" == "/test"
-- > "home" </> "bob" == "home/bob"
-- > "x:" </> "foo" == "x:/foo"
--
--   Not combined:
--
-- > "home" </> "/bob" == "/bob"
#else
-- | Combine two paths with a path separator.
--   If the second path starts with a path separator or a drive letter, then it returns the second.
--   The intention is that @readFile (dir '</>' file)@ will access the same file as
--   @setCurrentDirectory dir; readFile file@.
--
-- > Posix:   "/directory" </> "file.ext" == "/directory/file.ext"
-- > Windows: "/directory" </> "file.ext" == "/directory\\file.ext"
-- >          "directory" </> "/file.ext" == "/file.ext"
-- > Valid x => (takeDirectory x </> takeFileName x) `equalFilePath` x
--
--   Combined:
--
-- > Posix:   "/" </> "test" == "/test"
-- > Posix:   "home" </> "bob" == "home/bob"
-- > Posix:   "x:" </> "foo" == "x:/foo"
-- > Windows: "C:\\foo" </> "bar" == "C:\\foo\\bar"
-- > Windows: "home" </> "bob" == "home\\bob"
--
--   Not combined:
--
-- > Posix:   "home" </> "/bob" == "/bob"
-- > Windows: "home" </> "C:\\bob" == "C:\\bob"
--
--   Not combined (tricky):
--
--   On Windows, if a filepath starts with a single slash, it is relative to the
--   root of the current drive. In [1], this is (confusingly) referred to as an
--   absolute path.
--   The current behavior of '</>' is to never combine these forms.
--
-- > Windows: "home" </> "/bob" == "/bob"
-- > Windows: "home" </> "\\bob" == "\\bob"
-- > Windows: "C:\\home" </> "\\bob" == "\\bob"
--
--   On Windows, from [1]: "If a file name begins with only a disk designator
--   but not the backslash after the colon, it is interpreted as a relative path
--   to the current directory on the drive with the specified letter."
--   The current behavior of '</>' is to never combine these forms.
--
-- > Windows: "D:\\foo" </> "C:bar" == "C:bar"
-- > Windows: "C:\\foo" </> "C:bar" == "C:bar"
#endif
(</>) :: FILEPATH_NAME -> FILEPATH_NAME -> FILEPATH_NAME
(</>) = combine


#ifdef WINDOWS_DOC
-- | Split a path by the directory separator.
--
-- > splitPath "/directory/file.ext" == ["/","directory/","file.ext"]
-- > concat (splitPath x) == x
-- > splitPath "test//item/" == ["test//","item/"]
-- > splitPath "test/item/file" == ["test/","item/","file"]
-- > splitPath "" == []
-- > splitPath "c:\\test\\path" == ["c:\\","test\\","path"]
#elif defined(POSIX_DOC)
-- | Split a path by the directory separator.
--
-- > splitPath "/directory/file.ext" == ["/","directory/","file.ext"]
-- > concat (splitPath x) == x
-- > splitPath "test//item/" == ["test//","item/"]
-- > splitPath "test/item/file" == ["test/","item/","file"]
-- > splitPath "" == []
-- > splitPath "/file/test" == ["/","file/","test"]
#else
-- | Split a path by the directory separator.
--
-- > splitPath "/directory/file.ext" == ["/","directory/","file.ext"]
-- > concat (splitPath x) == x
-- > splitPath "test//item/" == ["test//","item/"]
-- > splitPath "test/item/file" == ["test/","item/","file"]
-- > splitPath "" == []
-- > Windows: splitPath "c:\\test\\path" == ["c:\\","test\\","path"]
-- > Posix:   splitPath "/file/test" == ["/","file/","test"]
#endif
splitPath :: FILEPATH_NAME -> [FILEPATH_NAME]
splitPath (OSSTRING_NAME bs) = OSSTRING_NAME <$> C.splitPath bs

#ifdef WINDOWS_DOC
-- | Just as 'splitPath', but don't add the trailing slashes to each element.
--
-- > splitDirectories "/directory/file.ext" == ["/","directory","file.ext"]
-- > splitDirectories "test/file" == ["test","file"]
-- > splitDirectories "/test/file" == ["/","test","file"]
-- > splitDirectories "C:\\test\\file" == ["C:\\", "test", "file"]
-- > Valid x => joinPath (splitDirectories x) `equalFilePath` x
-- > splitDirectories "" == []
-- > splitDirectories "C:\\test\\\\\\file" == ["C:\\", "test", "file"]
-- > splitDirectories "/test///file" == ["/","test","file"]
#elif defined(POSIX_DOC)
-- | Just as 'splitPath', but don't add the trailing slashes to each element.
--
-- > splitDirectories "/directory/file.ext" == ["/","directory","file.ext"]
-- > splitDirectories "test/file" == ["test","file"]
-- > splitDirectories "/test/file" == ["/","test","file"]
-- > Valid x => joinPath (splitDirectories x) `equalFilePath` x
-- > splitDirectories "" == []
-- > splitDirectories "/test///file" == ["/","test","file"]
#else
-- | Just as 'splitPath', but don't add the trailing slashes to each element.
--
-- >          splitDirectories "/directory/file.ext" == ["/","directory","file.ext"]
-- >          splitDirectories "test/file" == ["test","file"]
-- >          splitDirectories "/test/file" == ["/","test","file"]
-- > Windows: splitDirectories "C:\\test\\file" == ["C:\\", "test", "file"]
-- >          Valid x => joinPath (splitDirectories x) `equalFilePath` x
-- >          splitDirectories "" == []
-- > Windows: splitDirectories "C:\\test\\\\\\file" == ["C:\\", "test", "file"]
-- >          splitDirectories "/test///file" == ["/","test","file"]
#endif
splitDirectories :: FILEPATH_NAME -> [FILEPATH_NAME]
splitDirectories (OSSTRING_NAME x) = OSSTRING_NAME <$> C.splitDirectories x

#ifdef WINDOWS_DOC
-- | Join path elements back together.
--
-- > joinPath z == foldr (</>) "" z
-- > joinPath ["/","directory/","file.ext"] == "/directory/file.ext"
-- > Valid x => joinPath (splitPath x) == x
-- > joinPath [] == ""
#elif defined(POSIX_DOC)
-- | Join path elements back together.
--
-- > joinPath z == foldr (</>) "" z
-- > joinPath ["/","directory/","file.ext"] == "/directory/file.ext"
-- > Valid x => joinPath (splitPath x) == x
-- > joinPath [] == ""
-- > joinPath ["test","file","path"] == "test/file/path"
#else
-- | Join path elements back together.
--
-- > joinPath z == foldr (</>) "" z
-- > joinPath ["/","directory/","file.ext"] == "/directory/file.ext"
-- > Valid x => joinPath (splitPath x) == x
-- > joinPath [] == ""
-- > Posix: joinPath ["test","file","path"] == "test/file/path"
#endif
joinPath :: [FILEPATH_NAME] -> FILEPATH_NAME
joinPath = foldr (</>) (OSSTRING_NAME mempty)









------------------------
-- File name manipulations


#ifdef WINDOWS_DOC
-- | Equality of two filepaths.
--   If you call @System.Directory.canonicalizePath@
--   first this has a much better chance of working.
--   Note that this doesn't follow symlinks or DOSNAM~1s.
--
-- Similar to 'normalise', this does not expand @".."@, because of symlinks.
--
-- > x == y ==> equalFilePath x y
-- > normalise x == normalise y ==> equalFilePath x y
-- > equalFilePath "foo" "foo/"
-- > not (equalFilePath "/a/../c" "/c")
-- > not (equalFilePath "foo" "/foo")
-- > equalFilePath "foo" "FOO"
-- > not (equalFilePath "C:" "C:/")
#elif defined(POSIX_DOC)
-- | Equality of two filepaths.
--   If you call @System.Directory.canonicalizePath@
--   first this has a much better chance of working.
--   Note that this doesn't follow symlinks or DOSNAM~1s.
--
-- Similar to 'normalise', this does not expand @".."@, because of symlinks.
--
-- > x == y ==> equalFilePath x y
-- > normalise x == normalise y ==> equalFilePath x y
-- > equalFilePath "foo" "foo/"
-- > not (equalFilePath "/a/../c" "/c")
-- > not (equalFilePath "foo" "/foo")
-- > not (equalFilePath "foo" "FOO")
#else
-- | Equality of two filepaths.
--   If you call @System.Directory.canonicalizePath@
--   first this has a much better chance of working.
--   Note that this doesn't follow symlinks or DOSNAM~1s.
--
-- Similar to 'normalise', this does not expand @".."@, because of symlinks.
--
-- >          x == y ==> equalFilePath x y
-- >          normalise x == normalise y ==> equalFilePath x y
-- >          equalFilePath "foo" "foo/"
-- >          not (equalFilePath "/a/../c" "/c")
-- >          not (equalFilePath "foo" "/foo")
-- > Posix:   not (equalFilePath "foo" "FOO")
-- > Windows: equalFilePath "foo" "FOO"
-- > Windows: not (equalFilePath "C:" "C:/")
#endif
equalFilePath :: FILEPATH_NAME -> FILEPATH_NAME -> Bool
equalFilePath (OSSTRING_NAME p1) (OSSTRING_NAME p2) = C.equalFilePath p1 p2

#ifdef WINDOWS_DOC
-- | Contract a filename, based on a relative path. Note that the resulting path
--   will never introduce @..@ paths, as the presence of symlinks means @..\/b@
--   may not reach @a\/b@ if it starts from @a\/c@. For a worked example see
--   <http://neilmitchell.blogspot.co.uk/2015/10/filepaths-are-subtle-symlinks-are-hard.html this blog post>.
--
--   The corresponding @makeAbsolute@ function can be found in
--   @System.Directory@.
--
-- > makeRelative "/directory" "/directory/file.ext" == "file.ext"
-- > Valid x => makeRelative (takeDirectory x) x `equalFilePath` takeFileName x
-- > makeRelative x x == "."
-- > Valid x y => equalFilePath x y || (isRelative x && makeRelative y x == x) || equalFilePath (y </> makeRelative y x) x
-- > makeRelative "C:\\Home" "c:\\home\\bob" == "bob"
-- > makeRelative "C:\\Home" "c:/home/bob" == "bob"
-- > makeRelative "C:\\Home" "D:\\Home\\Bob" == "D:\\Home\\Bob"
-- > makeRelative "C:\\Home" "C:Home\\Bob" == "C:Home\\Bob"
-- > makeRelative "/Home" "/home/bob" == "bob"
-- > makeRelative "/" "//" == "//"
#elif defined(POSIX_DOC)
-- | Contract a filename, based on a relative path. Note that the resulting path
--   will never introduce @..@ paths, as the presence of symlinks means @..\/b@
--   may not reach @a\/b@ if it starts from @a\/c@. For a worked example see
--   <http://neilmitchell.blogspot.co.uk/2015/10/filepaths-are-subtle-symlinks-are-hard.html this blog post>.
--
--   The corresponding @makeAbsolute@ function can be found in
--   @System.Directory@.
--
-- > makeRelative "/directory" "/directory/file.ext" == "file.ext"
-- > Valid x => makeRelative (takeDirectory x) x `equalFilePath` takeFileName x
-- > makeRelative x x == "."
-- > Valid x y => equalFilePath x y || (isRelative x && makeRelative y x == x) || equalFilePath (y </> makeRelative y x) x
-- > makeRelative "/Home" "/home/bob" == "/home/bob"
-- > makeRelative "/home/" "/home/bob/foo/bar" == "bob/foo/bar"
-- > makeRelative "/fred" "bob" == "bob"
-- > makeRelative "/file/test" "/file/test/fred" == "fred"
-- > makeRelative "/file/test" "/file/test/fred/" == "fred/"
-- > makeRelative "some/path" "some/path/a/b/c" == "a/b/c"
#else
-- | Contract a filename, based on a relative path. Note that the resulting path
--   will never introduce @..@ paths, as the presence of symlinks means @..\/b@
--   may not reach @a\/b@ if it starts from @a\/c@. For a worked example see
--   <http://neilmitchell.blogspot.co.uk/2015/10/filepaths-are-subtle-symlinks-are-hard.html this blog post>.
--
--   The corresponding @makeAbsolute@ function can be found in
--   @System.Directory@.
--
-- >          makeRelative "/directory" "/directory/file.ext" == "file.ext"
-- >          Valid x => makeRelative (takeDirectory x) x `equalFilePath` takeFileName x
-- >          makeRelative x x == "."
-- >          Valid x y => equalFilePath x y || (isRelative x && makeRelative y x == x) || equalFilePath (y </> makeRelative y x) x
-- > Windows: makeRelative "C:\\Home" "c:\\home\\bob" == "bob"
-- > Windows: makeRelative "C:\\Home" "c:/home/bob" == "bob"
-- > Windows: makeRelative "C:\\Home" "D:\\Home\\Bob" == "D:\\Home\\Bob"
-- > Windows: makeRelative "C:\\Home" "C:Home\\Bob" == "C:Home\\Bob"
-- > Windows: makeRelative "/Home" "/home/bob" == "bob"
-- > Windows: makeRelative "/" "//" == "//"
-- > Posix:   makeRelative "/Home" "/home/bob" == "/home/bob"
-- > Posix:   makeRelative "/home/" "/home/bob/foo/bar" == "bob/foo/bar"
-- > Posix:   makeRelative "/fred" "bob" == "bob"
-- > Posix:   makeRelative "/file/test" "/file/test/fred" == "fred"
-- > Posix:   makeRelative "/file/test" "/file/test/fred/" == "fred/"
-- > Posix:   makeRelative "some/path" "some/path/a/b/c" == "a/b/c"
#endif
makeRelative :: FILEPATH_NAME -> FILEPATH_NAME -> FILEPATH_NAME
makeRelative (OSSTRING_NAME root) (OSSTRING_NAME path) = OSSTRING_NAME $ C.makeRelative root path

#ifdef WINDOWS_DOC
-- | Normalise a file
--
-- * \/\/ outside of the drive can be made blank
--
-- * \/ -> 'pathSeparator'
--
-- * .\/ -> \"\"
--
-- Does not remove @".."@, because of symlinks.
--
-- > normalise "c:\\file/bob\\" == "C:\\file\\bob\\"
-- > normalise "c:\\" == "C:\\"
-- > normalise "C:.\\" == "C:"
-- > normalise "\\\\server\\test" == "\\\\server\\test"
-- > normalise "//server/test" == "\\\\server\\test"
-- > normalise "c:/file" == "C:\\file"
-- > normalise "/file" == "\\file"
-- > normalise "\\" == "\\"
-- > normalise "/./" == "\\"
-- > normalise "." == "."
#elif defined(POSIX_DOC)
-- | Normalise a file
--
-- * \/\/ outside of the drive can be made blank
--
-- * \/ -> 'pathSeparator'
--
-- * .\/ -> \"\"
--
-- Does not remove @".."@, because of symlinks.
--
-- > normalise "/file/\\test////" == "/file/\\test/"
-- > normalise "/file/./test" == "/file/test"
-- > normalise "/test/file/../bob/fred/" == "/test/file/../bob/fred/"
-- > normalise "../bob/fred/" == "../bob/fred/"
-- > normalise "/a/../c" == "/a/../c"
-- > normalise "./bob/fred/" == "bob/fred/"
-- > normalise "." == "."
-- > normalise "./" == "./"
-- > normalise "./." == "./"
-- > normalise "/./" == "/"
-- > normalise "/" == "/"
-- > normalise "bob/fred/." == "bob/fred/"
-- > normalise "//home" == "/home"
#else
-- | Normalise a file
--
-- * \/\/ outside of the drive can be made blank
--
-- * \/ -> 'pathSeparator'
--
-- * .\/ -> \"\"
--
-- Does not remove @".."@, because of symlinks.
--
-- > Posix:   normalise "/file/\\test////" == "/file/\\test/"
-- > Posix:   normalise "/file/./test" == "/file/test"
-- > Posix:   normalise "/test/file/../bob/fred/" == "/test/file/../bob/fred/"
-- > Posix:   normalise "../bob/fred/" == "../bob/fred/"
-- > Posix:   normalise "/a/../c" == "/a/../c"
-- > Posix:   normalise "./bob/fred/" == "bob/fred/"
-- > Windows: normalise "c:\\file/bob\\" == "C:\\file\\bob\\"
-- > Windows: normalise "c:\\" == "C:\\"
-- > Windows: normalise "C:.\\" == "C:"
-- > Windows: normalise "\\\\server\\test" == "\\\\server\\test"
-- > Windows: normalise "//server/test" == "\\\\server\\test"
-- > Windows: normalise "c:/file" == "C:\\file"
-- > Windows: normalise "/file" == "\\file"
-- > Windows: normalise "\\" == "\\"
-- > Windows: normalise "/./" == "\\"
-- >          normalise "." == "."
-- > Posix:   normalise "./" == "./"
-- > Posix:   normalise "./." == "./"
-- > Posix:   normalise "/./" == "/"
-- > Posix:   normalise "/" == "/"
-- > Posix:   normalise "bob/fred/." == "bob/fred/"
-- > Posix:   normalise "//home" == "/home"
#endif
normalise :: FILEPATH_NAME -> FILEPATH_NAME
normalise (OSSTRING_NAME filepath) = OSSTRING_NAME $ C.normalise filepath


#ifdef WINDOWS_DOC
-- | Is a filepath valid, i.e. could you create a file like it? This function checks for invalid names,
--   and invalid characters, but does not check if length limits are exceeded, as these are typically
--   filesystem dependent.
--
-- > isValid "" == False
-- > isValid "\0" == False
-- > isValid "c:\\test" == True
-- > isValid "c:\\test:of_test" == False
-- > isValid "test*" == False
-- > isValid "c:\\test\\nul" == False
-- > isValid "c:\\test\\prn.txt" == False
-- > isValid "c:\\nul\\file" == False
-- > isValid "\\\\" == False
-- > isValid "\\\\\\foo" == False
-- > isValid "\\\\?\\D:file" == False
-- > isValid "foo\tbar" == False
-- > isValid "nul .txt" == False
-- > isValid " nul.txt" == True
#elif defined(POSIX_DOC)
-- | Is a filepath valid, i.e. could you create a file like it? This function checks for invalid names,
--   and invalid characters, but does not check if length limits are exceeded, as these are typically
--   filesystem dependent.
--
-- > isValid "" == False
-- > isValid "\0" == False
-- > isValid "/random_ path:*" == True
-- > isValid x == not (null x)
#else
-- | Is a filepath valid, i.e. could you create a file like it? This function checks for invalid names,
--   and invalid characters, but does not check if length limits are exceeded, as these are typically
--   filesystem dependent.
--
-- >          isValid "" == False
-- >          isValid "\0" == False
-- > Posix:   isValid "/random_ path:*" == True
-- > Posix:   isValid x == not (null x)
-- > Windows: isValid "c:\\test" == True
-- > Windows: isValid "c:\\test:of_test" == False
-- > Windows: isValid "test*" == False
-- > Windows: isValid "c:\\test\\nul" == False
-- > Windows: isValid "c:\\test\\prn.txt" == False
-- > Windows: isValid "c:\\nul\\file" == False
-- > Windows: isValid "\\\\" == False
-- > Windows: isValid "\\\\\\foo" == False
-- > Windows: isValid "\\\\?\\D:file" == False
-- > Windows: isValid "foo\tbar" == False
-- > Windows: isValid "nul .txt" == False
-- > Windows: isValid " nul.txt" == True
#endif
isValid :: FILEPATH_NAME -> Bool
isValid (OSSTRING_NAME filepath) = C.isValid filepath


#ifdef WINDOWS_DOC
-- | Take a filepath and make it valid; does not change already valid filepaths.
--
-- > isValid (makeValid x)
-- > isValid x ==> makeValid x == x
-- > makeValid "" == "_"
-- > makeValid "file\0name" == "file_name"
-- > makeValid "c:\\already\\/valid" == "c:\\already\\/valid"
-- > makeValid "c:\\test:of_test" == "c:\\test_of_test"
-- > makeValid "test*" == "test_"
-- > makeValid "c:\\test\\nul" == "c:\\test\\nul_"
-- > makeValid "c:\\test\\prn.txt" == "c:\\test\\prn_.txt"
-- > makeValid "c:\\test/prn.txt" == "c:\\test/prn_.txt"
-- > makeValid "c:\\nul\\file" == "c:\\nul_\\file"
-- > makeValid "\\\\\\foo" == "\\\\drive"
-- > makeValid "\\\\?\\D:file" == "\\\\?\\D:\\file"
-- > makeValid "nul .txt" == "nul _.txt"
#elif defined(POSIX_DOC)
-- | Take a filepath and make it valid; does not change already valid filepaths.
--
-- > isValid (makeValid x)
-- > isValid x ==> makeValid x == x
-- > makeValid "" == "_"
-- > makeValid "file\0name" == "file_name"
#else
-- | Take a filepath and make it valid; does not change already valid filepaths.
--
-- > isValid (makeValid x)
-- > isValid x ==> makeValid x == x
-- > makeValid "" == "_"
-- > makeValid "file\0name" == "file_name"
-- > Windows: makeValid "c:\\already\\/valid" == "c:\\already\\/valid"
-- > Windows: makeValid "c:\\test:of_test" == "c:\\test_of_test"
-- > Windows: makeValid "test*" == "test_"
-- > Windows: makeValid "c:\\test\\nul" == "c:\\test\\nul_"
-- > Windows: makeValid "c:\\test\\prn.txt" == "c:\\test\\prn_.txt"
-- > Windows: makeValid "c:\\test/prn.txt" == "c:\\test/prn_.txt"
-- > Windows: makeValid "c:\\nul\\file" == "c:\\nul_\\file"
-- > Windows: makeValid "\\\\\\foo" == "\\\\drive"
-- > Windows: makeValid "\\\\?\\D:file" == "\\\\?\\D:\\file"
-- > Windows: makeValid "nul .txt" == "nul _.txt"
#endif
makeValid :: FILEPATH_NAME -> FILEPATH_NAME
makeValid (OSSTRING_NAME path) = OSSTRING_NAME $ C.makeValid path


#ifdef WINDOWS_DOC
-- | Is a path relative, or is it fixed to the root?
--
-- > isRelative "path\\test" == True
-- > isRelative "c:\\test" == False
-- > isRelative "c:test" == True
-- > isRelative "c:\\" == False
-- > isRelative "c:/" == False
-- > isRelative "c:" == True
-- > isRelative "\\\\foo" == False
-- > isRelative "\\\\?\\foo" == False
-- > isRelative "\\\\?\\UNC\\foo" == False
-- > isRelative "/foo" == True
-- > isRelative "\\foo" == True
--
-- According to [1]:
--
-- * "A UNC name of any format [is never relative]."
--
-- * "You cannot use the "\\?\" prefix with a relative path."
#elif defined(POSIX_DOC)
-- | Is a path relative, or is it fixed to the root?
--
-- > isRelative "test/path" == True
-- > isRelative "/test" == False
-- > isRelative "/" == False
#else
-- | Is a path relative, or is it fixed to the root?
--
-- > Windows: isRelative "path\\test" == True
-- > Windows: isRelative "c:\\test" == False
-- > Windows: isRelative "c:test" == True
-- > Windows: isRelative "c:\\" == False
-- > Windows: isRelative "c:/" == False
-- > Windows: isRelative "c:" == True
-- > Windows: isRelative "\\\\foo" == False
-- > Windows: isRelative "\\\\?\\foo" == False
-- > Windows: isRelative "\\\\?\\UNC\\foo" == False
-- > Windows: isRelative "/foo" == True
-- > Windows: isRelative "\\foo" == True
-- > Posix:   isRelative "test/path" == True
-- > Posix:   isRelative "/test" == False
-- > Posix:   isRelative "/" == False
--
-- According to [1]:
--
-- * "A UNC name of any format [is never relative]."
--
-- * "You cannot use the "\\?\" prefix with a relative path."
#endif
isRelative :: FILEPATH_NAME -> Bool
isRelative (OSSTRING_NAME x) = C.isRelative x


-- | @not . 'isRelative'@
--
-- > isAbsolute x == not (isRelative x)
isAbsolute :: FILEPATH_NAME -> Bool
isAbsolute (OSSTRING_NAME x) = C.isAbsolute x


-- things not defined in os-string

#ifdef WINDOWS
encodeFS :: String -> IO WindowsPath
encodeFS = fmap WindowsString . encodeWithBaseWindows

decodeFS :: WindowsPath -> IO String
decodeFS (WindowsString x) = decodeWithBaseWindows x
#elif defined(POSIX)
encodeFS :: String -> IO PosixPath
encodeFS = fmap PosixString . encodeWithBasePosix

decodeFS :: PosixPath -> IO String
decodeFS (PosixString x) = decodeWithBasePosix x
#else
encodeFS :: String -> IO OsPath
#if defined(mingw32_HOST_OS) || defined(__MINGW32__)
encodeFS = fmap (OsString . WindowsString) . encodeWithBaseWindows
#else
encodeFS = fmap (OsString . PosixString) . encodeWithBasePosix
#endif

decodeFS :: OsPath -> IO String
#if defined(mingw32_HOST_OS) || defined(__MINGW32__)
decodeFS (OsString (WindowsString x)) = decodeWithBaseWindows x
#else
decodeFS (OsString (PosixString x)) = decodeWithBasePosix x
#endif

#endif

