module Main where

import           Ntha                     (Expr (..),
                                           Type (..),
                                           Value (..),
                                           TypeScope (..),
                                           ValueScope (..),
                                           EPath,
                                           analyze,
                                           assumptions,
                                           builtins,
                                           checker,
                                           eval,
                                           isImport,
                                           parseExpr)
import           Paths_ntha

import           System.Console.Haskeline
import           System.Environment
import           Control.Lens
import           Control.Monad            (foldM)
import           Control.Monad.Trans
import           Data.List                (intercalate)
import qualified Control.Exception        as E
import qualified Data.Map                 as M
import qualified Data.Set                 as S

type Env = (TypeScope, ValueScope)

emptyEnv :: (TypeScope, ValueScope)
emptyEnv = (TypeScope Nothing M.empty, ValueScope Nothing M.empty)

loadFile :: Env -> EPath -> IO Env
loadFile env path = do
  file <- getDataFileName path
  fileContent <- readFile file
  (env', _, _) <- process' env $ parseExpr fileContent
  return env'

loadImport :: Env -> Expr -> IO (Env, Expr)
loadImport env expr = case expr of
  EProgram instructions -> do
    let imports = filter isImport instructions
    let continueAst = EProgram $ filter (not . isImport) instructions
    importEnv <- foldM (\ev (EImport path) -> loadFile ev path) env imports
    return (importEnv, continueAst)
  _ -> return (env, expr)

loadLib :: IO Env
loadLib = do
  assumps <- assumptions
  loadFile (assumps, builtins) "lib/std.ntha"

process' :: Env -> Expr -> IO (Env, Value, Type)
process' env expr = do
   ((importAssumps, importBuiltins), ast) <- loadImport env expr
   (assumps', t) <- analyze ast importAssumps S.empty
   checker ast assumps'
   let (builtins', v) = eval ast importBuiltins
   return ((assumps', builtins'), v, t)

process :: Env -> String -> IO Env
process env@(assumps, prevBuiltins) expr =
  E.catch (do
           (env', v, t) <- process' env $ parseExpr expr
           putStrLn $ show v ++ " : " ++ show t
           return env')
          (\(E.ErrorCall e) -> do
           putStrLn e
           return (assumps, prevBuiltins))

loop :: Env -> InputT IO Env
loop env = do
  minput <- getInputLine "λ> "
  case minput of
    Nothing -> do
      outputStrLn "Goodbye."
      return emptyEnv
    Just input -> (liftIO $ process env input) >>= (\env' -> loop env')

prologueMessage :: String
prologueMessage = intercalate "\n"
  ["      _   __   __     __",
   "     / | / /  / /_   / /_   ____ _",
   "    /  |/ /  / __/  / __ \\ / __ `/",
   "   / /|  /  / /_   / / / // /_/ /",
   "  /_/ |_/   \\__/  /_/ /_/ \\__,_/",
   ""
   ]

main :: IO Env
main = do
  env <- loadLib
  args <- getArgs
  case (args ^? element 0) of
    Just arg -> if arg == "repl"
                then repl env
                else do
                  file <- readFile arg
                  process env file
    Nothing -> repl env
  where repl ev = do putStrLn prologueMessage
                     runInputT defaultSettings (loop ev)
