{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}

-----------------------------------------------------------------------------

-----------------------------------------------------------------------------

{- |
 Module      :  OpenTelemetry.Context
 Copyright   :  (c) Ian Duncan, 2021
 License     :  BSD-3
 Description :  Carrier for execution-scoped values across API boundaries
 Maintainer  :  Ian Duncan
 Stability   :  experimental
 Portability :  non-portable (GHC extensions)

 The ability to correlate events across service boundaries is one of the principle concepts behind distributed tracing. To find these correlations, components in a distributed system need to be able to collect, store, and transfer metadata referred to as context.

 A context will often have information identifying the current span and trace, and can contain arbitrary correlations as key-value pairs.

 Propagation is the means by which context is bundled and transferred in and across services, often via HTTP headers.

 Together, context and propagation represent the engine behind distributed tracing.
-}
module OpenTelemetry.Context (
  Key (keyName),
  newKey,
  Context,
  HasContext (..),
  empty,
  lookup,
  insert,
  -- , insertWith
  adjust,
  delete,
  union,
  insertSpan,
  lookupSpan,
  removeSpan,
  insertBaggage,
  lookupBaggage,
  removeBaggage,
) where

import Control.Monad.IO.Class
import Data.Maybe
import Data.Text (Text)
import qualified Data.Vault.Strict as V
import OpenTelemetry.Baggage (Baggage)
import OpenTelemetry.Context.Types
import OpenTelemetry.Internal.Trace.Types
import System.IO.Unsafe
import Prelude hiding (lookup)


newKey :: (MonadIO m) => Text -> m (Key a)
newKey n = liftIO (Key n <$> V.newKey)


class HasContext s where
  contextL :: Lens' s Context


empty :: Context
empty = Context V.empty


lookup :: Key a -> Context -> Maybe a
lookup (Key _ k) (Context v) = V.lookup k v


insert :: Key a -> a -> Context -> Context
insert (Key _ k) x (Context v) = Context $ V.insert k x v


-- insertWith
--   :: (a -> a -> a)
--   -- ^ new value -> old value -> result
--   -> Key a -> a -> Context -> Context
-- insertWith f (Key _ k) x (Context v) = Context $ case V.lookup k of
--   Nothing -> V.insert k x v
--   Just ox -> V.insert k (f x ox) v

adjust :: (a -> a) -> Key a -> Context -> Context
adjust f (Key _ k) (Context v) = Context $ V.adjust f k v


delete :: Key a -> Context -> Context
delete (Key _ k) (Context v) = Context $ V.delete k v


union :: Context -> Context -> Context
union (Context v1) (Context v2) = Context $ V.union v1 v2


spanKey :: Key Span
spanKey = unsafePerformIO $ newKey "span"
{-# NOINLINE spanKey #-}


lookupSpan :: Context -> Maybe Span
lookupSpan = lookup spanKey


insertSpan :: Span -> Context -> Context
insertSpan = insert spanKey


removeSpan :: Context -> Context
removeSpan = delete spanKey


baggageKey :: Key Baggage
baggageKey = unsafePerformIO $ newKey "baggage"
{-# NOINLINE baggageKey #-}


lookupBaggage :: Context -> Maybe Baggage
lookupBaggage = lookup baggageKey


insertBaggage :: Baggage -> Context -> Context
insertBaggage b c = case lookup baggageKey c of
  Nothing -> insert baggageKey b c
  Just b' -> insert baggageKey (b <> b') c


removeBaggage :: Context -> Context
removeBaggage = delete baggageKey
