module System.FilePath.FilePather.FilePathPredicate
(
  FilePathPredicate(..)
) where

import qualified System.FilePath as P
import System.FilePath.FilePather.RecursePredicate
import System.FilePath.FilePather.FilterPredicate
import Control.Monad
import qualified Data.Foldable as F

-- | Functions that are common to predicates that work on 'FilePath' values.
class FilePathPredicate f where
  -- | A predicate that always succeeds.
  always ::
    Monad g =>
    f g
  -- | A predicate that always fails.
  never ::
    Monad g =>
    f g
  -- | Return a predicate that succeeds only if the two given predicates succeed.
  (.&&.) ::
    Monad g =>
    f g
    -> f g
    -> f g
  -- | Return a predicate that succeeds if any of the two given predicates succeed.
  (.||.) ::
    Monad g =>
    f g
    -> f g
    -> f g
  -- | Negates the given predicate.
  (.!.) ::
    Monad g =>
    f g
    -> f g
  -- | Folds the predicates on disjunction.
  anyof ::
    (F.Foldable t, Monad g) =>
    t (f g)
    -> f g
  -- | Folds the predicates on conjunction.
  anyof =
    F.foldr (.||.) never
  allof ::
    (F.Foldable t, Monad g) =>
    t (f g)
    -> f g
  allof =
    F.foldr (.&&.) always
  -- | A predicate that computes its result based on a file name extension.
  extension ::
    Monad g =>
    (FilePath -> Bool)
    -> f g
  -- | A predicate that computes its result based on equivalence to a file name extension. This function matches with and without the preceding extension separator (.).
  extensionEq ::
    Monad g =>
    FilePath
    -> f g
  extensionEq p =
    extension (== p)
  -- | A predicate that computes its result based on equivalence to one of a list of file name extensions.
  extensionOneof ::
    (F.Foldable t, Monad g) =>
    t FilePath
    -> f g
  extensionOneof =
    F.foldr (\a b -> extensionEq a .||. b) never
  -- | A predicate that computes its result based on inequivalence to any of a list of file name extensions.
  extensionNoneof ::
    (F.Foldable t, Monad g) =>
    t FilePath
    -> f g
  extensionNoneof =
    F.foldr (\a b -> (.!.) (extensionEq a) .&&. b) always
  -- | A predicate that computes its result based on a directory.
  directory ::
    Monad g =>
    (FilePath -> Bool)
    -> f g
  -- | A predicate that succeeds if its 'FilePath' has an extension.
  hasExtension ::
    Monad g =>
    f g
  hasExtension =
    (.!.) notHasExtension
  -- | A predicate that succeeds if its 'FilePath' does not have an extension.
  notHasExtension ::
    Monad g =>
    f g
  notHasExtension =
    (.!.) hasExtension
  -- | A predicate that computes its result based on the splitting of a name and extension.
  splitExtension ::
    Monad g =>
    (String -> String -> Bool)
    -> f g
  -- | A predicate that computes its result based on the splitting of a name into directories.
  splitDirectories ::
    Monad g =>
    ([FilePath] -> Bool)
    -> f g
  -- | A predicate that succeeds if its 'FilePath' has a trailing path separator.
  hasTrailingPathSeparator ::
    Monad g =>
    f g
  hasTrailingPathSeparator =
    (.!.) notHasTrailingPathSeparator
  -- | A predicate that succeeds if its 'FilePath' does not have a trailing path separator.
  notHasTrailingPathSeparator::
    Monad g =>
    f g
  notHasTrailingPathSeparator =
    (.!.) hasTrailingPathSeparator
  -- | A predicate that computes its result based on the file name.
  fileName ::
    Monad g =>
    (FilePath -> Bool)
    -> f g
  -- | A predicate that computes its result based on the base name.
  baseName ::
    Monad g =>
    (FilePath -> Bool)
    -> f g
  -- | A predicate that computes its result based on the normalised file name.
  normalise ::
    Monad g =>
    (FilePath -> Bool)
    -> f g
  -- | A predicate that computes its result based on the file name having been made valid.
  makeValid ::
    Monad g =>
    (FilePath -> Bool)
    -> f g
  -- | A predicate that succeeds if its 'FilePath' is relative.
  isRelative ::
    Monad g =>
    f g
  isRelative =
    (.!.) isNotRelative
  -- | A predicate that succeeds if its 'FilePath' is not relative.
  isNotRelative ::
    Monad g =>
    f g
  isNotRelative =
    (.!.) isRelative
  -- | A predicate that succeeds if its 'FilePath' is absolute.
  isAbsolute ::
    Monad g =>
    f g
  isAbsolute =
    (.!.) isNotAbsolute
  -- | A predicate that succeeds if its 'FilePath' is not absolute.
  isNotAbsolute ::
    Monad g =>
    f g
  isNotAbsolute =
    (.!.) isAbsolute
  -- | A predicate that succeeds if its 'FilePath' is valid.
  isValid ::
    Monad g =>
    f g
  isValid =
    (.!.) isNotValid
  -- | A predicate that succeeds if its 'FilePath' is not valid.
  isNotValid ::
    Monad g =>
    f g
  isNotValid =
    (.!.) isValid

