{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}

module Df1.Html.Render
  ( log,
   -- * Themes
   --
   -- $themes
  )
where

import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Lazy as BL
import Data.Foldable (toList)
import Data.List (intercalate)
import qualified Data.Sequence as Seq
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.Text.Lazy as TL
import qualified Data.Time.Clock.System as Time
import qualified Df1 as D
import qualified Df1.Render as DR
import qualified Xmlbf as X
import Prelude hiding (log)

-- | Converts 'D.Log' into a list of 'X.Node's from "Xmlbf" to render it as HTML.
--
-- Example log:
-- @1999-12-20T07:11:39.230553031Z \/foo x=a y=b \/bar \/qux z=c z=d WARNING Something@
--
-- The generated HTML matches the following CSS selectors:
--
-- [@.df1-log.df1-debug@]:
--
-- [@.df1-log.df1-info@]:
--
-- [@.df1-log.df1-notice@]:
--
-- [@.df1-log.df1-warning@]:
--
-- [@.df1-log.df1-error@]:
--
-- [@.df1-log.df1-critical@]:
--
-- [@.df1-log.df1-alert@]:
--
-- [@.df1-log.df1-emergency@]: Top level container for a 'D.Log' entry of a particular 'D.Level'.
--
-- [@.df1-log .df1-time@]: Timestamp - Example: @1999-12-20T07:11:39.230553031Z@
--
-- [@.df1-log .df1-path@]: Full list of 'D.Path's - Example: @\/foo x=a y=b \/bar \/qux z=c z=d@
--
-- [@.df1-log .df1-path .df1-push@]: Single 'D.Push' - Examples: @\/foo@, @\/bar@, @\/qux@
--
-- [@.df1-log .df1-path .df1-push .df1-seg@]: Single 'D.Segment' - Example: @foo@
--
-- [@.df1-log .df1-path .df1-attr@]: Single 'D.Attr' - Example: @x=a@, @y=b@, @z=c@, @z=d@
--
-- [@.df1-log .df1-path .df1-attr .df1-key@]: Single 'D.Key' - Example: @x@, @y@, @z@, @z@
--
-- [@.df1-log .df1-path .df1-attr .df1-value@]: Single 'D.Value' - Example: @a@, @b@, @c@, @d@
--
-- [@.df1-log .df1-level@]: 'D.Level' - Example: @WARNING@
--
-- [@.df1-log .df1-msg@]: 'D.Message' - Example: @Something@
--
log :: D.Log -> [X.Node]
log :: Log -> [Node]
log Log
x =
  Text -> HashMap Text Text -> [Node] -> [Node]
X.element Text
"div" [(Text
"class", Text
"df1-log " forall a. Semigroup a => a -> a -> a
<> Level -> Text
levelClass (Log -> Level
D.log_level Log
x))] forall a b. (a -> b) -> a -> b
$
    forall a. Monoid a => [a] -> a
mconcat
      [ SystemTime -> [Node]
timeHtml (Log -> SystemTime
D.log_time Log
x),
        Text -> [Node]
X.text Text
" ",
        Seq Path -> [Node]
pathsHtml (Log -> Seq Path
D.log_path Log
x),
        Text -> [Node]
X.text Text
" ",
        Level -> [Node]
levelHtml (Log -> Level
D.log_level Log
x),
        Text -> [Node]
X.text Text
" ",
        Message -> [Node]
messageHtml (Log -> Message
D.log_message Log
x)
      ]

levelClass :: D.Level -> T.Text
levelClass :: Level -> Text
levelClass Level
l = Text
"df1-" forall a. Semigroup a => a -> a -> a
<> Text -> Text
T.toLower (Level -> Text
levelToText Level
l)

timeHtml :: Time.SystemTime -> [X.Node]
timeHtml :: SystemTime -> [Node]
timeHtml SystemTime
t = Text -> [Node] -> [Node]
spanClass Text
"df1-time" (Text -> [Node]
X.textLazy (Builder -> Text
textLazyFromBuilder (SystemTime -> Builder
DR.iso8601 SystemTime
t)))

textLazyFromBuilder :: BB.Builder -> TL.Text
textLazyFromBuilder :: Builder -> Text
textLazyFromBuilder Builder
b = Text -> Text
TL.fromStrict (ByteString -> Text
TE.decodeUtf8 (ByteString -> ByteString
BL.toStrict (Builder -> ByteString
BB.toLazyByteString Builder
b)))

