{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}

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

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

import Juno.Consensus.Handle.Types
import Juno.Runtime.Sender (sendAllAppendEntries)
import Juno.Runtime.Timer (resetHeartbeatTimer, hasElectionTimerLeaderFired)
import Juno.Util.Util (debug,enqueueEvent)
import qualified Juno.Types as JT

data HeartbeatTimeoutEnv = HeartbeatTimeoutEnv {
      _nodeRole :: Role
    , _leaderWithoutFollowers :: Bool
}
makeLenses ''HeartbeatTimeoutEnv

data HeartbeatTimeoutOut = IsLeader | NotLeader | NoFollowers

handleHeartbeatTimeout :: (MonadReader HeartbeatTimeoutEnv m, MonadWriter [String] m) => String -> m HeartbeatTimeoutOut
handleHeartbeatTimeout s = do
  tell ["heartbeat timeout: " ++ s]
  role' <- view nodeRole
  leaderWithoutFollowers' <- view leaderWithoutFollowers
  case role' of
    Leader -> if leaderWithoutFollowers'
              then tell ["Leader found to not have followers"] >> return NoFollowers
              else return IsLeader
    _ -> return NotLeader

handle :: Monad m => String -> JT.Raft m ()
handle msg = do
  s <- get
  leaderWithoutFollowers' <- hasElectionTimerLeaderFired
  (out,l) <- runReaderT (runWriterT (handleHeartbeatTimeout msg)) $
             HeartbeatTimeoutEnv
             (JT._nodeRole s)
             leaderWithoutFollowers'
  mapM_ debug l
  case out of
    IsLeader -> do
      sendAllAppendEntries
      resetHeartbeatTimer
      hbMicrosecs <- view (JT.cfg . JT.heartbeatTimeout)
      JT.timeSinceLastAER %= (+ hbMicrosecs)
    NotLeader -> JT.timeSinceLastAER .= 0 -- probably overkill, but nice to know this gets set to 0 if not leader
    NoFollowers -> do
      timeout' <- return $ JT._timeSinceLastAER s
      enqueueEvent $ ElectionTimeout $ "Leader has not hear from followers in: " ++ show (timeout' `div` 1000) ++ "ms"
