{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE FlexibleInstances   #-}
{-# LANGUAGE RankNTypes          #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies        #-}
{-# LANGUAGE TypeOperators       #-}
-- | Support for RSS extensions.
-- Cf specification at <http://web.resource.org/rss/1.0/modules/>.
--
-- To implement an RSS extension:
--
-- - Create a data-type, that will be used as a tag to identify the extension.
--   To allow stacking multiple extensions, your data-type should have kind * -> *
--
--   > data MyExtension otherExtensions = MyExtension otherExtensions
--
-- - Implement extension types for @\<channel\>@ and @\<item\>@ elements:
--
--   > data instance RssChannelExtension (MyExtension e) = MyExtensionChannel
--   >   { -- ... add your fields ...
--   >   , otherChannelExtensions :: RssChannelExtension e
--   >   }
--   >
--   > data instance RssItemExtension (MyExtension e) = MyExtensionItem
--   >   { -- ... add your fields ...
--   >   , otherItemExtensions :: RssItemExtension e
--   >   }
--
-- - Implement 'ParseRssExtension' and 'RenderRssExtension' type classes:
--
--   > -- Parser should rely on ZipConduit to be order-insensitive
--   > instance ParseRssExtension e => ParseRssExtension (MyExtension e) where
--   >   parseRssChannelExtension = getZipConduit $ MyExtensionChannel
--   >     <$> ZipConduit -- ... parse fields
--   >     <*> ZipConduit parseRssChannelExtension
--   >   parseRssItemExtension = -- ... similarly
--   >
--   > instance RenderRssExtension e => RenderRssExtension (MyExtension e) where
--   >   renderRssChannelExtension (MyExtensionChannel {- fields -} otherChannelExtensions) = do
--   >     -- ... render fields
--   >     renderRssChannelExtension otherChannelExtensions
--   >   renderRssItemExtension (MyExtensionItem {- fields -} otherItemExtensions) = -- ... similarly
module Text.RSS.Extensions where

-- {{{ Imports
import           Control.Exception.Safe  as Exception
import           Data.Conduit
import           Data.Maybe
import           Data.Proxy
import           Data.Text
import           Data.XML.Types
import           GHC.Generics
import           Text.Atom.Conduit.Parse
import           Text.Atom.Types
import           Text.Read               (readMaybe)
import           Text.RSS.Types
import           Text.XML.Stream.Parse
import           URI.ByteString
-- }}}

-- * Parsing

-- | Class of RSS extensions that can be parsed.
class ParseRssExtension a where
  -- | This parser will be fed with all 'Event's within the @\<channel\>@ element.
  -- Therefore, it is expected to ignore 'Event's unrelated to the RSS extension.
  parseRssChannelExtension :: MonadThrow m => ConduitT Event o m (RssChannelExtension a)
  -- | This parser will be fed with all 'Event's within the @\<item\>@ element.
  -- Therefore, it is expected to ignore 'Event's unrelated to the RSS extension.
  parseRssItemExtension :: MonadThrow m => ConduitT Event o m (RssItemExtension a)

instance ParseRssExtension NoExtensions where
  parseRssChannelExtension = pure NoChannelExtensions
  parseRssItemExtension = pure NoItemExtensions


-- * Rendering

-- | Class of RSS extensions that can be rendered.
class RenderRssExtension e where
  -- | Render extension for the @\<channel\>@ element.
  renderRssChannelExtension :: Monad m => RssChannelExtension e -> ConduitT () Event m ()
  -- | Render extension for the @\<item\>@ element.
  renderRssItemExtension :: Monad m => RssItemExtension e -> ConduitT () Event m ()

instance RenderRssExtension NoExtensions where
  renderRssChannelExtension = const $ pure ()
  renderRssItemExtension = const $ pure ()