levelHtml :: D.Level -> [X.Node]
levelHtml :: Level -> [Node]
levelHtml Level
l = Text -> [Node] -> [Node]
spanClass Text
"df1-level" (Text -> [Node]
X.text (Level -> Text
levelToText Level
l))

levelToText :: D.Level -> T.Text
levelToText :: Level -> Text
levelToText Level
l =
  case Level
l of
    Level
D.Debug -> Text
"DEBUG"
    Level
D.Info -> Text
"INFO"
    Level
D.Notice -> Text
"NOTICE"
    Level
D.Warning -> Text
"WARNING"
    Level
D.Error -> Text
"ERROR"
    Level
D.Critical -> Text
"CRITICAL"
    Level
D.Alert -> Text
"ALERT"
    Level
D.Emergency -> Text
"EMERGENCY"

messageHtml :: D.Message -> [X.Node]
messageHtml :: Message -> [Node]
messageHtml Message
m = Text -> [Node] -> [Node]
spanClass Text
"df1-msg" (Text -> [Node]
X.textLazy (Builder -> Text
textLazyFromBuilder (Message -> Builder
DR.message Message
m)))

pathsHtml :: Seq.Seq D.Path -> [X.Node]
pathsHtml :: Seq Path -> [Node]
pathsHtml Seq Path
ps = Text -> [Node] -> [Node]
spanClass Text
"df1-path" (forall a. [a] -> [[a]] -> [a]
intercalate (Text -> [Node]
X.text Text
" ") (forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Path -> [Node]
pathHtml (forall (t :: * -> *) a. Foldable t => t a -> [a]
toList Seq Path
ps)))

pathHtml :: D.Path -> [X.Node]
pathHtml :: Path -> [Node]
pathHtml Path
p = case Path
p of
  D.Push Segment
seg -> Text -> [Node] -> [Node]
spanClass Text
"df1-push" (Text -> [Node]
X.text Text
"/" forall a. Semigroup a => a -> a -> a
<> Segment -> [Node]
segmentHtml Segment
seg)
  D.Attr Key
key Value
val -> Text -> [Node] -> [Node]
spanClass Text
"df1-attr" (Key -> [Node]
keyHtml Key
key forall a. Semigroup a => a -> a -> a
<> Text -> [Node]
X.text Text
"=" forall a. Semigroup a => a -> a -> a
<> Value -> [Node]
valueHtml Value
val)

segmentHtml :: D.Segment -> [X.Node]
segmentHtml :: Segment -> [Node]
segmentHtml Segment
s = Text -> [Node] -> [Node]
spanClass Text
"df1-seg" (Text -> [Node]
X.textLazy (Builder -> Text
textLazyFromBuilder (Segment -> Builder
DR.segment Segment
s)))

keyHtml :: D.Key -> [X.Node]
keyHtml :: Key -> [Node]
keyHtml Key
k = Text -> [Node] -> [Node]
spanClass Text
"df1-key" (Text -> [Node]
X.textLazy (Builder -> Text
textLazyFromBuilder (Key -> Builder
DR.key Key
k)))

valueHtml :: D.Value -> [X.Node]
valueHtml :: Value -> [Node]
valueHtml Value
v = Text -> [Node] -> [Node]
spanClass Text
"df1-value" (Text -> [Node]
X.textLazy (Builder -> Text
textLazyFromBuilder (Value -> Builder
DR.value Value
v)))

spanClass :: T.Text -> [X.Node] -> [X.Node]
spanClass :: Text -> [Node] -> [Node]
spanClass Text
t = Text -> HashMap Text Text -> [Node] -> [Node]
X.element Text
"span" [(Text
"class", Text
t)]

-- $themes
--
-- If you need to style the rendered HTML, you can use some of the themes shipped with this library.
--
-- == [theme-solarized-dark.css](https://raw.githubusercontent.com/k0001/di/master/df1-html/theme-solarized-dark.css)
--
-- To use this theme, wrap the @.df1-log@ elements in a @.df1-theme-solarized-dark@ element.
--
-- ![theme-solarized-dark](https://raw.githubusercontent.com/k0001/di/master/df1-html/theme-solarized-dark.png)
--
-- == [theme-solarized-light.css](https://raw.githubusercontent.com/k0001/di/master/df1-html/theme-solarized-light.css)
--
-- To use this theme, wrap the @.df1-log@ elements in a @.df1-theme-solarized-light@ element.
--
-- ![theme-solarized-light](https://raw.githubusercontent.com/k0001/di/master/df1-html/theme-solarized-light.png)