
module Test.Sandwich.TH.ModuleMap where

import Control.Monad
import qualified Data.List as L
import qualified Data.Map as M
import Data.Maybe (isNothing)
import System.Directory
import System.FilePath


-- Map from qualified import name to path
type ModuleMap = M.Map String String
type ReverseModuleMap = M.Map String String

buildModuleMap :: FilePath -> String -> IO ModuleMap
buildModuleMap baseDir modulePrefix = traverseDir (const True) (\x y -> return $ addModuleToMap baseDir modulePrefix x y) mempty baseDir

addModuleToMap :: FilePath -> String -> ModuleMap -> FilePath -> ModuleMap
addModuleToMap relativeTo modulePrefix mm path@(takeExtension -> ".hs") = case pathParts of
  [] -> mm
  _ -> M.insert moduleName (modulePrefix <> (L.intercalate "." pathParts)) mm
  where
    relativePath = (takeFileName relativeTo) </> (makeRelative relativeTo path)
    pathParts = splitDirectories $ dropExtension relativePath
    baseModuleName = last pathParts
    moduleName = head $ filter doesNotExist (baseModuleName : [baseModuleName <> show n | n <- [1..]])
    doesNotExist x = isNothing (M.lookup x mm)
addModuleToMap _ _ mm _ = mm

-- | From https://stackoverflow.com/a/51713361/2659595
traverseDir :: (FilePath -> Bool) -> (b -> FilePath -> IO b) -> b -> FilePath -> IO b
traverseDir validDir transition =
  let go state dirPath = do
        names <- listDirectory dirPath
        let paths = map (dirPath </>) names
        (dirPaths, filePaths) <- partitionM doesDirectoryExist paths
        state' <- foldM transition state filePaths
        foldM go state' (filter validDir dirPaths)
 in go

-- | From https://hackage.haskell.org/package/extra-1.7.9/docs/src/Control.Monad.Extra.html#partitionM
partitionM :: Monad m => (a -> m Bool) -> [a] -> m ([a], [a])
partitionM _ [] = pure ([], [])
partitionM f (x:xs) = do
    res <- f x
    (as,bs) <- partitionM f xs
    pure ([x | res]++as, [x | not res]++bs)
