module LaunchDarkly.Server.Client.Internal
    ( Client(..)
    , ClientI(..)
    , Status(..)
    , clientVersion
    , setStatus
    , getStatusI
    ) where

import Data.Text                           (Text)
import Data.IORef                          (IORef, readIORef, atomicModifyIORef')
import GHC.Generics                        (Generic)
import Control.Concurrent                  (ThreadId)
import Control.Concurrent.MVar             (MVar)
import Data.Generics.Product               (getField)

import LaunchDarkly.Server.Config.Internal (ConfigI)
import LaunchDarkly.Server.Store.Internal  (StoreHandle, getInitializedC)
import LaunchDarkly.Server.Events          (EventState)

-- | Client is the LaunchDarkly client. Client instances are thread-safe.
-- Applications should instantiate a single instance for the lifetime of their
-- application.
newtype Client = Client ClientI

-- | The version string for this library.
clientVersion :: Text
clientVersion :: Text
clientVersion = Text
"2.2.0"

-- | The status of the client initialization.
data Status
    = Uninitialized
      -- ^ The client has not yet finished connecting to LaunchDarkly.
    | Unauthorized
      -- ^ The client attempted to connect to LaunchDarkly and was denied.
    | Initialized
      -- ^ The client has successfuly connected to LaunchDarkly.
    | ShuttingDown
      -- ^ The client is being terminated
    deriving (Status -> Status -> Bool
(Status -> Status -> Bool)
-> (Status -> Status -> Bool) -> Eq Status
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Status -> Status -> Bool
$c/= :: Status -> Status -> Bool
== :: Status -> Status -> Bool
$c== :: Status -> Status -> Bool
Eq)

setStatus :: ClientI -> Status -> IO ()
setStatus :: ClientI -> Status -> IO ()
setStatus ClientI
client Status
status' = IORef Status -> (Status -> (Status, ())) -> IO ()
forall a b. IORef a -> (a -> (a, b)) -> IO b
atomicModifyIORef' (ClientI -> IORef Status
forall (f :: Symbol) a s. HasField' f s a => s -> a
getField @"status" ClientI
client) ((Status -> (Status, ())) -> IO ())
-> (Status -> (Status, ())) -> IO ()
forall a b. (a -> b) -> a -> b
$ \Status
status ->
    case Status
status' of
        -- Only allow setting Initialized if Uninitialized
        Status
Initialized   -> (if Status
status Status -> Status -> Bool
forall a. Eq a => a -> a -> Bool
== Status
Uninitialized then Status
Initialized  else Status
status, ())
        -- Only allow setting status if not ShuttingDown
        Status
_             -> (if Status
status Status -> Status -> Bool
forall a. Eq a => a -> a -> Bool
== Status
ShuttingDown  then Status
ShuttingDown else Status
status', ())

getStatusI :: ClientI -> IO Status
getStatusI :: ClientI -> IO Status
getStatusI ClientI
client = IORef Status -> IO Status
forall a. IORef a -> IO a
readIORef (ClientI -> IORef Status
forall (f :: Symbol) a s. HasField' f s a => s -> a
getField @"status" ClientI
client) IO Status -> (Status -> IO Status) -> IO Status
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
    Status
Unauthorized -> Status -> IO Status
forall (f :: * -> *) a. Applicative f => a -> f a
pure Status
Unauthorized
    Status
ShuttingDown -> Status -> IO Status
forall (f :: * -> *) a. Applicative f => a -> f a
pure Status
ShuttingDown
    Status
_            -> StoreHandle IO -> StoreResultM IO Bool
forall store (m :: * -> *).
LaunchDarklyStoreRead store m =>
store -> StoreResultM m Bool
getInitializedC (ClientI -> StoreHandle IO
forall (f :: Symbol) a s. HasField' f s a => s -> a
getField @"store" ClientI
client) StoreResultM IO Bool
-> (Either Text Bool -> IO Status) -> IO Status
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
        Right Bool
True -> Status -> IO Status
forall (f :: * -> *) a. Applicative f => a -> f a
pure Status
Initialized
        Either Text Bool
_          -> Status -> IO Status
forall (f :: * -> *) a. Applicative f => a -> f a
pure Status
Uninitialized

data ClientI = ClientI
    { ClientI -> ConfigI
config             :: !(ConfigI)
    , ClientI -> StoreHandle IO
store              :: !(StoreHandle IO)
    , ClientI -> IORef Status
status             :: !(IORef Status)
    , ClientI -> EventState
events             :: !EventState
    , ClientI -> Maybe (ThreadId, MVar ())
downloadThreadPair :: !(Maybe (ThreadId, MVar ()))
    , ClientI -> Maybe (ThreadId, MVar ())
eventThreadPair    :: !(Maybe (ThreadId, MVar ()))
    } deriving ((forall x. ClientI -> Rep ClientI x)
-> (forall x. Rep ClientI x -> ClientI) -> Generic ClientI
forall x. Rep ClientI x -> ClientI
forall x. ClientI -> Rep ClientI x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep ClientI x -> ClientI
$cfrom :: forall x. ClientI -> Rep ClientI x
Generic)