module Hob.DirectoryTree (
    DirectoryForest,
    DirectoryTreeLoader,
    DirectoryReader,
    IsDirectoryCheck,
    DirectoryTreeElement(..),
    fileTreeGenerator
) where

import Control.Monad   (forM)
import Data.List       (sortBy)
import Data.Tree
import System.FilePath

type IsDirectory = Bool

data DirectoryTreeElement = DirectoryTreeElement { elementLabel :: String, elementPath :: FilePath, isDirectory :: IsDirectory }

instance Show (DirectoryTreeElement) where
  show (DirectoryTreeElement label path isDir) = show label ++" ["++ show path ++"] "++(if isDir then "(directory)" else "(file)")

instance  Eq (DirectoryTreeElement)  where
    (DirectoryTreeElement label path isDir) == (DirectoryTreeElement label' path' isDir') = (label == label') && (path == path') && (isDir == isDir')

type DirectoryForest = Forest DirectoryTreeElement
type DirectoryTreeLoader = FilePath -> IO DirectoryForest
type DirectoryReader = FilePath -> IO [FilePath]
type IsDirectoryCheck = FilePath -> IO Bool

fileTreeGenerator :: DirectoryReader -> IsDirectoryCheck -> DirectoryTreeLoader
fileTreeGenerator getDirectoryContents doesDirectoryExist root = do
    directoryContents <- retrieveDirectoryContents
    forM directoryContents $ \ (name, path, isDir) -> do
        childrenForest <- if isDir then callSelf path else return []
        return $ Node (DirectoryTreeElement name path isDir) childrenForest
    where
        callSelf = fileTreeGenerator getDirectoryContents doesDirectoryExist
        removeBannedDirectories = filter (`notElem` bannedDirectories)
        addFileInfo contents =
            forM contents $ \ child -> do
                let childPath = root </> child
                isDir <- doesDirectoryExist childPath
                return (child, childPath, isDir)
        retrieveDirectoryContents = do
            contents <- getDirectoryContents root
            let filteredContents = removeBannedDirectories contents
            filteredFileInfo <- addFileInfo filteredContents
            return $ sortBy contentsSortCriteria filteredFileInfo
        bannedDirectories = [".", "..", ".git"]
        contentsSortCriteria (name1, _, isDir1) (name2, _, isDir2)
            | isDir1 && not isDir2 = LT
            | isDir2 && not isDir1 = GT
            | otherwise = compare name1 name2