----------------------------------------------------------------------------
-- |
-- Module      :  CSPM.Interpreter.Test.CLI
-- Copyright   :  (c) Fontaine 2008 - 2011
-- License     :  BSD3
-- 
-- Maintainer  :  Fontaine@cs.uni-duesseldorf.de
-- Stability   :  experimental
-- Portability :  GHC-only
--
-- This is mainly useful for testing the functional sub language.
-- This module does not allow tracing of processes
-- (tracing is implemented in an other package).
--
-- 'runFile' loads a CSPM-specification from a file and evaluates an expression in
-- the context of that specification.
--
-- Example:
--
--    'runFile' fib.csp fib(10)
--
-- where the file fib.csp contains:
--    fib(x)= if x <2 then 1 else fib(x-1)+fib(x-2)
--
-- 'runFile' writes to 'stdout' and handles some exceptions.
--
----------------------------------------------------------------------------

module CSPM.Interpreter.Test.CLI
(
  runFile
  ,evalFile
  ,evalString
)

where
import Language.CSPM.Frontend
import qualified Language.CSPM.AST as AST

import qualified CSPM.Interpreter.Types as Types
import CSPM.Interpreter.Eval
import CSPM.Interpreter.Types (Value)
import CSPM.Interpreter.PrepareAST (prepareAST)
import CSPM.Interpreter.CoreInstances ()

import System.Exit
import System.CPUTime
import Control.Exception.Base (evaluate)
import Control.Monad
import qualified Data.IntMap as IntMap
import qualified Data.Map as Map
import Data.Maybe

-- | Load a specification from a file and evaluate an expression in the context.
-- Print the result to 'stdout' and handle some exceptions.
runFile ::
     FilePath -- ^ a file containing a CSPM specification
  -> String  -- ^ a CSPM expression
  -> IO ()
runFile fileName expr
  = handleLexError lexErrorHandler
      $ handleParseError parseErrorHandler
        $ handleRenameError renameErrorHandler $
  do
    res <- evalFile False (Just fileName) expr
    putStrLn $ show $ fst res
    exitSuccess

{- Todo: clean up the mess below -}

-- | Evaluate an expression, optionall load a context from a file
evalFile ::
     Bool -- ^ verbose output ?
  -> Maybe FilePath -- ^ optional specification to load into context
  -> String -- ^ a CSPM expression
  -> IO (Value, Types.Env)
evalFile verbose context expr = do
  plainSrc <- case context of
    Just path -> readFile path
    Nothing -> return ""
  evalString verbose plainSrc (fromMaybe "expression" context) expr

-- | Evaluate an expression, optionall load a context from a string
evalString ::
     Bool -- ^ verbose output ?
  -> String -- ^ String containg CSPM specification (may be empty)
  -> String       -- ^ name of the specification for error-reporting
  -> String -- ^ a CSPM expression
  -> IO (Value, Types.Env)
evalString verbose specSrc specName expr = do
{- this is a hack:
we simply append the expression to be evaluated at the end of the sourcefile
and parse both together in one go
todo : fix
-}
  let src = specSrc ++ "\n--patch entrypoint\ntest__entry = " ++expr ++"\n"

  _startTime <- (return $ length src) >> getCPUTime
  tokenList <- lexInclude src >>= eitherToExc
  _time_have_tokens <- getCPUTime

  ast <- eitherToExc $ parse specName tokenList
  _time_have_ast <- getCPUTime

  (renamedAst, renaming) <- eitherToExc $ renameModule ast
  astNew <- evaluate $ prepareAST renamedAst
  _time_have_renaming <- getCPUTime

--  putStrLn $ "Parsing OK"
--  putStrLn $ "lextime : " ++ showTime (time_have_tokens - startTime)
--  putStrLn $ "parsetime : " ++ showTime(time_have_ast - time_have_tokens)
--  putStrLn $ "renamingtime : " ++ showTime (time_have_renaming - time_have_ast)
--  putStrLn $ "total : " ++ showTime(time_have_ast - startTime)

  time_start_execute <- getCPUTime
  let
    entry :: AST.UniqueIdent
    entry = fromJust $ Map.lookup "test__entry" $ visible renaming
    env = evalModule astNew
    val :: Value
    val = (IntMap.!) (Types.getLetBindings env) $ AST.uniqueIdentId entry
  when verbose $ do
     putStrLn $ "eval result         : " ++ show val
     time_finish_execute <- getCPUTime
     putStrLn $ "eval execution time : " ++ showTime (time_finish_execute - time_start_execute)
  return (val,env)

showTime :: Integer -> String
showTime a = show (div a 1000000000) ++ "ms"

parseErrorHandler :: ParseError -> IO ()
parseErrorHandler err = do
  putStrLn "ParseError : "
  putStrLn $ show err
  exitFailure

lexErrorHandler :: LexError -> IO ()
lexErrorHandler err = do
  putStrLn "LexError : "
  putStrLn $ show err
  exitFailure

renameErrorHandler :: RenameError -> IO ()
renameErrorHandler err = do 
  putStrLn "RenamingError : "
  putStrLn $ show err
  exitFailure