module Filesystem.Path
	( FilePath
	, empty
	
	
	, null
	, root
	, directory
	, parent
	, filename
	, dirname
	, basename
	, absolute
	, relative
	
	
	, append
	, (</>)
	, concat
	, commonPrefix
	, stripPrefix
	, collapse
	, splitDirectories
	
	
	, extension
	, extensions
	, hasExtension
	
	, addExtension
	, (<.>)
	, dropExtension
	, replaceExtension
	
	, addExtensions
	, dropExtensions
	, replaceExtensions
	
	, splitExtension
	, splitExtensions
	) where
import           Prelude hiding (FilePath, concat, null)
import           Data.List (foldl')
import           Data.Maybe (isNothing)
import qualified Data.Monoid as M
import qualified Data.Text as T
import           Filesystem.Path.Internal
instance M.Monoid FilePath where
	mempty = empty
	mappend = append
	mconcat = concat
null :: FilePath -> Bool
null = (== empty)
root :: FilePath -> FilePath
root p = empty { pathRoot = pathRoot p }
directory :: FilePath -> FilePath
directory p = empty
	{ pathRoot = pathRoot p
	, pathDirectories = let
		starts = map Just [dot, dots]
		dot' | safeHead (pathDirectories p) `elem` starts = []
		     | isNothing (pathRoot p) = [dot]
		     | otherwise = []
		in dot' ++ pathDirectories p
	}
parent :: FilePath -> FilePath
parent p = empty
	{ pathRoot = pathRoot p
	, pathDirectories = let
		starts = map Just [dot, dots]
		directories = if null (filename p)
			then safeInit (pathDirectories p)
			else pathDirectories p
		
		dot' | safeHead directories `elem` starts = []
		     | isNothing (pathRoot p) = [dot]
		     | otherwise = []
		in dot' ++ directories
	}
filename :: FilePath -> FilePath
filename p = empty
	{ pathBasename = pathBasename p
	, pathExtensions = pathExtensions p
	}
dirname :: FilePath -> FilePath
dirname p = case reverse (pathDirectories p) of
	[] -> FilePath Nothing [] Nothing []
	(d:_) -> case parseFilename d of
		(base, exts) -> FilePath Nothing [] base exts
basename :: FilePath -> FilePath
basename p = empty
	{ pathBasename = pathBasename p
	}
absolute :: FilePath -> Bool
absolute p = case pathRoot p of
	Just RootPosix -> True
	Just (RootWindowsVolume _) -> True
	_ -> False
relative :: FilePath -> Bool
relative p = case pathRoot p of
	Just _ -> False
	_ -> True
append :: FilePath -> FilePath -> FilePath
append x y = cased where
	cased = case pathRoot y of
		Just RootPosix -> y
		Just (RootWindowsVolume _) -> y
		Just RootWindowsCurrentVolume -> case pathRoot x of
			Just (RootWindowsVolume _) -> y { pathRoot = pathRoot x }
			_ -> y
		Nothing -> xy
	xy = y
		{ pathRoot = pathRoot x
		, pathDirectories = directories
		}
	directories = xDirectories ++ pathDirectories y
	xDirectories = (pathDirectories x ++) $ if null (filename x)
		then []
		else [filenameChunk x]
(</>) :: FilePath -> FilePath -> FilePath
(</>) = append
concat :: [FilePath] -> FilePath
concat [] = empty
concat ps = foldr1 append ps
commonPrefix :: [FilePath] -> FilePath
commonPrefix [] = empty
commonPrefix ps = foldr1 step ps where
	step x y = if pathRoot x /= pathRoot y
		then empty
		else let cs = commonDirectories x y in
			if cs /= pathDirectories x || pathBasename x /= pathBasename y
				then empty { pathRoot = pathRoot x, pathDirectories = cs }
				else let exts = commonExtensions x y in
					x { pathExtensions = exts }
	
	commonDirectories x y = common (pathDirectories x) (pathDirectories y)
	commonExtensions x y = common (pathExtensions x) (pathExtensions y)
	
	common [] _ = []
	common _ [] = []
	common (x:xs) (y:ys) = if x == y
		then x : common xs ys
		else []
stripPrefix :: FilePath -> FilePath -> Maybe FilePath
stripPrefix x y = if pathRoot x /= pathRoot y
	then case pathRoot x of
		Nothing -> Just y
		Just _ -> Nothing
	else do
		dirs <- strip (pathDirectories x) (pathDirectories y)
		case dirs of
			[] -> case (pathBasename x, pathBasename y) of
				(Nothing, Nothing) -> do
					exts <- strip (pathExtensions x) (pathExtensions y)
					return (y { pathRoot = Nothing, pathDirectories = dirs, pathExtensions = exts })
				(Nothing, Just _) -> case pathExtensions x of
					[] -> Just (y { pathRoot = Nothing, pathDirectories = dirs })
					_ -> Nothing
				(Just x_b, Just y_b) | x_b == y_b -> do
					exts <- strip (pathExtensions x) (pathExtensions y)
					return (empty { pathExtensions = exts })
				_ -> Nothing
			_ -> case (pathBasename x, pathExtensions x) of
				(Nothing, []) -> Just (y { pathRoot = Nothing, pathDirectories = dirs })
				_ -> Nothing
strip :: Eq a => [a] -> [a] -> Maybe [a]
strip [] ys = Just ys
strip _ [] = Nothing
strip (x:xs) (y:ys) = if x == y
	then strip xs ys
	else Nothing
collapse :: FilePath -> FilePath
collapse p = p { pathDirectories = reverse newDirs } where
	(_, newDirs) = foldl' step (True, []) (pathDirectories p)
	
	step (True, acc) c = (False, c:acc)
	step (_, acc) c | c == dot = (False, acc)
	step (_, acc) c | c == dots = case acc of
		[] -> (False, c:acc)
		(h:ts) | h == dot -> (False, c:ts)
		       | h == dots -> (False, c:acc)
		       | otherwise -> (False, ts)
	step (_, acc) c = (False, c:acc)
splitDirectories :: FilePath -> [FilePath]
splitDirectories p = rootName ++ dirNames ++ fileName where
	rootName = case pathRoot p of
		Nothing -> []
		r -> [asFile (rootChunk r)]
	dirNames = map asFile (pathDirectories p)
	fileName = case (pathBasename p, pathExtensions p) of
		(Nothing, []) -> []
		_ -> [filename p]
	
	asFile :: Chunk -> FilePath
	asFile c = case parseFilename c of
		(base, exts) -> empty
			{ pathBasename = base
			, pathExtensions = exts
			}
extension :: FilePath -> Maybe T.Text
extension p = case extensions p of
	[] -> Nothing
	es -> Just (last es)
extensions :: FilePath -> [T.Text]
extensions = map unescape' . pathExtensions
hasExtension :: FilePath -> T.Text -> Bool
hasExtension p e = extension p == Just e
addExtension :: FilePath -> T.Text -> FilePath
addExtension p ext = addExtensions p [ext]
addExtensions :: FilePath -> [T.Text] -> FilePath
addExtensions p exts = p { pathExtensions = newExtensions } where
	newExtensions = pathExtensions p ++ map escape exts
(<.>) :: FilePath -> T.Text -> FilePath
(<.>) = addExtension
dropExtension :: FilePath -> FilePath
dropExtension p = p { pathExtensions = safeInit (pathExtensions p) }
dropExtensions :: FilePath -> FilePath
dropExtensions p = p { pathExtensions = [] }
replaceExtension :: FilePath -> T.Text -> FilePath
replaceExtension = addExtension . dropExtension
replaceExtensions :: FilePath -> [T.Text] -> FilePath
replaceExtensions = addExtensions . dropExtensions
splitExtension :: FilePath -> (FilePath, Maybe T.Text)
splitExtension p = (dropExtension p, extension p)
splitExtensions :: FilePath -> (FilePath, [T.Text])
splitExtensions p = (dropExtensions p, extensions p)
safeInit :: [a] -> [a]
safeInit xs = case xs of
	[] -> []
	_ -> init xs
safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x:_) = Just x