content: {-# LANGUAGE CPP #-}
  
  module Language.Haskell.Source.Enumerator
    ( enumeratePath
    ) where
  
  import           Conduit
  import           Control.Applicative
  import           Control.Monad
  import           Data.List
  import           Distribution.PackageDescription
  import qualified Distribution.Verbosity                 as Verbosity
  import           System.Directory
  import           System.FilePath
  #if MIN_VERSION_Cabal(2,2,0)
  import           Distribution.PackageDescription.Parsec (readGenericPackageDescription)
  #elif MIN_VERSION_Cabal(2,0,0)
  import           Distribution.PackageDescription.Parse  (readGenericPackageDescription)
  #else
  import           Distribution.PackageDescription.Parse  (readPackageDescription)
  
  readGenericPackageDescription ::
       Verbosity.Verbosity -> FilePath -> IO GenericPackageDescription
  readGenericPackageDescription = readPackageDescription
  #endif
  enumeratePath :: FilePath -> ConduitT () FilePath IO ()
  enumeratePath path = enumPath path .| mapC normalise
  
  enumPath :: FilePath -> ConduitT () FilePath IO ()
  enumPath path = do
    isDirectory <- lift $ doesDirectoryExist path
    case isDirectory of
      True -> enumDirectory path
      False
        | hasCabalExtension path -> enumPackage path
      False
        | hasHaskellExtension path -> yield path
      False -> return ()
  
  enumPackage :: FilePath -> ConduitT () FilePath IO ()
  enumPackage cabalFile = readPackage cabalFile >>= expandPaths
    where
      readPackage = lift . readGenericPackageDescription Verbosity.silent
      expandPaths = mapM_ (enumPath . mkFull) . sourcePaths
      packageDir = dropFileName cabalFile
      mkFull = (packageDir </>)
  
  enumDirectory :: FilePath -> ConduitT () FilePath IO ()
  enumDirectory path = do
    contents <- lift $ getDirectoryContentFullPaths path
    cabalFiles <- lift $ filterM isCabalFile contents
    if null cabalFiles
      then mapM_ enumPath contents
      else mapM_ enumPackage cabalFiles
  
  getDirectoryContentFullPaths :: FilePath -> IO [FilePath]
  getDirectoryContentFullPaths path =
    mkFull . notHidden . notMeta <$> getDirectoryContents path
    where
      mkFull = map (path </>)
      notHidden = filter (not . isPrefixOf ".")
      notMeta = (\\ [".", ".."])
  
  isCabalFile :: FilePath -> IO Bool
  isCabalFile path = return (hasCabalExtension path) <&&> doesFileExist path
  
  hasCabalExtension :: FilePath -> Bool
  hasCabalExtension path = ".cabal" `isSuffixOf` path
  
  hasHaskellExtension :: FilePath -> Bool
  hasHaskellExtension path = ".hs" `isSuffixOf` path || ".lhs" `isSuffixOf` path
  
  sourcePaths :: GenericPackageDescription -> [FilePath]
  sourcePaths pkg = nub $ concatMap ($ pkg) pathExtractors
    where
      pathExtractors =
        [ maybe [] (hsSourceDirs . libBuildInfo . condTreeData) . condLibrary
        , concatMap (hsSourceDirs . buildInfo . condTreeData . snd) .
          condExecutables
        , concatMap (hsSourceDirs . testBuildInfo . condTreeData . snd) .
          condTestSuites
        , concatMap (hsSourceDirs . benchmarkBuildInfo . condTreeData . snd) .
          condBenchmarks
        ]
  
  (<&&>) :: Applicative f => f Bool -> f Bool -> f Bool
  (<&&>) = liftA2 (&&)
  
  infixr 3 <&&> -- same as (&&)
  