-- | Parse feed from (lazy) text.
parseText :: Text -> Either ParseError Feed
parseText txt = do
  doc <- bimap XMLConduitError id $ XML.parseText XML.def txt
  k   <- parseKind doc
  deepseq doc $ return $ fromXML doc k
  where
  parseKind :: Document -> Either ParseError FeedKind
  parseKind doc = case doc^?root.localName of
    Just "RDF"  -> return RSS1Kind
    Just "rss"  -> return RSS2Kind
    Just "feed" -> return AtomKind
    _           -> Left   UnknownFeedKind

  fromXML :: Document -> FeedKind -> Feed
  fromXML doc k = emptyFeed k

    & title       .~ case k of
        RSS1Kind -> doc^?root./ell "channel"./ell "title".text.to T.strip
        RSS2Kind -> doc^?root./el  "channel"./el  "title".text.to T.strip
        AtomKind -> doc^?root./ell                "title".text.to T.strip

    & feedHome    .~ case k of
        RSS1Kind -> Nothing
        RSS2Kind -> Nothing
        AtomKind -> doc^?root.ell "feed"./ell "link".
                      attributeIs "type" "application/atom+xml".
                        attr "href".to T.strip

    & feedHtml    .~ case k of
        RSS1Kind -> doc^?root./ell "channel"./ell "link".text.to T.strip
        RSS2Kind -> doc^?root./ell "channel"./ell "link".text.to T.strip
        AtomKind -> doc^?root.ell "feed"./ell "link".
                      attributeIs "type" "text/html".attr "href".to T.strip

    & description .~ case k of
        RSS1Kind -> doc^?root.entire.ell "channel"./
                      ell "description".text.to T.strip
        RSS2Kind -> doc^?root.entire.ell "channel"./
                      ell "description".text.to T.strip
        AtomKind -> doc^?root.entire.ell "subtitle".text.to T.strip

    & date        .~ case k of
        RSS1Kind -> doc^?root.entire./ell "date".text.to T.strip
        RSS2Kind -> Nothing
        AtomKind -> doc^?root.entire.ell "updated".text.to T.strip

    & items       .~ case k of
        RSS1Kind -> doc^..root./ell "item" & mapped %~ parseItem
        RSS2Kind ->
          doc^..root./el "channel"./el "item" & mapped %~ parseItem
        AtomKind -> doc^..root./ell "entry" & mapped %~ parseItem
    where
    parseItem :: Element -> Item
    parseItem e = emptyItem
      & title       .~ case k of
          RSS1Kind -> e^?entire.ell "title".text.to T.strip
          RSS2Kind -> e^?entire.el  "title".text.to T.strip
          AtomKind -> e^?entire.ell "title".text.to T.strip
      & link        .~ case k of
          RSS1Kind -> e^?entire.ell "link".text.to T.strip
          RSS2Kind -> e^?entire.el  "link".text.to T.strip
          AtomKind -> e^?entire.ell "link".attr "href".to T.strip
      & date        .~ case k of
          RSS1Kind -> Nothing
          RSS2Kind -> e^?entire.el  "pubDate".text
          AtomKind -> e^?entire.ell "published".text <|>
                      e^?entire.ell "updated".text
      & author      .~ case k of
          RSS1Kind -> e^?entire.ell "creator".text.to T.strip
          RSS2Kind -> e^?entire.el  "author".text.to T.strip
          AtomKind -> e^?entire.ell "author"./ell "name".text.to T.strip
      & description .~ case k of
          RSS1Kind -> e^?entire.ell "description".text.to T.strip
          RSS2Kind -> e^?entire.el  "description".text.to T.strip
          AtomKind -> e^?entire.ell "summary".text.to T.strip <>
                      e^?entire.ell "content".text.to T.strip

------------------------------------------------------------------------
-- $errors
-- Sometimes errors occur when we parse.

-- | Datatype that captures all ways parsing can fail.
parseText :: Text -> Either ParseError Feed
parseText txt = do
  doc <- bimap XMLConduitError id $ XML.parseText XML.def txt
  k   <- parseKind doc
  deepseq doc $ return $ fromXML doc k
  where
  parseKind :: Document -> Either ParseError FeedKind
  parseKind doc = case doc^?root.localName of
    Just "RDF"  -> return RSS1Kind
    Just "rss"  -> return RSS2Kind
    Just "feed" -> return AtomKind
    _           -> Left   UnknownFeedKind

  fromXML :: Document -> FeedKind -> Feed
  fromXML doc k = emptyFeed k

    & title       .~ case k of
        RSS1Kind -> doc^?root./ell "channel"./ell "title".text.to T.strip
        RSS2Kind -> doc^?root./el  "channel"./el  "title".text.to T.strip
        AtomKind -> doc^?root./ell                "title".text.to T.strip

    & feedHome    .~ case k of
        RSS1Kind -> Nothing
        RSS2Kind -> Nothing
        AtomKind -> doc^?root.ell "feed"./ell "link".
                      attributeIs "type" "application/atom+xml".
                        attr "href".to T.strip

    & feedHtml    .~ case k of
        RSS1Kind -> doc^?root./ell "channel"./ell "link".text.to T.strip
        RSS2Kind -> doc^?root./ell "channel"./ell "link".text.to T.strip
        AtomKind -> doc^?root.ell "feed"./ell "link".
                      attributeIs "type" "text/html".attr "href".to T.strip

    & description .~ case k of
        RSS1Kind -> doc^?root.entire.ell "channel"./
                      ell "description".text.to T.strip
        RSS2Kind -> doc^?root.entire.ell "channel"./
                      ell "description".text.to T.strip
        AtomKind -> doc^?root.entire.ell "subtitle".text.to T.strip

    & date        .~ case k of
        RSS1Kind -> doc^?root.entire./ell "date".text.to T.strip
        RSS2Kind -> Nothing
        AtomKind -> doc^?root.entire.ell "updated".text.to T.strip

    & items       .~ case k of
        RSS1Kind -> doc^..root./ell "item" & mapped %~ parseItem
        RSS2Kind ->
          doc^..root./el "channel"./el "item" & mapped %~ parseItem
        AtomKind -> doc^..root./ell "entry" & mapped %~ parseItem
    where
    parseItem :: Element -> Item
    parseItem e = emptyItem
      & title       .~ case k of
          RSS1Kind -> e^?entire.ell "title".text.to T.strip
          RSS2Kind -> e^?entire.el  "title".text.to T.strip
          AtomKind -> e^?entire.ell "title".text.to T.strip
      & link        .~ case k of
          RSS1Kind -> e^?entire.ell "link".text.to T.strip
          RSS2Kind -> e^?entire.el  "link".text.to T.strip
          AtomKind -> e^?entire.ell "link".attr "href".to T.strip
      & date        .~ case k of
          RSS1Kind -> Nothing
          RSS2Kind -> e^?entire.el  "pubDate".text
          AtomKind -> e^?entire.ell "published".text <|>
                      e^?entire.ell "updated".text
      & author      .~ case k of
          RSS1Kind -> e^?entire.ell "creator".text.to T.strip
          RSS2Kind -> e^?entire.el  "author".text.to T.strip
          AtomKind -> e^?entire.ell "author"./ell "name".text.to T.strip
      & description .~ case k of
          RSS1Kind -> e^?entire.ell "description".text.to T.strip
          RSS2Kind -> e^?entire.el  "description".text.to T.strip
          AtomKind -> e^?entire.ell "summary".text.to T.strip <>
                      e^?entire.ell "content".text.to T.strip

