{-# LANGUAGE ParallelListComp #-}
{-# LANGUAGE OverloadedStrings #-}

module Language.Virgil.Eval where

import qualified Data.Aeson as Ae
import           Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HM

import           Language.Virgil.AST

evalCh :: ChValue -> Either String Ae.Value
evalCh (ChObject obj) = Ae.Object `fmap` mapM evalCh obj
evalCh (ChArray arr)  = Ae.Array `fmap` mapM evalCh arr
evalCh (ChString t)   = pure (Ae.String t)
evalCh (ChNumber n)   = pure (Ae.Number n)
evalCh (ChBool b)     = pure (Ae.Bool b)
evalCh ChNull         = pure Ae.Null
evalCh (ChQuote _)    = Left "no evaluation yet"

-- | Match a pattern against a value
match :: Pattern -> Value -> Maybe [(Ident, Value)]
match (PConstr i p) (VConstr i' v)
  | i == i'   = match p v
match (PTup ps) (VTup vs)
  | length ps == length vs =
      fmap concat (sequence [ match p v | p <- ps | v <- vs ])
match (PVar i) x = return [(i, x)]
match (PLit l) (VLit v)
  | l == v = return []
match PUnderscore _ = return []
match _ _ = Nothing

eval :: Env -> Expr -> Either String Value
eval _ (ELit l) = return (VLit l)
eval _ con@(EConstr _) = do
  let fnCase = FnCase { fcPat  = PVar "x"
                      , fcBody = EApp con (EVar "x")
                      }
  return (VLam [fnCase] HM.empty)
eval env (EApp (EConstr i) e) = do
  e' <- eval env e
  return (VConstr i e')
eval env (EApp f x) = do
  VLam cs env' <- eval env f
  arg <- eval env x
  app env cs arg
eval env (EVar v) = case HM.lookup v env of
  Just x  -> Right x
  Nothing -> Left ("Unbound variable: " ++ show v)
eval env (ETup es) = do
  vs <- mapM (eval env) es
  return (VTup vs)
eval env (EList es) = do
  vs <- mapM (eval env) es
  return (foldr (\ x xs -> VConstr "Cons" (VTup [x, xs]))
                (VConstr "Nil" (VTup []))
                vs)
eval env (ERecord rs) = do
  vs <- mapM (eval env . snd) rs
  return (VRecord [ (i, v) | i <- map fst rs | v <- vs ])
eval env (ELam cs) = return (VLam cs env)
eval env (ETyped e _) = eval env e
eval env (EAccess e i) = do
  VRecord r <- eval env e
  case lookup i r of
    Just v  -> return v
    Nothing -> Left ("Bad lookup: field " ++ show i ++
                     " in " ++ show r)

extend :: Env -> [(Ident, Value)] -> Env
extend = foldr (uncurry HM.insert)

app :: Env -> [FnCase] -> Value -> Either String Value
app _ [] _ = Left "Non-exhaustive cases"
app env (c:cs) arg = case match (fcPat c) arg of
  Just bindings -> eval (extend env bindings) (fcBody c)
  Nothing       -> app env cs arg
