module System.Directory.Tree where

open import Level
open import Size

open import Codata.IO
open import Codata.Thunk
open import Data.Bool.Base
open import Data.List.Base as List
open import Data.Product as Prod
open import Data.Sum.Base
open import Function
open import System.Directory
open import System.FilePath.Posix

{-# NO_UNIVERSE_CHECK #-}
data Tree′ n (i : Size) : Set where
  _∋_:<_ :
    FilePath n                                    -- path to the root of the tree
    List (FilePath n)                             -- list of files in it
    List (FilePath n × IO 0ℓ (Thunk (Tree′ n) i))  -- trees for subdirectories
    Tree′ n i

Tree : Nature  Set
Tree n = Tree′ n _

AbsoluteTree : Set
AbsoluteTree = Tree Nature.absolute

RelativeTree : Set
RelativeTree = Tree Nature.relative

treeᵗ :  {i} 
        KnownNature n                 -- nature of the paths stored in the tree
        (AbsolutePath × FilePath n)   -- path to the root (absolute for setting, n for storing)
        IO 0ℓ (Thunk (Tree′ n) i)
treeᵗ n (afp , fp) = withCurrentDirectory afp $ do
  -- get content of the current directory
  vs  listDirectory afp
  -- tag each filepath with whether it points to a directory
  bvs  ListIO.forM vs $ λ fp  do
    fp    toKnownNature n fp
    true  doesDirectoryExist fp where false  pure (inj₂ fp)
    -- thanks to withCurrentDirectory, this will return the right answer
    afp   makeAbsolute fp
    pure (inj₁ (fp , (afp , fp)))
  -- partition into a list ds of directories and one fs of files
  let (ds , fs) = partitionSums bvs
  -- return the files together with the ability to further explore the tree
  pure  where .force  fp  fs :< List.map (Prod.map₂ (treeᵗ n)) ds)

tree# : Thunk (Tree′ n)   Tree n
tree# t = t .force

tree : {{KnownNature n}}  FilePath m  IO 0ℓ (Tree n)
tree {{n}} fp = do
  afp  makeAbsolute fp
  fp   toKnownNature n fp
  tree# <$> treeᵗ n (afp , fp)