{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

{-|
    Module      : Rollbar.Item.Data
    Description : Metadata about the item being reported
    Copyright   : (c) Hardy Jones, 2017
    License     : BSD3
    Maintainer  : jones3.hardy@gmail.com
    Stability   : experimental

    The majority of this module is metadata that is reported for each item.
-}

module Rollbar.Item.Data
    ( Data(..)
    , Context(..)
    , Fingerprint(..)
    , Framework(..)
    , Title(..)
    , UUID4(..)

    , RemoveHeaders
    ) where

import Data.Aeson
    ( FromJSON
    , ToJSON
    , Value(String)
    , defaultOptions
    , genericParseJSON
    , genericToEncoding
    , genericToJSON
    , parseJSON
    , toEncoding
    , toJSON
    )
import Data.Aeson.Types
    (Options, fieldLabelModifier, omitNothingFields, typeMismatch)
import Data.String      (IsString)
import Data.Time        (UTCTime)
import Data.UUID        (UUID, fromText, toText)

import GHC.Generics (Generic)

import Rollbar.Item.Body        (Body)
import Rollbar.Item.CodeVersion (CodeVersion)
import Rollbar.Item.Environment (Environment)
import Rollbar.Item.Hardcoded   (Hardcoded)
import Rollbar.Item.Level       (Level)
import Rollbar.Item.Person      (Person)
import Rollbar.Item.Request     (RemoveHeaders, Request)
import Rollbar.Item.Server      (Server)

import Rollbar.Item.Internal.Notifier (Notifier)
import Rollbar.Item.Internal.Platform (Platform)

import qualified Data.HashMap.Lazy as HM
import qualified Data.Text         as T

-- | The main payload of an item.
--  Most of this is metadata.
--
--  N.B. While it's entirely possible for you to create one of these yourself,
--  it's usually easier to use helpers like 'info' and 'error'.
data Data body headers
    = Data
        { body        :: Body body
        , codeVersion :: Maybe CodeVersion
        , context     :: Maybe Context
        , custom      :: Maybe (HM.HashMap T.Text Value)
        , environment :: Environment
        , fingerprint :: Maybe Fingerprint
        , framework   :: Maybe Framework
        , language    :: Hardcoded "haskell"
        , level       :: Level
        , notifier    :: Notifier
        -- ^ Metadata about this package. You probably shouldn't create this yourself.
        , person      :: Maybe Person
        , platform    :: Platform
        -- ^ Metadata about this package. You probably shouldn't create this yourself.
        , request     :: Maybe (Request headers)
        , server      :: Maybe Server
        , timestamp   :: Maybe UTCTime
        , title       :: Maybe Title
        , uuid        :: Maybe UUID4
        }
    deriving (Eq, Generic, Show)

instance FromJSON body => FromJSON (Data body headers) where
    parseJSON = genericParseJSON options

instance (RemoveHeaders headers, ToJSON body) => ToJSON (Data body headers) where
    toJSON = genericToJSON options
    toEncoding = genericToEncoding options

options :: Options
options = defaultOptions
    { fieldLabelModifier = codeVersionModifier
    , omitNothingFields = True
    }

codeVersionModifier :: (Eq s, IsString s) => s -> s
codeVersionModifier = \case
    "codeVersion" -> "code_version"
    str -> str

-- | The framework that is using this package.
--  E.g. "scotty", "servant", "yesod"
newtype Framework
    = Framework T.Text
    deriving (Eq, FromJSON, IsString, Show, ToJSON)

-- | The place in the code where this item came from.
newtype Context
    = Context T.Text
    deriving (Eq, FromJSON, IsString, Show, ToJSON)

-- | How to group the item.
newtype Fingerprint
    = Fingerprint T.Text
    deriving (Eq, FromJSON, IsString, Show, ToJSON)

-- | The title of the item.
newtype Title
    = Title T.Text
    deriving (Eq, FromJSON, IsString, Show, ToJSON)

-- | A unique identifier for each item.
newtype UUID4
    = UUID4 UUID
    deriving (Eq, Generic, Show)

instance FromJSON UUID4 where
    parseJSON v@(String s) =
        maybe (typeMismatch "UUID4" v) (pure . UUID4) $ fromText s
    parseJSON v = typeMismatch "UUID4" v

instance ToJSON UUID4 where
    toJSON (UUID4 u) = toJSON (toText u)
    toEncoding (UUID4 u) = toEncoding (toText u)