{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE OverloadedStrings #-}
{-|
Module      : Instana.SDK.Span.Span
Description : A type for spans (entries, exits or intermediates).
-}
module Instana.SDK.Span.Span
  ( Span (Entry, Exit)
  , SpanKind (EntryKind, ExitKind, IntermediateKind)
  , traceId
  , spanId
  , spanKind
  , parentId
  , spanName
  , timestamp
  , errorCount
  , addToErrorCount
  , serviceName
  , setServiceName
  , spanData
  , addRegisteredData
  , addRegisteredDataAt
  , addTag
  , addTagAt
  ) where


import           Data.Aeson                 (Value, (.=))
import qualified Data.Aeson                 as Aeson
import qualified Data.List                  as List
import           Data.Text                  as T
import           Data.Text                  (Text)
import           GHC.Generics

import           Instana.SDK.Internal.Id    (Id)
import           Instana.SDK.Span.EntrySpan (EntrySpan)
import qualified Instana.SDK.Span.EntrySpan as EntrySpan
import           Instana.SDK.Span.ExitSpan  (ExitSpan)
import qualified Instana.SDK.Span.ExitSpan  as ExitSpan


-- |The span kind (entry, exit or intermediate).
data SpanKind =
    -- |The monitored componenent receives a call.
    EntryKind
    -- |The monitored componenent calls something else.
  | ExitKind
    -- |An additional annotation that is added to the trace while a traced call
    -- is being processed.
  | IntermediateKind
  deriving (Eq, Generic, Show)


-- |A span.
data Span =
    Entry EntrySpan
  | Exit ExitSpan
  deriving (Eq, Generic, Show)


-- |Accessor for the trace ID.
traceId :: Span -> Id
traceId span_ =
  case span_ of
    Entry entry -> EntrySpan.traceId entry
    Exit exit   -> ExitSpan.traceId exit


-- |Accessor for the span ID.
spanId :: Span -> Id
spanId span_ =
  case span_ of
    Entry entry -> EntrySpan.spanId entry
    Exit exit   -> ExitSpan.spanId exit


-- |Parent span ID.
parentId :: Span -> Maybe Id
parentId span_ =
  case span_ of
    Entry entry -> EntrySpan.parentId entry
    Exit exit   -> Just $ ExitSpan.parentId exit


-- |Name of span.
spanName :: Span -> Text
spanName span_ =
  case span_ of
    Entry entry -> EntrySpan.spanName entry
    Exit exit   -> ExitSpan.spanName exit


-- |Kind of span.
spanKind :: Span -> SpanKind
spanKind span_ =
  case span_ of
    Entry _ -> EntryKind
    Exit _  -> ExitKind


-- |Start time.
timestamp :: Span -> Int
timestamp span_ =
  case span_ of
    Entry entry -> EntrySpan.timestamp entry
    Exit exit   -> ExitSpan.timestamp exit


-- |Error count.
errorCount :: Span -> Int
errorCount span_ =
  case span_ of
    Entry entry -> EntrySpan.errorCount entry
    Exit exit   -> ExitSpan.errorCount exit


-- |Add to the error count.
addToErrorCount :: Int -> Span -> Span
addToErrorCount increment span_ =
  case span_ of
    Entry entry ->
      Entry $ EntrySpan.addToErrorCount increment entry
    Exit exit ->
      Exit $ ExitSpan.addToErrorCount increment exit


-- |An optional attribute for overriding the name of the service in Instana.
serviceName :: Span -> Maybe Text
serviceName span_ =
  case span_ of
    Entry entry -> EntrySpan.serviceName entry
    Exit exit   -> ExitSpan.serviceName exit


-- |Override the name of the service for the associated call in Instana.
setServiceName :: Text -> Span -> Span
setServiceName serviceName_ span_ =
  case span_ of
    Entry entry ->
      Entry $ EntrySpan.setServiceName serviceName_ entry
    Exit exit ->
      Exit $ ExitSpan.setServiceName serviceName_ exit


-- |Optional additional span data.
spanData :: Span -> Value
spanData span_ =
  case span_ of
    Entry entry -> EntrySpan.spanData entry
    Exit exit   -> ExitSpan.spanData exit


-- |Add a value to the span's custom tags section. This should be used for SDK
-- spans instead of addRegisteredData.
addTag :: Value -> Span -> Span
addTag value span_ =
  addRegisteredDataAt "sdk.custom.tags" value span_


-- |Add a value to the given path to the span's custom tags section. This should
-- be used for SDK spans instead of addRegisteredDataAt.
addTagAt :: Aeson.ToJSON a => Text -> a -> Span -> Span
addTagAt path value span_ =
  addRegisteredDataAt (T.concat ["sdk.custom.tags.", path]) value span_



-- |Add a value to the span's data section. This should only be used for
-- registered spans, not for SDK spans. For SDK spans, you should use addTag
-- instead.
addRegisteredData :: Value -> Span -> Span
addRegisteredData value span_ =
  case span_ of
    Entry entry -> Entry $ EntrySpan.addData value entry
    Exit exit   -> Exit $ ExitSpan.addData value exit


-- |Add a value at the given path to the span's data section. For SDK spans, you
-- should use addTagAt instead.
addRegisteredDataAt :: Aeson.ToJSON a => Text -> a -> Span -> Span
addRegisteredDataAt path value span_ =
  let
    pathSegments = T.splitOn "." path
    newData = List.foldr
      (\nextPathSegment accumulatedAesonValue ->
        Aeson.object [
          nextPathSegment .= accumulatedAesonValue
        ]
      )
      (Aeson.toJSON value)
      pathSegments
  in
  addRegisteredData newData span_