------------------------------------------------------------------------
-- $errors
-- Sometimes errors occur when we parse.

-- | Datatype that captures all ways parsing can fail.
parseText txt = do
  doc <- bimap XMLConduitError id $ XML.parseText XML.def txt
  k   <- parseKind doc
  deepseq doc $ return $ fromXML doc k
  where
  parseKind :: Document -> Either ParseError FeedKind
  parseKind doc = case doc^?root.localName of
    Just "RDF"  -> return RSS1Kind
    Just "rss"  -> return RSS2Kind
    Just "feed" -> return AtomKind
    _           -> Left   UnknownFeedKind

  fromXML :: Document -> FeedKind -> Feed
  fromXML doc k = emptyFeed k

    & title       .~ case k of
        RSS1Kind -> doc^?root./ell "channel"./ell "title".text.to T.strip
        RSS2Kind -> doc^?root./el  "channel"./el  "title".text.to T.strip
        AtomKind -> doc^?root./ell                "title".text.to T.strip

    & feedHome    .~ case k of
        RSS1Kind -> Nothing
        RSS2Kind -> Nothing
        AtomKind -> doc^?root.ell "feed"./ell "link".
                      attributeIs "type" "application/atom+xml".
                        attr "href".to T.strip

    & feedHtml    .~ case k of
        RSS1Kind -> doc^?root./ell "channel"./ell "link".text.to T.strip
        RSS2Kind -> doc^?root./ell "channel"./ell "link".text.to T.strip
        AtomKind -> doc^?root.ell "feed"./ell "link".
                      attributeIs "type" "text/html".attr "href".to T.strip

    & description .~ case k of
        RSS1Kind -> doc^?root.entire.ell "channel"./
                      ell "description".text.to T.strip
        RSS2Kind -> doc^?root.entire.ell "channel"./
                      ell "description".text.to T.strip
        AtomKind -> doc^?root.entire.ell "subtitle".text.to T.strip

    & date        .~ case k of
        RSS1Kind -> doc^?root.entire./ell "date".text.to T.strip
        RSS2Kind -> Nothing
        AtomKind -> doc^?root.entire.ell "updated".text.to T.strip

    & items       .~ case k of
        RSS1Kind -> doc^..root./ell "item" & mapped %~ parseItem
        RSS2Kind ->
          doc^..root./el "channel"./el "item" & mapped %~ parseItem
        AtomKind -> doc^..root./ell "entry" & mapped %~ parseItem
    where
    parseItem :: Element -> Item
    parseItem e = emptyItem
      & title       .~ case k of
          RSS1Kind -> e^?entire.ell "title".text.to T.strip
          RSS2Kind -> e^?entire.el  "title".text.to T.strip
          AtomKind -> e^?entire.ell "title".text.to T.strip
      & link        .~ case k of
          RSS1Kind -> e^?entire.ell "link".text.to T.strip
          RSS2Kind -> e^?entire.el  "link".text.to T.strip
          AtomKind -> e^?entire.ell "link".attr "href".to T.strip
      & date        .~ case k of
          RSS1Kind -> Nothing
          RSS2Kind -> e^?entire.el  "pubDate".text
          AtomKind -> e^?entire.ell "published".text <|>
                      e^?entire.ell "updated".text
      & author      .~ case k of
          RSS1Kind -> e^?entire.ell "creator".text.to T.strip
          RSS2Kind -> e^?entire.el  "author".text.to T.strip
          AtomKind -> e^?entire.ell "author"./ell "name".text.to T.strip
      & description .~ case k of
          RSS1Kind -> e^?entire.ell "description".text.to T.strip
          RSS2Kind -> e^?entire.el  "description".text.to T.strip
          AtomKind -> e^?entire.ell "summary".text.to T.strip <>
                      e^?entire.ell "content".text.to T.strip

------------------------------------------------------------------------
-- $errors
-- Sometimes errors occur when we parse.

-- | Datatype that captures all ways parsing can fail.