instance FilePathPredicate RecursePredicateT where
  always =
    recursePredicateT . const . return $ True
  never =
    recursePredicateT . const . return $ False
  f .&&. g =
    recursePredicateT $ \p -> do r <- runRecursePredicateT f p
                                 if r
                                   then
                                     runRecursePredicateT g p
                                   else
                                     return False
  f .||. g =
    recursePredicateT $ \p -> do r <- runRecursePredicateT f p
                                 if r
                                   then
                                     return True
                                   else
                                     runRecursePredicateT g p
  (.!.) f =
    recursePredicateT $ liftM not . runRecursePredicateT f
  extension f = 
    recursePredicateT $ return . liftM2 (||) f (f . drop 1) . P.takeExtension
  directory f = 
    recursePredicateT $ return . f . P.takeDirectory
  hasExtension =
    recursePredicateT $ return . P.hasExtension
  splitExtension f =
    recursePredicateT $ return . uncurry f . P.splitExtension
  splitDirectories f = 
    recursePredicateT $ return . f . P.splitDirectories
  hasTrailingPathSeparator =
    recursePredicateT $ return . P.hasTrailingPathSeparator
  fileName f = 
    recursePredicateT $ return . f . P.takeFileName
  baseName f = 
    recursePredicateT $ return . f . P.takeBaseName
  normalise f = 
    recursePredicateT $ return . f . P.normalise
  makeValid f = 
    recursePredicateT $ return . f . P.makeValid
  isRelative =
    recursePredicateT $ return . P.isRelative
  isAbsolute =
    recursePredicateT $ return . P.isAbsolute
  isValid =
    recursePredicateT $ return . P.isValid

instance FilePathPredicate FilterPredicateT where
  always =
    filterPredicateT . const . const . return $ True
  never =
    filterPredicateT . const . const . return $ False
  f .&&. g =
    filterPredicateT $ \p k -> do r <- runFilterPredicateT f p k
                                  if r
                                    then
                                      runFilterPredicateT g p k
                                    else
                                      return False
  f .||. g =
    filterPredicateT $ \p k -> do r <- runFilterPredicateT f p k
                                  if r
                                    then
                                      return True
                                    else
                                      runFilterPredicateT g p k
  (.!.) f =
    filterPredicateT $ \p -> liftM not . runFilterPredicateT f p
  extension f = 
    filterPredicateT $ const . return . liftM2 (||) f (f . drop 1) . P.takeExtension
  directory f = 
    filterPredicateT $ const . return . f . P.takeDirectory
  hasExtension =
    filterPredicateT $ const . return . P.hasExtension
  splitExtension f =
    filterPredicateT $ const . return . uncurry f . P.splitExtension
  splitDirectories f = 
    filterPredicateT $ const . return . f . P.splitDirectories
  hasTrailingPathSeparator =
    filterPredicateT $ const . return . P.hasTrailingPathSeparator
  fileName f = 
    filterPredicateT $ const . return . f . P.takeFileName
  baseName f = 
    filterPredicateT $ const . return . f . P.takeBaseName
  normalise f = 
    filterPredicateT $ const . return . f . P.normalise
  makeValid f = 
    filterPredicateT $ const . return . f . P.makeValid
  isRelative =
    filterPredicateT $ const . return . P.isRelative
  isAbsolute =
    filterPredicateT $ const . return . P.isAbsolute
  isValid =
    filterPredicateT $ const . return . P.isValid