-- |
-- Module      : Foundation.VFS.Path
-- License     : BSD-style
-- Maintainer  : foundation
-- Stability   : experimental
-- Portability : portable
--

{-# LANGUAGE FlexibleContexts #-}

module Foundation.VFS.Path
    (
      -- * Path class
      Path(..)

    , parent
    , filename
    , prefix
    , suffix
    ) where

import Basement.Compat.Base

-- $setup
-- >>> import Basement.Compat.Base
-- >>> import Foundation.VFS.FilePath
-- >>> import Foundation.VFS.Path

-- | Path type class
--
-- defines the Path associated types and basic functions to implement related
-- to the path manipulation
--
-- # TODO, add missing enhancement:
--
-- @
-- splitExtension :: PathEnt path -> (PathEnt path, PathEnt path)
-- addExtension  :: PathEnt path -> PathEnt path -> PathEnt path
-- (<.>) :: path -> PathEnt path -> path
-- (-<.>) :: path -> PathEnt path -> path
-- @
--
class Path path where
    -- | the associated PathEntity of the given `path`
    -- this type is the minimal element contained in the Path
    -- a Path is not a collection but it is possible to see this
    -- associated type equivalent to the `Foundation.Collection.Element` type family
    type PathEnt path

    -- | the associated prefix of the given `path`
    --
    -- in the case of a `Foundation.VFS.FilePath`, it is a void (i.e. `()`)
    -- in the case of a `Foundation.VFS.URI`, it is the schema, host, port...
    type PathPrefix path

    -- | the associated suffix of the given path
    --
    -- in the case of the `Foundation.VFS.FilePath`, it is a void (i.e. `()`)
    -- in the case of the `Foundation.VFS.URI`, it is a the query, the fragment
    type PathSuffix path

    -- | join a path entity to a given path
    (</>) :: path -> PathEnt path -> path

    -- | split the path into the associated elements
    splitPath :: path -> ( PathPrefix path
                         , [PathEnt path]
                         , PathSuffix path
                         )

    -- | build the path from the associated elements
    buildPath :: ( PathPrefix path
                 , [PathEnt path]
                 , PathSuffix path
                 )
              -> path


-- | parent is only going to drop the filename.
--
-- if you actually want to reference to the parent directory, simply uses:
--
-- @
-- parent "." /= "." </> ".."
-- @
--
-- >>> parent ("foo.hs" :: FilePath)
-- .
--
-- >>> parent ("foo/bar/baz.hs" :: FilePath)
-- foo/bar
parent :: Path path => path -> path
parent path = buildPath (p, init ps, s)
  where
    (p, ps, s) = splitPath path


-- | get the filename of the given path
--
-- If there is no filename, you will receive the 'mempty' of the 'PathEnt'
--
-- >>> filename ("foo.hs" :: FilePath)
-- foo.hs
--
-- >>> filename ("foo/bar/baz.hs" :: FilePath)
-- baz.hs
filename :: (Path path, Monoid (PathEnt path)) => path -> PathEnt path
filename path = case ps of
    [] -> mempty
    _  -> last ps
  where
    (_, ps , _) = splitPath path

-- TODO: this might be better in Sequential ?
init :: [a] -> [a]
init [] = []
init [_] = []
init (x:xs) = x : init xs

-- TODO: this might be better in Sequential ?
last :: [a] -> a
last [] = undefined
last [x] = x
last (_:xs) = last xs


-- | get the path prefix information
--
-- >>> prefix ("/home/tab" :: FilePath)
-- Absolute
--
-- >>> prefix ("home/tab" :: FilePath)
-- Relative
--
-- or for URI (TODO, not yet accurate)
--
-- @
-- prefix "http://github.com/vincenthz/hs-foundation?w=1"
--    == URISchema http Nothing Nothing "github.com" Nothing
-- @
prefix :: Path path => path -> PathPrefix path
prefix p = pre
  where
    (pre, _, _) = splitPath p

-- | get the path suffix information
--
-- >>> suffix ("/home/tab" :: FilePath)
-- ()
--
-- or for URI (TODO, not yet accurate)
--
-- @
-- suffix "http://github.com/vincenthz/hs-foundation?w=1"
--    == URISuffix (["w", "1"], Nothing)
-- @
suffix :: Path path => path -> PathSuffix path
suffix p = suf
  where
    (_, _, suf) = splitPath p