--

-- Copyright (c) 2012 Mark Dittmer - http://www.markdittmer.org

-- Developed for a Google Summer of Code project - http://gsoc2012.markdittmer.org

--

{-# LANGUAGE MultiParamTypeClasses, TypeSynonymInstances, FlexibleInstances #-}



module System.FSNotify.Path

       ( findFiles

       , findDirs

       , findFilesAndDirs

       , canonicalizeDirPath

       , canonicalizePath

       , hasThisExtension

       ) where



import Control.Monad

import qualified Data.Text as T

import Prelude hiding (FilePath)

import qualified System.Directory as D

import System.FilePath

import System.PosixCompat.Files as PF



getDirectoryContentsPath :: FilePath -> IO [FilePath]

getDirectoryContentsPath path =

  ((map (path </>)) . filter (not . dots) <$> D.getDirectoryContents path) >>= filterM exists

  where

    exists x = (||) <$> D.doesFileExist x <*> D.doesDirectoryExist x

    dots "."  = True

    dots ".." = True

    dots _    = False



fileDirContents :: FilePath -> IO ([FilePath], [FilePath])

fileDirContents path = do

  contents <- getDirectoryContentsPath path

  stats <- mapM getFileStatus contents

  let pairs = zip stats contents

  let files = [ f | (s, f) <- pairs, PF.isRegularFile s]

  let dirs = [ d | (s, d) <- pairs, PF.isDirectory s]

  return (files, dirs)



findAllFiles :: FilePath -> IO [FilePath]

findAllFiles path = do

  (files, dirs) <- fileDirContents path

  nestedFiles <- mapM findAllFiles dirs

  return (files ++ concat nestedFiles)



findImmediateFiles, findImmediateDirs :: FilePath -> IO [FilePath]

findImmediateFiles = fileDirContents >=> mapM D.canonicalizePath . fst

findImmediateDirs  = fileDirContents >=> mapM D.canonicalizePath . snd



findAllDirs :: FilePath -> IO [FilePath]

findAllDirs path = do

  dirs <- findImmediateDirs path

  nestedDirs <- mapM findAllDirs dirs

  return (dirs ++ concat nestedDirs)



-- * Exported functions below this point



findFiles :: Bool -> FilePath -> IO [FilePath]

findFiles True path  = findAllFiles       =<< canonicalizeDirPath path

findFiles False path = findImmediateFiles =<<  canonicalizeDirPath path



findDirs :: Bool -> FilePath -> IO [FilePath]

findDirs True path  = findAllDirs       =<< canonicalizeDirPath path

findDirs False path = findImmediateDirs =<< canonicalizeDirPath path



findFilesAndDirs :: Bool -> FilePath -> IO [FilePath]

findFilesAndDirs False path = getDirectoryContentsPath =<< canonicalizeDirPath path

findFilesAndDirs True path = do

  (files, dirs) <- fileDirContents path

  nestedFilesAndDirs <- concat <$> (mapM (findFilesAndDirs False) dirs)

  return (files ++ dirs ++ nestedFilesAndDirs)



-- | add a trailing slash to ensure the path indicates a directory

addTrailingSlash :: FilePath -> FilePath

addTrailingSlash = addTrailingPathSeparator



canonicalizeDirPath :: FilePath -> IO FilePath

canonicalizeDirPath path = addTrailingSlash `fmap` D.canonicalizePath path



-- | bugfix older version of canonicalizePath (system-fileio <= 0.3.7) loses trailing slash

canonicalizePath :: FilePath -> IO FilePath

canonicalizePath path = let was_dir = null (takeFileName path) in

  if not was_dir then D.canonicalizePath path

  else canonicalizeDirPath path



hasThisExtension :: FilePath -> T.Text -> Bool

hasThisExtension p ext = takeExtension p == T.unpack ext