{-# LANGUAGE GADTs #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Language.Nomyx.Engine.Evaluation where

import Prelude hiding ((.), log)
import Control.Monad
import Control.Monad.State
import Control.Monad.Reader
import Data.List
import Data.Typeable
import Data.Function hiding ((.))
import Data.Time
import Data.Lens
import Data.Maybe
import Control.Category
import Control.Monad.Error (ErrorT(..))
import Control.Monad.Error.Class (MonadError(..))
import Control.Applicative ((<$>))
import Language.Nomyx.Expression
import Language.Nomyx.Engine.Game
import Language.Nomyx.Engine.Utils


type Evaluate a = ErrorT String (State Game) a

-- an untyped version of InputData for serialization
data UInputData = URadioData Int
                | UCheckboxData [Int]
                | UTextData String
                | UTextAreaData String
                | UButtonData
                  deriving (Show, Read, Eq, Ord)

-- | evaluate an expression.
-- The rule number passed is the number of the rule containing the expression.
evalNomex :: Nomex a -> RuleNumber -> Evaluate a
evalNomex (NewVar name def) rn = do
   vars <- access variables
   case find ((== name) . getL vName) vars of
      Nothing -> do
         variables %= (Var rn name def : )
         return $ Just (V name)
      Just _ -> return Nothing

evalNomex (DelVar (V name)) _ = do
   vars <- access variables
   case find ((== name) . getL vName) vars of
      Nothing -> return False
      Just _ -> do
         variables %= filter ((/= name) . getL vName)
         return True

evalNomex (WriteVar (V name) val) _ = do
   vars <- access variables
   case find (\(Var _ myName _) -> myName == name) vars of
      Nothing -> return False
      Just (Var rn myName _) -> do
         variables %= replaceWith ((== name) . getL vName) (Var rn myName val)
         return True

evalNomex (OnEvent event handler) rn = do
   evs <- access events
   let en = getFreeNumber (map _eventNumber evs)
   events %= (EH en rn event handler SActive : )
   return en

evalNomex (DelEvent en) _ = evDelEvent en

evalNomex (DelAllEvents e) _ = do
   evs <- access events
   let filtered = filter (\EH {event} -> event === e) evs
   mapM_ evDelEvent (_eventNumber <$> filtered)

evalNomex (SendMessage (Message id) myData) _ = triggerEvent_ (Message id) (MessageData myData)

evalNomex (NewOutput pn s)      rn = evNewOutput pn rn s
evalNomex (UpdateOutput on s)   _  = evUpdateOutput on s
evalNomex (DelOutput on)        _  = evDelOutput on
evalNomex (ProposeRule rule)    _  = evProposeRule rule
evalNomex (ActivateRule rule)   rn = evActivateRule rule rn
evalNomex (RejectRule rule)     rn = evRejectRule rule rn
evalNomex (AddRule rule)        _  = evAddRule rule
evalNomex (ModifyRule mod rule) _  = evModifyRule mod rule
evalNomex (SetPlayerName pn n)  _  = evChangeName pn n
evalNomex (DelPlayer pn)        _  = evDelPlayer pn
evalNomex (LiftEffect e)        pn = liftEval $ evalNomexNE e pn


evalNomex (ThrowError s)        _  = throwError s
evalNomex (CatchError n h)      rn = catchError (evalNomex n rn) (\a -> evalNomex (h a) rn)
evalNomex (SetVictory ps)       rn = do
   void $ victory ~= (Just $ VictoryCond rn ps)
   triggerEvent_ Victory (VictoryData $ VictoryCond rn ps)

evalNomex (Return a)            _  = return a
evalNomex (Bind exp f) rn = do
   e <- evalNomex exp rn
   evalNomex (f e) rn

liftEval :: Reader Game a -> Evaluate a
liftEval r = runReader r <$> get

evalNomexNE :: NomexNE a -> RuleNumber -> Reader Game a
evalNomexNE (ReadVar (V name)) _ = do
   vars <- asks _variables
   let var = find ((== name) . getL vName) vars
   case var of
      Nothing -> return Nothing
      Just (Var _ _ val) -> case cast val of
          Just v -> return $ Just v
          Nothing -> return Nothing

evalNomexNE (GetOutput on)        _  = evGetOutput on
evalNomexNE GetRules              _  = asks _rules
evalNomexNE GetPlayers            _  = asks _players
evalNomexNE SelfRuleNumber        rn = return rn
evalNomexNE (CurrentTime)         _  = asks _currentTime
evalNomexNE (Return a)            _  = return a
evalNomexNE (Bind exp f) rn = do
   e <- evalNomexNE exp rn
   evalNomexNE (f e) rn

evalNomexNE (Simu sim ev) rn = do
   let s = runEvalError Nothing (evalNomex sim rn)
   g <- ask
   let g' = execState s g
   return $ runReader (evalNomexNE ev rn) g'

getVictorious :: Game -> [PlayerNumber]
getVictorious g = case _victory g of
   Nothing -> []
   Just (VictoryCond rn v) -> runReader (evalNomexNE v rn) g

evalOutput :: Game -> Output -> String
evalOutput g (Output _ rn _ o _) = runReader (evalNomexNE o rn) g

allOutputs :: Game -> [String]
allOutputs g = map (evalOutput g) (_outputs g)

isOutput :: String -> Game -> Bool
isOutput s g = s `elem` allOutputs g

--execute all the handlers of the specified event with the given data
triggerEvent :: (Typeable e) => Event e -> EventData e -> Evaluate Bool
triggerEvent e dat = do
   evs <- access events
   let filtered = filter (\(EH {event, _evStatus}) -> e === event && _evStatus == SActive) (reverse evs)
   case filtered of
      [] -> return False
      xs -> do
         mapM_ (triggerHandler dat) xs
         return True


triggerHandler :: (Typeable e) => EventData e -> EventHandler -> Evaluate ()
triggerHandler dat (EH {_ruleNumber, _eventNumber, handler}) = case cast handler of
    Just castedH -> do
       let (exp :: Nomex ()) = castedH (_eventNumber, dat)
       (evalNomex exp _ruleNumber) `catchError` (errorHandler _ruleNumber _eventNumber)
    Nothing -> logAll ("failed " ++ (show $ typeOf handler))

triggerEvent_ :: (Typeable e) => Event e -> EventData e -> Evaluate ()
triggerEvent_ e ed = void $ triggerEvent e ed

errorHandler :: RuleNumber -> EventNumber -> String -> Evaluate ()
errorHandler rn en s = logAll $ "Error in rule " ++ show rn ++ " (triggered by event " ++ show en ++ "): " ++ s

-- trigger the input event with the input data
triggerInput :: EventNumber -> UInputData -> Evaluate ()
triggerInput en ir = do
   evs <- access events
   let filtered = filter ((== en) . getL eventNumber) evs
   mapM_ (execInputHandler ir) filtered


-- execute the event handler using the data received from user
execInputHandler :: UInputData -> EventHandler -> Evaluate ()
execInputHandler (UTextData s)      (EH en rn (InputEv (Input _ _ Text))          h SActive) = evalNomex (h (en, InputData $ TextData s)) rn
execInputHandler (UTextAreaData s)  (EH en rn (InputEv (Input _ _ TextArea))      h SActive) = evalNomex (h (en, InputData $ TextAreaData s)) rn
execInputHandler (UButtonData)      (EH en rn (InputEv (Input _ _ Button))        h SActive) = evalNomex (h (en, InputData $ ButtonData)) rn
execInputHandler (URadioData i)     (EH en rn (InputEv (Input _ _ (Radio cs)))    h SActive) = evalNomex (h (en, InputData $ RadioData $ fst $ cs!!i)) rn
execInputHandler (UCheckboxData is) (EH en rn (InputEv (Input _ _ (Checkbox cs))) h SActive) = evalNomex (h (en, InputData $ CheckboxData $ fst <$> cs `sel` is)) rn
execInputHandler _ _ = return ()

findEvent :: EventNumber -> [EventHandler] -> Maybe EventHandler
findEvent en = find ((== en) . getL eventNumber)

--Get all event numbers of type choice (radio button)
getChoiceEvents :: State Game [EventNumber]
getChoiceEvents = do
   evs <- access events
   return $ map _eventNumber $ filter choiceEvent evs
   where choiceEvent (EH _ _ (InputEv (Input _ _ (Radio _))) _ _) = True
         choiceEvent _ = False

--Get all event numbers of type text (text field)
getTextEvents :: State Game [EventNumber]
getTextEvents = do
   evs <- access events
   return $ map _eventNumber $ filter choiceEvent evs
   where choiceEvent (EH _ _ (InputEv (Input _ _ Text)) _ _) = True
         choiceEvent _ = False

evProposeRule :: RuleInfo -> Evaluate Bool
evProposeRule rule = do
   rs <- access rules
   case find ((== (rNumber ^$ rule)) . getL rNumber) rs of
      Nothing -> do
         rules %= (rule:)
         triggerEvent_ (RuleEv Proposed) (RuleData rule)
         return True
      Just _ -> return False

--Sets the rule status to Active and execute it if possible
evActivateRule :: RuleNumber -> RuleNumber -> Evaluate Bool
evActivateRule rn by = do
   rs <- access rules
   case find (\r -> _rNumber r == rn && _rStatus r /= Active) rs of
      Nothing -> return False
      Just r -> do
         let newrules = replaceWith ((== rn) . getL rNumber) r{_rStatus = Active, _rAssessedBy = Just by} rs
         rules ~= newrules
         --execute the rule
         evalNomex (_rRule r) rn
         triggerEvent_ (RuleEv Activated) (RuleData r)
         return True

evRejectRule :: RuleNumber -> RuleNumber -> Evaluate Bool
evRejectRule rn by = do
   rs <- access rules
   case find (\r -> _rNumber r == rn && _rStatus r /= Reject) rs of
      Nothing -> return False
      Just r -> do
         let newrules = replaceWith ((== rn) . getL rNumber) r{_rStatus = Reject, _rAssessedBy = Just by} rs
         rules ~= newrules
         triggerEvent_ (RuleEv Rejected) (RuleData r)
         delVarsRule rn
         delEventsRule rn
         delOutputsRule rn
         return True

evAddRule :: RuleInfo -> Evaluate Bool
evAddRule rule = do
   rs <- access rules
   case find ((== (rNumber ^$ rule)) . getL rNumber) rs of
      Nothing -> do
         rules %= (rule:)
         triggerEvent_ (RuleEv Added) (RuleData rule)
         return True
      Just _ -> return False


--TODO: clean and execute new rule
evModifyRule :: RuleNumber -> RuleInfo -> Evaluate Bool
evModifyRule mod rule = do
   rs <- access rules
   let newRules = replaceWith ((== mod) . getL rNumber) rule rs
   case find ((== mod) . getL rNumber) rs of
      Nothing -> return False
      Just r ->  do
         rules ~= newRules
         triggerEvent_ (RuleEv Modified) (RuleData r)
         return True

addPlayer :: PlayerInfo -> Evaluate Bool
addPlayer pi = do
   pls <- access players
   let exists = any (((==) `on` _playerNumber) pi) pls
   unless exists $ do
       players %= (pi:)
       triggerEvent_ (Player Arrive) (PlayerData pi)
   return $ not exists

evDelPlayer :: PlayerNumber -> Evaluate Bool
evDelPlayer pn = do
   g <- get
   case find ((== pn) . getL playerNumber) (_players g) of
      Nothing -> do
         tracePN pn "not in game!"
         return False
      Just pi -> do
         players %= filter ((/= pn) . getL playerNumber)
         triggerEvent_ (Player Leave) (PlayerData pi)
         tracePN pn $ "leaving the game: " ++ _gameName g
         return True

evChangeName :: PlayerNumber -> PlayerName -> Evaluate Bool
evChangeName pn name = do
   pls <- access players
   case find ((== pn) . getL playerNumber) pls of
      Nothing -> return False
      Just pi -> do
         players ~= replaceWith ((== pn) . getL playerNumber) (pi {_playerName = name}) pls
         return True

evDelEvent :: EventNumber -> Evaluate Bool
evDelEvent en = do
   evs <- access events
   case find ((== en) . getL eventNumber) evs of
      Nothing -> return False
      Just eh -> case _evStatus eh of
         SActive -> do
            let newEvents = replaceWith ((== en) . getL eventNumber) eh{_evStatus = SDeleted} evs
            events ~= newEvents
            return True
         SDeleted -> return False


evTriggerTime :: UTCTime -> Evaluate Bool
evTriggerTime t = triggerEvent (Time t) (TimeData t)


--delete all variables of a rule
delVarsRule :: RuleNumber -> Evaluate ()
delVarsRule rn = void $ variables %= filter ((/= rn) . getL vRuleNumber)

--delete all events of a rule
delEventsRule :: RuleNumber -> Evaluate ()
delEventsRule rn = do
   evs <- access events
   let toDelete = filter ((== rn) . getL ruleNumber) evs
   mapM_ (evDelEvent . _eventNumber) toDelete

--delete all outputs of a rule
delOutputsRule :: RuleNumber -> Evaluate ()
delOutputsRule rn = do
   os <- access outputs
   let toDelete = filter ((== rn) . getL oRuleNumber) os
   mapM_ (evDelOutput . _outputNumber) toDelete

evNewOutput :: Maybe PlayerNumber -> RuleNumber -> NomexNE String -> Evaluate OutputNumber
evNewOutput pn rn s = do
   ops <- access outputs
   let on = getFreeNumber (map _outputNumber ops)
   outputs %= (Output on rn pn s SActive : )
   return on

evGetOutput :: OutputNumber -> Reader Game (Maybe String)
evGetOutput on = do
   ops <- asks _outputs
   case find (\(Output myOn _ _ _ s) -> myOn == on && s == SActive) ops of
      Nothing -> return Nothing
      Just (Output _ rn _ o _) -> do
         out <- evalNomexNE o rn
         return $ Just out

evUpdateOutput :: OutputNumber -> NomexNE String -> Evaluate Bool
evUpdateOutput on s = do
   ops <- access outputs
   case find (\(Output myOn _ _ _ s) -> myOn == on && s == SActive) ops of
      Nothing -> return False
      Just (Output _ rn pn _ _) -> do
         outputs %= replaceWith ((== on) . getL outputNumber) (Output on rn pn s SActive)
         return True

evDelOutput :: OutputNumber -> Evaluate Bool
evDelOutput on = do
   ops <- access outputs
   case find ((== on) . getL outputNumber) ops of
      Nothing -> return False
      Just o -> case _oStatus o of
         SActive -> do
            let newOutputs = replaceWith ((== on) . getL outputNumber) o{_oStatus = SDeleted} ops
            outputs ~= newOutputs
            return True
         SDeleted -> return False

logPlayer :: PlayerNumber -> String -> Evaluate ()
logPlayer pn = log (Just pn)

logAll :: String -> Evaluate ()
logAll = log Nothing

log :: Maybe PlayerNumber -> String -> Evaluate ()
log mpn s = do
   time <- access currentTime
   void $ logs %= (Log mpn time s : )

--remove the ErrorT layer from the Evaluate monad stack.
runEvalError :: Maybe PlayerNumber -> Evaluate a -> State Game ()
runEvalError pn egs = do
   e <- runErrorT egs
   case e of
      Right _ -> return ()
      Left e -> do
         tracePN (fromMaybe 0 pn) $ "Error: " ++ e
         void $ runErrorT $ log pn "Error: "

-- | Show instance for Game
-- showing a game involves evaluating some parts (such as victory and outputs)
instance Show Game where
   show g@(Game { _gameName, _rules, _players, _variables, _events, _victory, _currentTime}) =
        "Game Name = "      ++ show _gameName ++
        "\n Rules = "       ++ (intercalate "\n " $ map show _rules) ++
        "\n Players = "     ++ show _players ++
        "\n Variables = "   ++ show _variables ++
        "\n Events = "      ++ show _events ++
        "\n Outputs = "     ++ show (allOutputs g) ++
        "\n Victory = "     ++ show (getVictorious g) ++
        "\n currentTime = " ++ show _currentTime ++ "\n"