{-# LANGUAGE OverloadedStrings, DeriveDataTypeable #-}

module Castor.Externs ( Exports(..)
                      , mkBif
                      , mkImport'
                      , importCompiled
                      , importScript
                      ) where

import Control.Applicative
import Control.Monad.Error
import Data.Char
import Data.IORef
import Data.List
import qualified Data.Map as M
import Data.Monoid
import qualified Data.Text as T
import qualified Data.Traversable as T
import Data.Maybe
import Data.Typeable
import System.Directory
import System.FilePath
import qualified System.Plugins.Load as P
import System.IO
import Text.Parsec

import Castor.Types
import Castor.Parser
import {-# SOURCE #-} Castor.Interpreter (eval, mkMinimalRef)

newtype Exports = Exports { runExports :: M.Map Sym Value }
    deriving (Typeable)

-- Make a new built-in function.
mkBif :: [Sym] -> Proc -> Value
mkBif syms f = foldr bindProc (Procedure f) syms
  where
    bindProc sym proc = Function sym proc Nothing

importCompiled :: FilePath -> Result
importCompiled fp = do
    mv <- liftIO $ P.load fp [] [] "exports"
    case mv of
        P.LoadFailure msg -> throwError $ fromString $ show msg
        P.LoadSuccess _ v -> do
            minimal <- mkMinimalRef $ takeDirectory fp
            Aggregate <$> T.mapM (\value -> eval value minimal minimal) v

importScript :: FilePath -> Result
importScript fp = do
    code <- liftIO $ readFile fp
    case parse program fp $ T.pack $ code of
        Left err -> throwError $ fromString $ show err
        Right v -> do
            minimal <- mkMinimalRef $ takeDirectory fp
            T.mapM (\value -> eval value minimal minimal) v
            maybeExports <- liftIO $ elookup (Sym "exports") minimal
            return $ case maybeExports of
                Just exports -> exports
                Nothing -> Aggregate M.empty

mkImport' :: FilePath -> Value
mkImport' fp = mkBif [Sym "path"] $ \envref _ -> do
    x <- extract (Sym "path") envref
    let path = combine fp $ toString x
    fex <- liftIO $ doesFileExist path
    if not fex then throwError $ Quoted $ Symbol $ Sym "no-such-file" else do
        let importf = if ".o" `isSuffixOf` path then importCompiled
                                                else importScript
        importf path
