{-# LANGUAGE CPP, ScopedTypeVariables, TypeOperators, DeriveDataTypeable #-}

module Yi.Eval (
        -- * Eval\/Interpretation
        jumpToErrorE,
        jumpToE,
        consoleKeymap,
        execEditorAction,
        getAllNamesInScope
) where

import Data.Array
import Data.List
import Prelude hiding (error, (.))
import Yi.Regex
import Yi.Core  hiding (toDyn, concatMap)
import Yi.Dired
import qualified Language.Haskell.Interpreter as LHI
import System.FilePath
import System.Directory

-- | Returns an Interpreter action that loads the desired modules and interprets the expression.
execEditorAction :: String -> YiM ()
execEditorAction s = do
   contextPath <- (</> ".yi" </> "local") <$> io getHomeDirectory
   let contextFile = contextPath </> "Env.hs"
   haveUserContext <- io $ doesFileExist contextFile
   res <- io $ LHI.runInterpreter $ do
       LHI.set [LHI.searchPath LHI.:= []]
       LHI.set [LHI.languageExtensions LHI.:= [LHI.OverloadedStrings, 
                                               LHI.NoImplicitPrelude -- use Yi prelude instead.
                                              ]]
       when haveUserContext $ do
          LHI.loadModules [contextFile]
          LHI.setTopLevelModules ["Env"]

       LHI.setImportsQ [("Yi", Nothing), ("Yi.Keymap",Just "Yi.Keymap")] -- Yi.Keymap: Action lives there
       LHI.interpret ("Yi.makeAction ("++s++")") (error "as" :: Action)
   case res of
       Left err -> errorEditor (show err)
       Right action -> runAction action

data NamesCache = NamesCache [String] deriving Typeable
instance Initializable NamesCache where
    initial = NamesCache []
 
getAllNamesInScope :: YiM [String]
getAllNamesInScope = do 
   NamesCache cache <- withEditor $ getA dynA
   result <-if null cache then do
        res <-io $ LHI.runInterpreter $ do
            LHI.set [LHI.searchPath LHI.:= []]
            LHI.getModuleExports "Yi"
        return $ case res of
           Left err ->[show err]
           Right exports -> flattenExports exports
      else return $ sort cache
   withEditor $ putA dynA (NamesCache result)
   return result
  

flattenExports :: [LHI.ModuleElem] -> [String]
flattenExports = concatMap flattenExport

flattenExport :: LHI.ModuleElem -> [String]
flattenExport (LHI.Fun x) = [x]
flattenExport (LHI.Class _ xs) = xs
flattenExport (LHI.Data _ xs) = xs

jumpToE :: String -> Int -> Int -> YiM ()
jumpToE filename line column = do
  fnewE filename
  withBuffer $ do _ <- gotoLn line
                  moveXorEol column

errorRegex :: Regex
errorRegex = makeRegex "^(.+):([0-9]+):([0-9]+):.*$"

parseErrorMessage :: String -> Maybe (String, Int, Int)
parseErrorMessage ln = do
  (_,result,_) <- matchOnceText errorRegex ln
  let [_,filename,line,col] = take 3 $ map fst $ elems result
  return (filename, read line, read col)

parseErrorMessageB :: BufferM (String, Int, Int)
parseErrorMessageB = do
  ln <- readLnB
  let Just location = parseErrorMessage ln
  return location

jumpToErrorE :: YiM ()
jumpToErrorE = do
  (f,l,c) <- withBuffer parseErrorMessageB
  jumpToE f l c

prompt :: String
prompt = "Yi> "

takeCommand :: String -> String
takeCommand x | prompt `isPrefixOf` x = drop (length prompt) x
              | otherwise = x

consoleKeymap :: Keymap
consoleKeymap = do _ <- event (Event KEnter [])
                   write $ do x <- withBuffer readLnB
                              case parseErrorMessage x of
                                Just (f,l,c) -> jumpToE f l c
                                Nothing -> do withBuffer $ do
                                                p <- pointB
                                                botB
                                                p' <- pointB
                                                when (p /= p') $
                                                   insertN ("\n" ++ prompt ++ takeCommand x)
                                                insertN "\n"
                                                pt <- pointB
                                                insertN prompt
                                                bm <- getBookmarkB "errorInsert"
                                                setMarkPointB bm pt
                                              execEditorAction $ takeCommand x