{-|
    Module to generate an expandible tree of directories
    contained within the file system.
-}

module DirectoryTree 
    (
        addSubTree,
        getPathAtActivation,
        getListing,
        expandPath
    ) 
where

import Graphics.UI.Gtk
import Data.Tree
import Util
import Popup
import qualified Control.Exception as E
import System.Directory ( getDirectoryContents, doesDirectoryExist )
import System.FilePath ( (</>), splitFileName, dropTrailingPathSeparator, splitDirectories )
import System.Posix.Files ( getSymbolicLinkStatus, isSymbolicLink, fileAccess )
import Control.Monad ( filterM, forM, forM_ )
import List ( isPrefixOf, sort )

           
{-|
    Calculates a string based on where a user has activated a
    row of a 'TreeStore'. It recursively scans through the tree path
    getting the string at each seperate node leading up to the selected
    node.
-}

getPathAtActivation :: TreePath -- ^ The path to the node selected at activation.
                    -> TreeStore String  -- ^ A store of strings.
                    -> IO String -- ^ The selected string.
getPathAtActivation path store
 | null ( init path ) = treeStoreGetValue store path >>= return
 | otherwise = ( treeStoreGetValue store path ) >>= ( \l ->
        ( getPathAtActivation ( init path ) store ) >>= ( \f ->
        return ( "/" ++ ( reverse ( dropTrailingPathSeparator ( reverse ( f ++ l ) ) ) ) ) ) )

{-|
    Scans to the selected node, that the user has requested be expanded.
    Once it gets there it checks to see whether or not the node already
    contains elements. If so, it does nothing, but if it is empty it
    adds in a sub tree containing the selected folders sub directories.
-}

addSubTree :: TreePath -- ^ The path to the node to be expanded.
           -> TreeStore String -- ^ The 'TreeStore' containing the selected node.
           -> IO ( )
addSubTree path store = do
    newtree <- getPathAtActivation path store >>= dirContents
    tree <- treeStoreGetTree store path
    case ( ( rootLabel ( ( subForest tree ) !! 0 ) ) == ("") ) of
            True -> do
                    forM_ ( reverse ( subForest newtree ) ) $ \sub -> treeStoreInsertTree store path 0 sub
                    ( treeStoreRemove store ( path ++ [length ( subForest newtree)] ) ) >> return ( )
            False -> return ( )

{-|
    Expands the tree to match a supplied file path, if it
    it exist within the treeStore.
-}

expandPath :: TreeView -- ^ The treeView containing the treeStore
           -> TreeStore String -- ^ The tree store containing the directories.
           -> FilePath -- ^ The path to be expanded to.
           -> IO ( )
expandPath tree store path = do
    treeS <- treeStoreGetTree store [0]
    let dirs = "/" : ( map ( "/" ++ ) ( tail ( splitDirectories path ) ) )
        paths = findPath [treeS] dirs
    case ( length paths == length dirs ) of
        False -> do
                 addSubTree paths store
                 expandPath tree store path
        True -> do
                    treeViewCollapseAll tree
                    treeViewExpandToPath tree paths
                    treeViewSetCursor tree paths Nothing

{-|
    Function to find the path to a list 
    of 'a' contained in a Forest.
-}
    
findPath :: Eq a => Forest a -- ^ Forest of data.
         -> [a] -- ^ List containing elements to make a path.
         -> TreePath -- ^ Resulting path to elements of [a]
findPath [] _ = []
findPath _ [] = []
findPath (x:xs) (y:ys)
 | rootLabel x == y = 0 : findPath ( subForest x ) ys
 | otherwise = case res of
                    [] -> []
                    (x:[]) -> 1 + x : []
                    (x:xs) -> 1 + x : xs
    where res = findPath xs (y:ys)
    
{-|
    Function to calculate the amount of directories
    within a directory.
-}

subDirSize :: FilePath -- ^ The path to the directory of which size is to be calculated.
           -> IO Int -- ^ The amount of accessible directories within the directory.
subDirSize dir = getDirectoryContents dir >>= 
    ( \c -> filterM doesDirectoryExist ( map ( dir </> ) ( filter ( not . isPrefixOf "." ) c ) ) ) >>= 
    filterLinks >>= (\d -> return ( length d ) )

{-|
    Function to get the directory contents and add them to an
    expanding Tree, with sub-directoryies added as nodes,
    viewable on expansion. It only shows accessible folders
    with sub-directories as expandible.
-}

dirContents :: FilePath -- ^ The path to the folder to be "treed"
            -> IO ( Tree String ) -- ^ A tree of String type, containing nodes representing sub-directories.
dirContents path = do
    let (_, title) = splitFileName path
    nodes <- ( getDirectoryContents path >>= ( \c -> 
            filterM doesDirectoryExist ( map ( path </> ) ( filter ( not . isPrefixOf "." ) c ) ) ) ) >>= 
            filterLinks >>= ( \dirs -> forM ( sort dirs ) $ \d -> do
                            let (_ , dname) = splitFileName d
                            subDirSize d >>= ( \s -> 
                                return ( Node { rootLabel = "/" ++ dname,
                                                 subForest = case s of
                                                                   0 -> [] 
                                                                   _ -> [Node { rootLabel = "",
                                                                                subForest = [] } ] } ) ) )
    return ( Node { rootLabel = ( "/" ++ title ),
                    subForest = nodes } )

{-|
        Function to generate an expandible tree listing of all directories on 
        the users machine.
-}

getListing :: IO ( TreeView, ScrolledWindow, TreeStore String ) -- ^ The 'TreeView', 'ScrolledWindow', 'TreeStore' associated with the expandible tree.
getListing = do
        scroll <- scrolledWindowNew Nothing Nothing
        store <- treeStoreNew [Node { rootLabel  = "/",
                                             subForest = [Node { rootLabel = "",
                                                                 subForest = [] } ] }]
        view <- treeViewNewWithModel store
        treeViewSetHeadersVisible view True
        col1 <- treeViewColumnNew
        treeViewColumnSetTitle col1 "FileSystem"
        renderer1 <- cellRendererTextNew
        cellLayoutPackStart col1 renderer1 True 
        cellLayoutSetAttributes col1 renderer1 store $ \row -> [ cellText := row ]
        treeViewAppendColumn view col1
        selection <- treeViewGetSelection view
        treeSelectionSetMode selection SelectionSingle
        containerAdd scroll view
        return ( view, scroll, store )
