{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
module Data.WebsiteContent
    ( WebsiteContent (..)
    , StackRelease (..)
    , Post (..)
    , loadWebsiteContent
    ) where

import ClassyPrelude.Yesod
import CMarkGFM
import Data.GhcLinks
import Data.Yaml
import System.FilePath (takeFileName)
import Text.Blaze.Html (preEscapedToHtml)
import Types

data WebsiteContent = WebsiteContent
    { wcHomepage      :: !Html
    , wcAuthors       :: !Html
    , wcOlderReleases :: !Html
    , wcGhcLinks      :: !GhcLinks
    , wcStackReleases :: ![StackRelease]
    , wcPosts         :: !(Vector Post)
    , wcSpamPackages  :: !(Set PackageNameP)
    -- ^ Packages considered spam which should not be displayed.
    }

data Post = Post
  { postTitle       :: !Text
  , postSlug        :: !Text
  , postAuthor      :: !Text
  , postTime        :: !UTCTime
  , postDescription :: !Text
  , postBody        :: !Html
  }

loadWebsiteContent :: FilePath -> IO WebsiteContent
loadWebsiteContent dir = do
    wcHomepage <- readHtml "homepage.html"
    wcAuthors <- readHtml "authors.html"
    wcOlderReleases <- readHtml "older-releases.html" `catchIO`
                    \_ -> readMarkdown "older-releases.md"
    wcGhcLinks <- readGhcLinks $ dir </> "stackage-cli"
    wcStackReleases <- decodeFileEither (dir </> "stack" </> "releases.yaml")
        >>= either throwIO return
    wcPosts <- loadPosts (dir </> "posts") `catchAny` \e -> do
      putStrLn $ "Error loading posts: " ++ tshow e
      return mempty
    wcSpamPackages <- decodeFileEither (dir </> "spam-packages.yaml")
                  >>= either throwIO (return . setFromList)
    return WebsiteContent {..}
  where
    readHtml fp = fmap preEscapedToMarkup $ readFileUtf8 $ dir </> fp
    readMarkdown fp = fmap (preEscapedToHtml . commonmarkToHtml
                        [optSmart]
                        [extTable, extAutolink])
               $ readFileUtf8 $ dir </> fp

loadPosts :: FilePath -> IO (Vector Post)
loadPosts dir =
     fmap (sortBy (\x y -> postTime y `compare` postTime x))
   $ runConduitRes
   $ sourceDirectory dir
  .| concatMapC (stripSuffix ".md")
  .| mapMC loadPost
  .| sinkVector
  where
    loadPost :: FilePath -> ResourceT IO Post
    loadPost noExt = handleAny (\e -> throwString $ "Could not parse " ++ noExt ++ ".md: " ++ show e) $ do
      bs <- readFile $ noExt ++ ".md"
      let slug = pack $ takeFileName noExt
          text = filter (/= '\r') $ decodeUtf8 bs
      (frontmatter, body) <-
        case lines text of
          "---":rest ->
            case break (== "---") rest of
              (frontmatter, "---":body) -> return (unlines frontmatter, unlines body)
              _ -> error "Missing closing --- on frontmatter"
          _ -> error "Does not start with --- frontmatter"
      case Data.Yaml.decodeEither' $ encodeUtf8 frontmatter of
        Left e -> throwIO e
        Right mkPost -> return $ mkPost slug $ preEscapedToHtml $ commonmarkToHtml
          [optSmart]
          [extTable, extAutolink]
          body

instance (slug ~ Text, body ~ Html) => FromJSON (slug -> body -> Post) where
  parseJSON = withObject "Post" $ \o -> do
    postTitle <- o .: "title"
    postAuthor <- o .: "author"
    postTime <- o .: "timestamp"
    postDescription <- o .: "description"
    return $ \postSlug postBody -> Post {..}

data StackRelease = StackRelease
    { srName    :: !Text
    , srPattern :: !Text
    }
instance FromJSON StackRelease where
    parseJSON = withObject "StackRelease" $ \o -> StackRelease
        <$> o .: "name"
        <*> o .: "pattern"
