{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TemplateHaskell #-}

module Juno.Consensus.Handle.Revolution
    (handle)
where

import Control.Lens
import Control.Monad.Reader
import Control.Monad.State
import Control.Monad.Writer

import Data.Map (Map)
import qualified Data.Map as Map
import Juno.Consensus.Handle.Types
import Juno.Util.Util (debug, getRevSigOrInvariantError)

import qualified Juno.Types as JT

data RevolutionEnv = RevolutionEnv {
    _lazyVote         :: Maybe (Term, NodeID, LogIndex) -- Handler
  , _currentLeader    :: Maybe NodeID -- Client,Handler,Role
  , _replayMap        :: Map (NodeID, Signature) (Maybe CommandResult) -- Handler
}
makeLenses ''RevolutionEnv

data RevolutionOut =
  UnknownNode |
  RevolutionCalledOnNonLeader |
  IgnoreLeader
    { _deleteReplayMapEntry :: (NodeID, Signature) } |
  IgnoreLeaderAndClearLazyVote
    { _deleteReplayMapEntry :: (NodeID, Signature) }

handleRevolution :: (MonadReader RevolutionEnv m, MonadWriter [String] m) => Revolution -> m RevolutionOut
handleRevolution rev@Revolution{..} = do
  currentLeader' <- view currentLeader
  replayMap' <- view replayMap
  let revSig = getRevSigOrInvariantError "handleRevolution" rev
  if Map.notMember (_revClientId, revSig) replayMap'
  then
    case currentLeader' of
      Just l | l == _revLeaderId -> do
        -- clear our lazy vote if it was for this leader
        lazyVote' <- view lazyVote
        case lazyVote' of
          Just (_, lvid, _) | lvid == _revLeaderId -> return $ IgnoreLeaderAndClearLazyVote (_revClientId, revSig)
          _ -> return $ IgnoreLeader (_revClientId, revSig)
      _ -> return RevolutionCalledOnNonLeader
  else return UnknownNode

handle :: Monad m => Revolution -> JT.Raft m ()
handle msg = do
  s <- get
  (out,l) <- runReaderT (runWriterT (handleRevolution msg)) $
               RevolutionEnv
                 (JT._lazyVote s)
                 (JT._currentLeader s)
                 (JT._replayMap s)
  mapM_ debug l
  case out of
    UnknownNode -> return ()
    RevolutionCalledOnNonLeader -> return ()
    IgnoreLeader{..} -> do
      JT.replayMap %= Map.insert _deleteReplayMapEntry Nothing
      JT.ignoreLeader .= True
    IgnoreLeaderAndClearLazyVote{..} -> do
      JT.replayMap %= Map.insert _deleteReplayMapEntry Nothing
      JT.lazyVote .= Nothing
      JT.ignoreLeader .= True
