{-# OPTIONS_GHC -optc-DBACKSLASH=92 #-}
{-# OPTIONS_GHC -optc-DSLASH=47 #-}
{-# OPTIONS_GHC -optc-DDOUBLE_QUOTE=34 #-}
{-# OPTIONS_GHC -optc-DCOLON=58 #-}
{-# OPTIONS_GHC -optc-DSEMICOLON=59 #-}
{-# OPTIONS_GHC -optc-DDOT=46 #-}
{-# OPTIONS_GHC -optc-DBUF_EXT_SIZ=4 #-}
{-# LINE 1 "Z/IO/FileSystem/FilePath.hsc" #-}
{-|
Module      : Z.IO.FileSystem.FilePath
Description : file path toolbox
Copyright   : (c) Dong Han, 2017-2020
License     : BSD
Maintainer  : winterland1989@gmail.com
Stability   : experimental
Portability : non-portable

This module provides file path manipulations using <https://likle.github.io/cwalk/ cwalk>,
both unix and window style path is accepted. Default style is choosen during compile time,
but can be changed during runtime.
-}

module Z.IO.FileSystem.FilePath
 ( -- * Paths
    splitBaseName, changeBaseName
  , splitRoot, changeRoot
  , splitSegments
  , isAbsolute
  , isRelative
  , join
  , concat
  , normalize
  , intersection
  , absolute
  , relative
  -- * Extensions
  , splitExtension, changeExtension
  -- * Path Style
  , PathStyle(..)
  , pathStyle
  , getPathStyle, setPathStyle
  , pathSeparator, searchPathSeparator, extensionSeparator
  -- * Search path
  , getSearchPath
 ) where


import           Data.Word
import qualified Data.List as List
import           GHC.Generics
import           Z.Data.CBytes (CBytes(CB), allocCBytesUnsafe, withCBytesUnsafe, withCBytesListUnsafe)
import qualified Z.Data.CBytes as CB
import qualified Z.Data.Text.ShowT as T
import           Z.Data.JSON (FromValue, ToValue, EncodeJSON)
import qualified Z.Data.Vector.Base as V
import qualified Z.Data.Vector      as V
import           Z.Foreign
import           Z.IO.Environment   (getEnv')
import Prelude hiding (concat)


-- \

-- /

-- "

-- :

-- ;

-- .




data PathStyle = WindowsStyle   -- ^ Use backslashes as a separator and volume for the root.
               | UnixStyle      -- ^ Use slashes as a separator and a slash for the root.
    deriving (Show, Eq, Ord, Generic)
    deriving anyclass (T.ShowT, FromValue, ToValue, EncodeJSON)

enumToPathStyle_ :: CInt -> PathStyle
enumToPathStyle_ (0) = WindowsStyle
{-# LINE 76 "Z/IO/FileSystem/FilePath.hsc" #-}
enumToPathStyle_ _ = UnixStyle

pathStyleToEnum_ :: PathStyle -> CInt
pathStyleToEnum_ WindowsStyle = (0)
{-# LINE 80 "Z/IO/FileSystem/FilePath.hsc" #-}
pathStyleToEnum_ _ = (1)
{-# LINE 81 "Z/IO/FileSystem/FilePath.hsc" #-}

-- | Guesses the path style.
--
-- This function guesses the path style based on a submitted path-string.
-- The guessing will look at the root and the type of slashes contained in the path
-- and return the style which is more likely used in the path. The algorithm checks the following:
--
-- * If the root is longer than 1 character      -> WINDOWS
-- * If the first separator is a backslash       -> WINDOWS
-- * If the first separator is a slash           -> UNIX
-- * If the last segment starts with a dot       -> UNIX
-- * If the last segment contains a dot          -> WINDOWS
-- * If nothing was found to determine the style -> UNIX
--
pathStyle :: CBytes -> IO PathStyle
pathStyle p = enumToPathStyle_ <$> withCBytesUnsafe p cwk_path_guess_style

-- | Gets the path style currently using.
getPathStyle :: IO PathStyle
getPathStyle = enumToPathStyle_ <$> cwk_path_get_style

-- | Configures which path style is used afterwards.
--
-- This function configures which path style is used.
-- call to this function is only required if a non-native behaviour is required. 
-- The style defaults to 'WindowsStyle' on windows builds and to 'UnixStyle' otherwise.
setPathStyle :: PathStyle -> IO ()
setPathStyle = cwk_path_set_style . pathStyleToEnum_

-- | Get the character that separates directories. 
pathSeparator :: IO [Word8]
pathSeparator = do
    s <- getPathStyle
    case s of UnixStyle -> return [(47)]
{-# LINE 115 "Z/IO/FileSystem/FilePath.hsc" #-}
              _         -> return [(47), (92)]
{-# LINE 116 "Z/IO/FileSystem/FilePath.hsc" #-}

-- | The character that is used to separate the entries in the $PATH environment variable.
--
-- * Windows: searchPathSeparator is ASCII @;@
-- * Unix:    searchPathSeparator is ASCII @:@
--
searchPathSeparator :: IO Word8
searchPathSeparator = do
    s <- getPathStyle
    case s of UnixStyle -> return (58)
{-# LINE 126 "Z/IO/FileSystem/FilePath.hsc" #-}
              _         -> return (59)
{-# LINE 127 "Z/IO/FileSystem/FilePath.hsc" #-}

-- | File extension character
--
-- ExtSeparator is ASCII @.@
extensionSeparator :: Word8
extensionSeparator = 46
{-# LINE 133 "Z/IO/FileSystem/FilePath.hsc" #-}

-- | Get the basename of a file path.
--
-- The basename is the last segment of a path. For instance, @logs@ is the basename of the path @\/var\/logs@.
--
-- +--------------------------+---------------+
-- |     Path                 |   Basename    |
-- +--------------------------+---------------+
-- | \/my\/path.txt           | path.txt      |
-- +--------------------------+---------------+
-- | \/my\/path.txt\/         | path.txt      |
-- +--------------------------+---------------+
-- | \/my\/path.txt\/\/\/\/   | path.txt      |
-- +--------------------------+---------------+
-- | file_name                | file_name     |
-- +--------------------------+---------------+
-- | ..                       | ..            |
-- +--------------------------+---------------+
-- | .                        | .             |
-- +--------------------------+---------------+
-- | \/                       | ""            |
-- +--------------------------+---------------+
-- | C:\path\test.txt         | test.txt      |
-- +--------------------------+---------------+
--
splitBaseName :: CBytes
              -> IO (CBytes, CBytes)    -- ^ return (dir, basename)
{-# INLINABLE splitBaseName #-}
splitBaseName p = do
    (off, len) <- withCBytesUnsafe p $ \ pp ->
        allocPrimUnsafe $ \ poff ->
        hs_cwk_path_get_basename pp poff
    if len == 0
    then return (p, CB.empty)
    else return ( CB (V.PrimVector (CB.rawPrimArray p) 0 off)
                , CB (V.PrimVector (CB.rawPrimArray p) off len))


-- | Changes the basename of a file path.
--
-- @
-- > changeBaseName  "foo\/bar.txt" "qux.png"
-- "foo\/qux.png"
-- @
--
changeBaseName :: CBytes
               -> CBytes   -- ^ new base name
               -> IO CBytes
changeBaseName p b = do
    let l = CB.length p + CB.length b + (4)
{-# LINE 183 "Z/IO/FileSystem/FilePath.hsc" #-}
    (p', _) <- withCBytesUnsafe p $ \ pp ->
        withCBytesUnsafe b $ \ pb ->
            allocCBytesUnsafe l $ \ pbuf ->
                cwk_path_change_basename pp pb pbuf (fromIntegral l)
    return p'

-- | Determines the root of a path.
--
-- This function determines the root of a path by finding it’s length.
-- The root comes before the first segment of the path.
-- For example, @C:\\@ is the root of @C:\\folder\\file.txt@.
-- It always starts at the submitted path. If the path has no root, 'CB.empty' will be returned.
--
-- +---------+--------------------------+----------------------+
-- | Style   | Path                     | Root                 |
-- +---------+--------------------------+----------------------+
-- | UNIX    | \/test\/                 | \/                   |
-- +---------+--------------------------+----------------------+
-- | UNIX    | test.txt                 | ""                   |
-- +---------+--------------------------+----------------------+
-- | UNIX    | C:\\test.txt             | ""                   |
-- +---------+--------------------------+----------------------+
-- | UNIX    | \\folder\\               | ""                   |
-- +---------+--------------------------+----------------------+
-- | WINDOWS | \/test.txt               | \/                   |
-- +---------+--------------------------+----------------------+
-- | WINDOWS | \\test.txt               | \\                   |
-- +---------+--------------------------+----------------------+
-- | WINDOWS | C:\\test.txt             | C:\\                 |
-- +---------+--------------------------+----------------------+
-- | WINDOWS | \\\\server\\folder\\data | \\\\server\\folder\\ |
-- +---------+--------------------------+----------------------+
-- | WINDOWS | \\\\.\\folder\\data      | \\\\.\\              |
-- +---------+--------------------------+----------------------+
-- | WINDOWS | \\\\?\\folder\\data      | \\\\?\\              |
-- +---------+--------------------------+----------------------+
-- | WINDOWS | C:test.txt               | C:                   |
-- +---------+--------------------------+----------------------+
-- | WINDOWS | ..\\hello\\world.txt     | ""                   |
-- +---------+--------------------------+----------------------+
--
splitRoot :: CBytes 
          -> IO (CBytes, CBytes) -- ^ return (root, rest path)
{-# INLINABLE splitRoot #-}
splitRoot p = do
    off <- withCBytesUnsafe p hs_cwk_path_get_root
    if off == 0
    then return (CB.empty, p)
    else return ( CB (V.PrimVector (CB.rawPrimArray p) 0 off)
                , CB (V.PrimVector (CB.rawPrimArray p) off (CB.length p - off)))

-- | Changes the root of a file path.
--
-- @
-- > changeBaseName "C:\\\\test.txt" "D:\\\\"    -- windows style
-- "D:\\test.txt"
-- @
--
changeRoot :: CBytes
           -> CBytes   -- ^ new base name
           -> IO CBytes
changeRoot p r = do
    let l = CB.length p + CB.length r + (4)
{-# LINE 246 "Z/IO/FileSystem/FilePath.hsc" #-}
    (p', _) <- withCBytesUnsafe p $ \ pp ->
        withCBytesUnsafe r $ \ pr ->
            allocCBytesUnsafe l $ \ pbuf ->
                cwk_path_change_root pp pr pbuf (fromIntegral l)
    return p'

-- | Split file path into (root, segments, basename) tuple.
--
-- Root may be empty, segments are separated by 'pathSeparator' and never be empty if any.
splitSegments :: CBytes
              -> IO (CBytes, [CBytes]) -- ^ return (root, segments)
{-# INLINABLE splitSegments #-}
splitSegments p = do
    (root, CB seg) <- splitRoot =<< normalize p
    sty <- getPathStyle
    let segs =
            if V.null seg
            then []
            else case sty of
                UnixStyle ->
                    V.splitWith (== (47)) seg
{-# LINE 267 "Z/IO/FileSystem/FilePath.hsc" #-}
                _ ->
                    V.splitWith (\ w -> w == (47) || w == (92)) seg
{-# LINE 269 "Z/IO/FileSystem/FilePath.hsc" #-}
    return (root, (map CB segs))

-- | Determine whether the path is absolute or not.
--
-- This function checks whether the path is an absolute (fully qualified) path or not.
-- A path is considered to be absolute if the root ends with a separator.
--
-- +---------+--------------------------+-----------+
-- | Style   | Path                     | Result    |
-- +---------+--------------------------+-----------+
-- | UNIX    | \/test\/                 | True      |
-- +---------+--------------------------+-----------+
-- | UNIX    | test.txt                 | False     |
-- +---------+--------------------------+-----------+
-- | UNIX    | C:\\test.txt             | False     |
-- +---------+--------------------------+-----------+
-- | UNIX    | \\folder\\               | False     |
-- +---------+--------------------------+-----------+
-- | WINDOWS | \/test.txt               | True      |
-- +---------+--------------------------+-----------+
-- | WINDOWS | \\test.txt               | True      |
-- +---------+--------------------------+-----------+
-- | WINDOWS | C:\\test.txt             | True      |
-- +---------+--------------------------+-----------+
-- | WINDOWS | \\\\server\\folder\\data | True      |
-- +---------+--------------------------+-----------+
-- | WINDOWS | \\\\.\\folder\\data      | True      |
-- +---------+--------------------------+-----------+
-- | WINDOWS | \\\\?\\folder\\data      | True      |
-- +---------+--------------------------+-----------+
-- | WINDOWS | C:test.txt               | False     |
-- +---------+--------------------------+-----------+
-- | WINDOWS | ..\\hello\\world.txt     | False     |
-- +---------+--------------------------+-----------+
--
isAbsolute :: CBytes -> IO Bool
isAbsolute p = (/=0) <$> withCBytesUnsafe p cwk_path_is_absolute

-- | Determine whether the path is relative or not.
--
-- This function checks whether the path is a relative path or not.
-- A path is considered to be relative if the root does not end with a separator.
--
-- +---------+--------------------------+-----------+
-- | Style   | Path                     | Result    |
-- +---------+--------------------------+-----------+
-- | UNIX    | \/test\/                 | False     |
-- +---------+--------------------------+-----------+
-- | UNIX    | test.txt                 | True      |
-- +---------+--------------------------+-----------+
-- | UNIX    | C:\\test.txt             | True      |
-- +---------+--------------------------+-----------+
-- | UNIX    | \\folder\\               | True      |
-- +---------+--------------------------+-----------+
-- | WINDOWS | \/test.txt               | False     |
-- +---------+--------------------------+-----------+
-- | WINDOWS | \\test.txt               | False     |
-- +---------+--------------------------+-----------+
-- | WINDOWS | C:\\test.txt             | False     |
-- +---------+--------------------------+-----------+
-- | WINDOWS | \\\\server\\folder\\data | False     |
-- +---------+--------------------------+-----------+
-- | WINDOWS | \\\\.\\folder\\data      | False     |
-- +---------+--------------------------+-----------+
-- | WINDOWS | \\\\?\\folder\\data      | False     |
-- +---------+--------------------------+-----------+
-- | WINDOWS | C:test.txt               | True      |
-- +---------+--------------------------+-----------+
-- | WINDOWS | ..\\hello\\world.txt     | True      |
-- +---------+--------------------------+-----------+
--
isRelative :: CBytes -> IO Bool
isRelative p = (/=0) <$> withCBytesUnsafe p cwk_path_is_relative

-- | Joins two paths together.
--
-- +---------+-----------------------+---------------------------+--------------------------------------+
-- | Style   | Path A                | Path B                    | Result                               |
-- +---------+-----------------------+---------------------------+--------------------------------------+
-- | UNIX    | hello\/there          | ..\/world                 | hello\/world                         |
-- +---------+-----------------------+---------------------------+--------------------------------------+
-- | UNIX    | \/first               | \/second                  | \/first\/second                      |
-- +---------+-----------------------+---------------------------+--------------------------------------+
-- | UNIX    | hello                 | ..                        | .                                    |
-- +---------+-----------------------+---------------------------+--------------------------------------+
-- | UNIX    | hello\/there          | ..                        | hello                                |
-- +---------+-----------------------+---------------------------+--------------------------------------+
-- | UNIX    | hello                 | there                     | hello\/there                         |
-- +---------+-----------------------+---------------------------+--------------------------------------+
-- | WINDOWS | this\\                | C:\\..\\..\\is\\a\\test\\ | is\\a\\test                          |
-- +---------+-----------------------+---------------------------+--------------------------------------+
-- | WINDOWS | C:\\this\\path        | C:\\is\\a\\test\\         | C:\\this\\path\\C:\\is\\a\\test      |
-- +---------+-----------------------+---------------------------+--------------------------------------+
-- | WINDOWS | C:\\this\\path        | C:\\..\\is\\a\\test\\     | C:\\this\\path\\is\\a\\test          |
-- +---------+-----------------------+---------------------------+--------------------------------------+
-- | WINDOWS | \\\\s1\\unc\\path     | \\\\s2\\unc\\pa           | \\\\s1\\unc\\pa\\s2\\unc\\path       |
-- +---------+-----------------------+---------------------------+--------------------------------------+
--
join :: CBytes -> CBytes -> IO CBytes
join p p2 = do
    let l = CB.length p + CB.length p2 + (4)
{-# LINE 370 "Z/IO/FileSystem/FilePath.hsc" #-}
    (p', _) <- withCBytesUnsafe p $ \ pp ->
        withCBytesUnsafe p2 $ \ pp2 ->
            allocCBytesUnsafe l $ \ pbuf ->
                cwk_path_join pp pp2 pbuf (fromIntegral l)
    return p'

-- | Joins multiple paths together.
--
-- This function generates a new path by joining multiple paths together.
-- It will remove double separators, and unlike 'absolute',
-- it permits the use of multiple relative paths to combine.
concat :: [CBytes] -> IO CBytes
concat ps = do
    (p', _) <- withCBytesListUnsafe ps $ \ pp l -> do
        let l' = sum (List.map (\ p -> CB.length p + (4)) ps)
{-# LINE 385 "Z/IO/FileSystem/FilePath.hsc" #-}
        allocCBytesUnsafe l' $ \ pbuf ->
            hs_cwk_path_join_multiple pp l pbuf (fromIntegral l')
    return p'

-- | Creates a normalized version of the path.
-- The following will be true for the normalized path:
--
-- * "..\/" will be resolved.
-- * ".\/" will be removed.
-- * double separators will be fixed with a single separator.
-- * separator suffixes will be removed.
--
-- +--------------------------------------------------+-------------------+
-- | Input                                            | Output            |
-- +--------------------------------------------------+-------------------+
-- | \/var                                            | \/var             |
-- +--------------------------------------------------+-------------------+
-- | \/var\/logs\/test\/..\/..\/                      | \/var             |
-- +--------------------------------------------------+-------------------+
-- | \/var\/logs\/test\/..\/..\/..\/..\/..\/..\/      | \/                |
-- +--------------------------------------------------+-------------------+
-- | rel\/..\/..\/                                    | ..                |
-- +--------------------------------------------------+-------------------+
-- | \/var\/\/\/\/logs\/\/test\/                      | \/var\/logs\/test |
-- +--------------------------------------------------+-------------------+
-- | \/var\/.\/.\/.\/.\/                              | \/var             |
-- +--------------------------------------------------+-------------------+
-- | \/var\/.\/logs\/.\/\/test\/..\/\/..\/\/\/\/\/\/  | \/var             |
-- +--------------------------------------------------+-------------------+
--
normalize :: CBytes -> IO CBytes
normalize p = do
    let l = CB.length p + (4)
{-# LINE 418 "Z/IO/FileSystem/FilePath.hsc" #-}
    (p', _) <- withCBytesUnsafe p $ \ pp ->
        allocCBytesUnsafe l $ \ pbuf ->
            cwk_path_normalize pp pbuf (fromIntegral l)
    return p'

-- | Finds common portions in two paths.
--
-- +---------+---------------------------+---------------------------+----------------------+
-- | Style   | Base                      | Other                     | Result               |
-- +---------+---------------------------+---------------------------+----------------------+
-- | UNIX    | \/test\/abc\/..\/foo\/bar | \/test\/foo\/har          | \/test\/abc\/..\/foo |
-- +---------+---------------------------+---------------------------+----------------------+
-- | UNIX    | \/test\/foo\/har          | \/test\/abc\/..\/foo\/bar | \/test\/foo          |
-- +---------+---------------------------+---------------------------+----------------------+
-- | UNIX    | \/test\/abc.txt           | test\/abc.txt             | ""                   |
-- +---------+---------------------------+---------------------------+----------------------+
-- | UNIX    | \/                        | ""                        | ""                   |
-- +---------+---------------------------+---------------------------+----------------------+
-- | UNIX    | \/this\/\/\/is\/a\/\/test | \/this\/\/is\/a\/\/\/file | \/this\/\/\/is\/a    |
-- +---------+---------------------------+---------------------------+----------------------+
-- | UNIX    | \/this\/is\/a\/test       | \/this\/is\/a\/           | \/this\/is\/a        |
-- +---------+---------------------------+---------------------------+----------------------+
-- | UNIX    | \/this\/is\/a\/test       | \/this\/is\/a             | \/this\/is\/a        |
-- +---------+---------------------------+---------------------------+----------------------+
-- | UNIX    | \/this\/is\/a\/test       | \/this\/is\/a\/string     | \/this\/is\/a        |
-- +---------+---------------------------+---------------------------+----------------------+
-- | WINDOWS | C:\/abc\/test.txt         | C:\/                      | C:\/                 |
-- +---------+---------------------------+---------------------------+----------------------+
-- | WINDOWS | C:\/abc\/test.txt         | C:\/def\/test.txt         | C:\/                 |
-- +---------+---------------------------+---------------------------+----------------------+
-- | WINDOWS | C:\/test\/abc.txt         | D:\/test\/abc.txt         | ""                   |
-- +---------+---------------------------+---------------------------+----------------------+
--
intersection :: CBytes      -- ^ The base path which will be compared with the other path.
             -> CBytes      -- ^ The other path which will compared with the base path.
             -> IO CBytes
intersection p1 p2 = do
    len <- withCBytesUnsafe p1 $ \ pp1 ->
        withCBytesUnsafe p2 $ \ pp2 ->
            cwk_path_get_intersection pp1 pp2
    if len == 0
    then return CB.empty
    else return (CB (V.PrimVector (CB.rawPrimArray p1) 0 (fromIntegral len)))

-- | Generates an absolute path based on a base.
--
-- This function generates an absolute path based on a base path and another path.
-- It is guaranteed to return an absolute path.
-- If the second submitted path is absolute, it will override the base path.
--
-- +----------------------+----------------------+-----------------------+
-- | Base                 | Path                 | Result                |
-- +----------------------+----------------------+-----------------------+
-- | \/hello\/there       | ..\/..\/..\/..\/..\/ | \/                    |
-- +----------------------+----------------------+-----------------------+
-- | \/hello\/\/..\/there | test\/\/thing        | \/there\/test\/thing  |
-- +----------------------+----------------------+-----------------------+
-- | hello\/there         | \/test               | \/test                |
-- +----------------------+----------------------+-----------------------+
-- | hello\/there         | test                 | \/hello\/there\/test  |
-- +----------------------+----------------------+-----------------------+
-- | \/hello\/there       | \/test               | \/test                |
-- +----------------------+----------------------+-----------------------+
-- | \/hello\/there       | ..                   | \/hello               |
-- +----------------------+----------------------+-----------------------+
--
absolute :: CBytes  -- ^ The absolute base path on which the relative path will be applied.
         -> CBytes  -- ^ The relative path which will be applied on the base path.
         -> IO CBytes
absolute p p2 = do
    let l = CB.length p + CB.length p2 + (4)
{-# LINE 489 "Z/IO/FileSystem/FilePath.hsc" #-}
    (p', _) <- withCBytesUnsafe p $ \ pp ->
        withCBytesUnsafe p2 $ \ pp2 ->
            allocCBytesUnsafe l $ \ pbuf ->
                cwk_path_get_absolute pp pp2 pbuf (fromIntegral l)
    return p'

-- | Generates a relative path based on a base.
--
-- This function generates a relative path based on a base path and another path.
-- It determines how to get to the submitted path, starting from the base directory.
--
-- +---------+--------------------------+--------------------------+-----------------+
-- | Style   | Base                     | Path                     | Result          |
-- +---------+--------------------------+--------------------------+-----------------+
-- | UNIX    | \/..\/..\/               | \/..\/..\/               | .               |
-- +---------+--------------------------+--------------------------+-----------------+
-- | UNIX    | \/path\/same             | \/path\/not_same\/ho\/.. | ..\/not_same    |
-- +---------+--------------------------+--------------------------+-----------------+
-- | UNIX    | \/path\/not_same\/ho\/.. | \/path\/same             | ..\/same        |
-- +---------+--------------------------+--------------------------+-----------------+
-- | UNIX    | \/path\/same             | \/path\/same\/ho\/..     | .               |
-- +---------+--------------------------+--------------------------+-----------------+
-- | UNIX    | \/path\/same\/ho\/..     | \/path\/same             | .               |
-- +---------+--------------------------+--------------------------+-----------------+
-- | UNIX    | \/path\/same             | \/path\/same             | .               |
-- +---------+--------------------------+--------------------------+-----------------+
-- | UNIX    | \/path\/long\/one        | \/path\/long\/one\/two   | two             |
-- +---------+--------------------------+--------------------------+-----------------+
-- | UNIX    | \/path\/long\/one\/two   | \/path\/long\/one        | ..              |
-- +---------+--------------------------+--------------------------+-----------------+
-- | UNIX    | .\/this\/is\/path_one    | .\/this\/is\/path_two    | ..\/path_two    |
-- +---------+--------------------------+--------------------------+-----------------+
-- | UNIX    | \/this\/is\/path_one     | \/this\/is\/path_two     | ..\/path_two    |
-- +---------+--------------------------+--------------------------+-----------------+
-- | WINDOWS | C:\/path\/same           | D:\/path\/same           | ""              |
-- +---------+--------------------------+--------------------------+-----------------+
--
relative :: CBytes  -- ^ The base path from which the relative path will start.
         -> CBytes  -- ^ The target path where the relative path will point to.
         -> IO CBytes
relative p p2 = do
    let l = CB.length p + CB.length p2 + (4)
{-# LINE 531 "Z/IO/FileSystem/FilePath.hsc" #-}
    (p', _) <- withCBytesUnsafe p $ \ pp ->
        withCBytesUnsafe p2 $ \ pp2 ->
            allocCBytesUnsafe l $ \ pbuf ->
                cwk_path_get_relative pp pp2 pbuf (fromIntegral l)
    return p'

-- | Split the extension of a file path.
--
-- This function extracts the extension portion of a file path.
--
-- +----------------------------+------------+
-- | Path                       | Result     |
-- +----------------------------+------------+
-- | \/my\/path.txt             | .txt       |
-- +----------------------------+------------+
-- | \/my\/path                 | ""         |
-- +----------------------------+------------+
-- | \/my\/.path                | .path      |
-- +----------------------------+------------+
-- | \/my\/path.                | .          |
-- +----------------------------+------------+
-- | \/my\/path.abc.txt.tests   | .tests     |
-- +----------------------------+------------+
--
splitExtension :: CBytes
               -> IO (CBytes, CBytes) -- ^ return (file, ext)
{-# INLINABLE splitExtension #-}
splitExtension p = do
    (len ,off) <- withCBytesUnsafe p $ \ pp ->
        allocPrimUnsafe $ \ plen ->
            hs_cwk_path_get_extension pp plen
    if off == -1
    then return (p, CB.empty)
    else return ( CB (V.PrimVector (CB.rawPrimArray p) 0 off)
                , CB (V.PrimVector (CB.rawPrimArray p) off len))

-- | Changes the extension of a file path.
--
-- This function changes the extension of a file name.
-- The function will append an extension if the basename does not have an extension,
-- or use the extension as a basename if the path does not have a basename.
--
-- Note:
--
-- * This function does not normalize the resulting path. You can use 'normalize' to do so.
-- * If the new_extension parameter starts with a dot,
--   the first dot will be ignored when the new extension is appended.
--
-- @
-- > changeExtension  "foo\/bar.txt" "png"
-- "foo\/bar.png"
-- @
--
changeExtension :: CBytes  -- ^ The path which will be used to make the change.
                -> CBytes  -- ^ The extension which will be placed within the new path.
                -> IO CBytes
changeExtension p p2 = do
    let l = CB.length p + CB.length p2 + (4)
{-# LINE 589 "Z/IO/FileSystem/FilePath.hsc" #-}
    (p', _) <- withCBytesUnsafe p $ \ pp ->
        withCBytesUnsafe p2 $ \ pp2 ->
            allocCBytesUnsafe l $ \ pbuf ->
                cwk_path_change_extension pp pp2 pbuf (fromIntegral l)
    return p'

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

-- | Get a list of paths in the @$PATH@ variable.
getSearchPath :: IO [CBytes]
getSearchPath = do
    s <- getEnv' "PATH"
    sp <- searchPathSeparator
    return (splitSearchPath s sp)
  where
    splitSearchPath (CB bs) sp = go bs sp
    go bs sp = case V.break (== sp) bs of
        (p, rest)
            | V.null rest -> []
            | otherwise -> g p : go (V.drop 1 rest) sp
    g bs = if V.null bs then CB.singleton (46) else CB bs
{-# LINE 610 "Z/IO/FileSystem/FilePath.hsc" #-}

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

foreign import ccall unsafe hs_cwk_path_get_basename :: BA# Word8 -> MBA# Int -> IO Int
foreign import ccall unsafe cwk_path_change_basename :: BA# Word8 -> BA# Word8 -> MBA# Word8 -> CSize -> IO CSize
-- foreign import ccall unsafe hs_cwk_path_get_dirname :: BA## Word8 -> IO Int
foreign import ccall unsafe hs_cwk_path_get_root :: BA# Word8 -> IO Int
foreign import ccall unsafe cwk_path_change_root :: BA# Word8 -> BA# Word8 -> MBA# Word8 -> CSize -> IO CSize
foreign import ccall unsafe cwk_path_is_absolute :: BA# Word8 -> IO CBool
foreign import ccall unsafe cwk_path_is_relative :: BA# Word8 -> IO CBool
foreign import ccall unsafe cwk_path_join :: BA# Word8 -> BA# Word8 -> MBA# Word8 -> CSize -> IO CSize
foreign import ccall unsafe hs_cwk_path_join_multiple :: BAArray# Word8 -> Int -> MBA# Word8 -> CSize -> IO CSize
foreign import ccall unsafe cwk_path_normalize :: BA# Word8 -> MBA# Word8 -> CSize -> IO CSize
foreign import ccall unsafe cwk_path_get_intersection :: BA# Word8 -> BA# Word8 -> IO CSize
foreign import ccall unsafe cwk_path_get_absolute :: BA# Word8 -> BA# Word8 -> MBA# Word8 -> CSize -> IO CSize
foreign import ccall unsafe cwk_path_get_relative :: BA# Word8 -> BA# Word8 -> MBA# Word8 -> CSize -> IO CSize
foreign import ccall unsafe hs_cwk_path_get_extension :: BA# Word8 -> MBA# CSize -> IO Int
foreign import ccall unsafe cwk_path_change_extension :: BA# Word8 -> BA# Word8 -> MBA# Word8 -> CSize -> IO CSize
foreign import ccall unsafe cwk_path_guess_style :: BA# Word8 -> IO CInt
foreign import ccall unsafe cwk_path_get_style :: IO CInt
foreign import ccall unsafe cwk_path_set_style :: CInt -> IO ()