{-# LANGUAGE DeriveAnyClass    #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes        #-}
-- | Streaming renderers for the Atom 1.0 standard.
module Text.Atom.Conduit.Render
  ( -- * Top-level
    renderAtomFeed
    -- * Elements
  , renderAtomEntry
  , renderAtomContent
  , renderAtomSource
  , renderAtomGenerator
  , renderAtomLink
  , renderAtomCategory
    -- * Constructs
  , renderAtomPerson
  , renderAtomText
  ) where

-- {{{ Imports
import           Text.Atom.Types

import           Control.Lens.Getter
import           Control.Monad

import           Data.Conduit
import           Data.Monoid
import           Data.NonNull
import           Data.Text              as Text
import           Data.Text.Encoding
import           Data.Time.Clock
import           Data.Time.LocalTime
import           Data.Time.RFC3339
import           Data.XML.Types

import           Text.XML.Stream.Render

import           URI.ByteString
-- }}}

-- | Render the top-level @atom:feed@ element.
renderAtomFeed :: (Monad m) => AtomFeed -> Source m Event
renderAtomFeed f = tag "feed" (attr "xmlns" "http://www.w3.org/2005/Atom") $ do
  forM_ (f^.feedAuthors_) $ renderAtomPerson "author"
  forM_ (f^.feedCategories_) renderAtomCategory
  forM_ (f^.feedContributors_) $ renderAtomPerson "contributor"
  forM_ (f^.feedEntries_) renderAtomEntry
  forM_ (f^.feedGenerator_) renderAtomGenerator
  forM_ (f^.feedIcon_) $ tag "icon" mempty . content . decodeUtf8 . serializeUriReference'
  tag "id" mempty . content . toNullable $ f^.feedId_
  forM_ (f^.feedLinks_) renderAtomLink
  forM_ (f^.feedLogo_) $ tag "logo" mempty . content . decodeUtf8 . serializeUriReference'
  forM_ (f^.feedRights_) $ renderAtomText "rights"
  forM_ (f^.feedSubtitle_) $ renderAtomText "subtitle"
  renderAtomText "title" $ f^.feedTitle_
  dateTag "updated" $ f^.feedUpdated_

-- | Render an @atom:entry@ element.
renderAtomEntry :: (Monad m) => AtomEntry -> Source m Event
renderAtomEntry e = tag "entry" mempty $ do
  forM_ (e^.entryAuthors_) $ renderAtomPerson "author"
  forM_ (e^.entryCategories_) renderAtomCategory
  forM_ (e^.entryContent_) renderAtomContent
  forM_ (e^.entryContributors_) $ renderAtomPerson "contributor"
  tag "id" mempty . content . toNullable $ e^.entryId_
  forM_ (e^.entryLinks_) renderAtomLink
  forM_ (e^.entryPublished_) $ dateTag "published"
  forM_ (e^.entryRights_) $ renderAtomText "rights"
  forM_ (e^.entrySource_) renderAtomSource
  forM_ (e^.entrySummary_) $ renderAtomText "summary"
  renderAtomText "title" (e^.entryTitle_)
  dateTag "updated" (e^.entryUpdated_)

-- | Render an @atom:content@ element.
renderAtomContent :: (Monad m) => AtomContent -> Source m Event
renderAtomContent (AtomContentInlineXHTML t) = tag "content" (attr "type" "xhtml")
  . tag "div" mempty $ content t
renderAtomContent (AtomContentOutOfLine ctype uri) = tag "content" (nonEmptyAttr "type" ctype <> attr "src" (decodeUtf8 $ serializeUriReference' uri)) $ return ()
renderAtomContent (AtomContentInlineText TypeHTML t) = tag "content" (attr "type" "html") $ content t
renderAtomContent (AtomContentInlineText TypeText t) = tag "content" mempty $ content t
renderAtomContent (AtomContentInlineOther ctype t) = tag "content" (attr "type" ctype) $ content t

-- | Render an @atom:source@ element.
renderAtomSource :: (Monad m) => AtomSource -> Source m Event
renderAtomSource s = tag "source" mempty $ do
  forM_ (s^.sourceAuthors_) $ renderAtomPerson "author"
  forM_ (s^.sourceCategories_) renderAtomCategory
  forM_ (s^.sourceContributors_) $ renderAtomPerson "contributor"
  forM_ (s^.sourceGenerator_) renderAtomGenerator
  forM_ (s^.sourceIcon_) $ tag "icon" mempty . content . decodeUtf8 . serializeUriReference'
  unless (Text.null $ s^.sourceId_) . tag "id" mempty . content $ s^.sourceId_
  forM_ (s^.sourceLinks_) renderAtomLink
  forM_ (s^.sourceLogo_) $ tag "logo" mempty . content . decodeUtf8 . serializeUriReference'
  forM_ (s^.sourceRights_) $ renderAtomText "rights"
  forM_ (s^.sourceSubtitle_) $ renderAtomText "subtitle"
  forM_ (s^.sourceTitle_) $ renderAtomText "title"
  forM_ (s^.sourceUpdated_) $ dateTag "updated"

-- | Render an @atom:generator@ element.
renderAtomGenerator :: (Monad m) => AtomGenerator -> Source m Event
renderAtomGenerator g = tag "generator" attributes . content . toNullable $ g^.generatorContent_
  where attributes = optionalAttr "uri" (decodeUtf8 . serializeUriReference' <$> g^.generatorUri_)
                     <> nonEmptyAttr "version" (g^.generatorVersion_)

-- | Render an @atom:link@ element.
renderAtomLink :: (Monad m) => AtomLink -> Source m Event
renderAtomLink l = tag "link" linkAttrs $ return ()
  where linkAttrs = attr "href" (decodeUtf8 . serializeUriReference' $ l^.linkHref_)
                    <> nonEmptyAttr "rel" (l^.linkRel_)
                    <> nonEmptyAttr "type" (l^.linkType_)
                    <> nonEmptyAttr "hreflang" (l^.linkLang_)
                    <> nonEmptyAttr "title" (l^.linkTitle_)
                    <> nonEmptyAttr "length" (l^.linkLength_)

-- | Render an @atom:category@ element.
renderAtomCategory :: (Monad m) => AtomCategory -> Source m Event
renderAtomCategory c = tag "category" attributes $ return ()
  where attributes = attr "term" (toNullable $ c^.categoryTerm_)
                     <> nonEmptyAttr "scheme" (c^.categoryScheme_)
                     <> nonEmptyAttr "label" (c^.categoryLabel_)

-- | Render an atom person construct.
renderAtomPerson :: (Monad m) => Name -> AtomPerson -> Source m Event
renderAtomPerson name p = tag name mempty $ do
  tag "name" mempty . content . toNullable $ p^.personName_
  unless (Text.null $ p^.personEmail_) $ tag "email" mempty . content $ p^.personEmail_
  forM_ (p^.personUri_) $ tag "uri" mempty . content . decodeUtf8 . serializeUriReference'

-- | Render an atom text construct.
renderAtomText :: (Monad m) => Name -> AtomText -> Source m Event
renderAtomText name (AtomXHTMLText t) = tag name (attr "type" "xhtml")
  . tag "div" mempty $ content t
renderAtomText name (AtomPlainText TypeHTML t) = tag name (attr "type" "html") $ content t
renderAtomText name (AtomPlainText TypeText t) = tag name mempty $ content t


dateTag :: (Monad m) => Name -> UTCTime -> Source m Event
dateTag name = tag name mempty . content . formatTimeRFC3339 . utcToZonedTime utc

nonEmptyAttr :: Name -> Text -> Attributes
nonEmptyAttr name value
  | value == mempty = mempty
  | otherwise = attr name value

-- serializeUriReference' :: UriReference -> ByteString
serializeUriReference' (UriReferenceUri u) = serializeURI' u
serializeUriReference' (UriReferenceRelativeRef r) = serializeRelativeRef' r