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

-- | Module supporting failover of a service by transforming the request each time a failure occurs up to a fixed number of attempts.
module Glue.Failover(
    FailoverOptions
  , defaultFailoverOptions
  , failover
  , maxFailovers
  , transformFailoverRequest
) where

import Glue.Types
import Control.Exception.Lifted hiding(throw)
import Control.Monad.Trans.Control

-- | Options for determining behaviour of failover services.
data FailoverOptions a = FailoverOptions {
    maxFailovers              :: Int      -- ^ The maximum numer of times the service will failover and make another attempt.
  , transformFailoverRequest  :: a -> a   -- ^ Each time the service performs a failover, transform the request with this function.
}

-- | Simple defaults that results in retrying 3 times in a dumb way immediately without transforming the request.
defaultFailoverOptions :: FailoverOptions a
defaultFailoverOptions = FailoverOptions { maxFailovers = 3, transformFailoverRequest = id }

-- | Creates a service that can transform requests with each subsequent failure.
failover :: (MonadBaseControl IO m) 
         => FailoverOptions a     -- ^ Instance of 'FailoverOptions' to configure the failover functionality.
         -> BasicService m a b    -- ^ The service to perform failover around.
         -> BasicService m a b
failover options service =
  let invokeService failCount request = 
                                        let afterFail   = (\(_ :: SomeException) -> invokeService (failCount + 1) ((transformFailoverRequest options) request))
                                            invoke      = service request
                                        in  if failCount >= (maxFailovers options) then invoke else catch invoke afterFail
  in  invokeService 0