module TW.Loader where

import TW.Ast
import TW.Parser

import Control.Monad.Except
import Data.Maybe
import System.Directory
import System.FilePath
import qualified Data.List as L
import qualified Data.Set as S
import qualified Data.Text as T

loadModules :: [FilePath] -> [ModuleName] -> IO (Either String [Module])
loadModules srcDirs entryPoints =
    runExceptT $ loadLoop srcDirs S.empty (S.fromList entryPoints) []

loadLoop :: [FilePath] -> S.Set ModuleName -> S.Set ModuleName -> [Module] -> ExceptT String IO [Module]
loadLoop srcDirs visited queue accum =
    case S.toList queue of
      [] -> return accum
      (x:xs) ->
          do (correctModuleName, filePath) <- getModuleFp srcDirs x
             loaded <- loadModuleFp filePath
             unless (correctModuleName (m_name loaded)) $
                    throwError $ "Wrong module name found in " ++ filePath ++ ": "
                               ++ T.unpack (printModuleName $ m_name loaded)
             let visited' = S.insert (m_name loaded) visited
                 queue' = S.fromList (m_imports loaded) `S.union` S.fromList xs
             loadLoop srcDirs visited' (queue' `S.difference` visited') (loaded : accum)

getModuleFp :: [FilePath] -> ModuleName -> ExceptT String IO (ModuleName -> Bool, FilePath)
getModuleFp srcDirs m@(ModuleName comps) =
    do let fileName = (L.foldl' (</>) "" $ map T.unpack comps) ++ ".tywi"
       discovered <-
           catMaybes <$>
           (forM srcDirs $ \srcDir ->
            do let fn = srcDir </> fileName
               isThere <- liftIO $ doesFileExist fn
               return $ if isThere then Just fn else Nothing)
       let errPrefix =
               "Can not resolve module " ++ T.unpack (printModuleName m) ++ ": "
       case discovered of
         [] ->
             throwError $ errPrefix ++ fileName ++ " not available in " ++ L.intercalate ", " srcDirs
         [x] ->
             return ((==) m, x)
         _ ->
             throwError $ errPrefix ++ "multiple possibilities found: " ++ L.intercalate ", " discovered


loadModuleFp :: FilePath -> ExceptT String IO Module
loadModuleFp fp =
    do m <- liftIO $ moduleFromFile fp
       case m of
         Left err -> throwError err
         Right ok -> return ok