{-# 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, pathSeparators
  , searchPathSeparator, extensionSeparator
  -- * Search path
  , getSearchPath
 ) where

import           Control.Monad      hiding (join)
import           Data.Word
import           Data.Bits
import qualified Data.List as List
import           GHC.Generics
import qualified Z.Data.CBytes      as CB
import           Z.Data.CBytes      (CBytes(CB), allocCBytesUnsafe, withCBytesUnsafe, withCBytesListUnsafe)
import           Z.Data.JSON        (JSON)
import qualified Z.Data.Text        as T
import qualified Z.Data.Vector.Base as V
import qualified Z.Data.Vector      as V
import           Z.Foreign
import           Z.IO.Environment   (getEnv')
import           Z.IO.Exception
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.Print, JSON)

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

pathStyleToEnum_ :: PathStyle -> CInt
pathStyleToEnum_ WindowsStyle = (0)
{-# LINE 83 "Z/IO/FileSystem/FilePath.hsc" #-}
pathStyleToEnum_ _ = (1)
{-# LINE 84 "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 default character that separates directories. 
pathSeparator :: IO Word8
pathSeparator = do
    s <- getPathStyle
    case s of UnixStyle -> return (47)
{-# LINE 118 "Z/IO/FileSystem/FilePath.hsc" #-}
              _         -> return (92)
{-# LINE 119 "Z/IO/FileSystem/FilePath.hsc" #-}

-- | Get characters that separates directories. 
pathSeparators :: IO [Word8]
pathSeparators = do
    s <- getPathStyle
    case s of UnixStyle -> return [(47)]
{-# LINE 125 "Z/IO/FileSystem/FilePath.hsc" #-}
              _         -> return [(47), (92)]
{-# LINE 126 "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 136 "Z/IO/FileSystem/FilePath.hsc" #-}
              _         -> return (59)
{-# LINE 137 "Z/IO/FileSystem/FilePath.hsc" #-}

-- | File extension character
--
-- ExtSeparator is ASCII @.@
extensionSeparator :: Word8
extensionSeparator = 46
{-# LINE 143 "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 193 "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 256 "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 277 "Z/IO/FileSystem/FilePath.hsc" #-}
                _ ->
                    V.splitWith (\ w -> w == (47) || w == (92)) seg
{-# LINE 279 "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 380 "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 395 "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 428 "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 499 "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.
-- 
-- Note the two arguments must be both absolute or both relative, otherwise an 'InvalidArgument' 
-- will be thrown.
--
-- +---------+--------------------------+--------------------------+-----------------+
-- | 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 :: HasCallStack
         => 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 `unsafeShiftL` 1) + CB.length p2 + (4)
{-# LINE 545 "Z/IO/FileSystem/FilePath.hsc" #-}
    (p', r) <- withCBytesUnsafe p $ \ pp ->
        withCBytesUnsafe p2 $ \ pp2 ->
            allocCBytesUnsafe l $ \ pbuf ->
                cwk_path_get_relative pp pp2 pbuf (fromIntegral l)
    when (r == 0) $
        throwIO (InvalidArgument (IOEInfo "EINVAL" "file path types are different" callStack))
    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 605 "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 626 "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 ()