module Tintin.Render where

import Tintin.Core

require Tintin.Capabilities.Logging
require Tintin.Capabilities.Filesystem
require Tintin.Capabilities.Process
require Tintin.Domain.HtmlFile
require Tintin.Domain.DocumentationFile
require Tintin.Domain.Project
require Tintin.Html.Templating
require Tintin.Errors

require Data.Map
require Data.Text

data Render

perform :: ( Has Logging.Capability eff
           , Has Filesystem.Capability eff
           , Has Process.Capability eff
           )
        => HtmlFile.BuildTool
        -> [DocumentationFile]
        -> Effectful eff [HtmlFile]
perform buildTool docFiles = do
  Logging.debug "Rendering"
  (errors, htmlFiles) <- docFiles
                         &  map  HtmlFile.fromDocumentationFile
                         &  mapM (HtmlFile.run buildTool)
                         & fmap partitionEithers
  unless (null errors) (Errors.textDie (HtmlFile.showCompilationError <$> errors))
  return htmlFiles


writeOutput :: ( Has Logging.Capability eff
               , Has Filesystem.Capability eff
               )
            => OutputDirectory
            -> Project.Info
            -> Effectful eff ()
writeOutput (OutputDirectory od) info = do
  Filesystem.makeDirectory (Filesystem.Path od)
  Logging.debug "Writing HTML output"
  forM_ (withContext (Project.pages info)) $ \(page, context) -> do
    let newContent = Templating.wrap info context page
    let slash = if "/" `Text.isSuffixOf` od then "" else "/"
    Filesystem.writeFile (Filesystem.Path $ od <> slash <> Project.filename page) newContent

-- | Pair up each page with context (next and previous links) by
-- alphabetical order, excluding index page.
--
-- This function is a little more complicated than might be expected
-- because the functionality is a little nuanced.  It has to:
--
-- 1.  Pair each page with next and previous links based on an alphabetical
--     sorting, /excluding/ the index page.
-- 2.  Give the /first/ non-index page, alphabetically, a "previous" link
--     pointing to the index /if it exists/.
-- 3.  Give the index, if it exists, a "next' link to the first non-index
--     page.
--
withContext
    :: [Project.Page]
    -> [(Project.Page, Project.Context)]
withContext ps = elems contextMap
  where
    -- | Pre-processing function.  Using a single fold, pick out the index
    -- page link (if it exists) and also accumulate a Map of all non-index
    -- pages, keyed by filenames.  The map enforces that the filenames are
    -- stored alphabetically.
    indexRef :: Maybe Project.PageRef
    pageMap  :: Map Text Project.Page
    (First indexRef, pageMap)
        = foldMap (\p -> let fn = Project.filename p
                         in  if fn == "index.html"
                               then (First (Just (makeRef p)), mempty     )
                               else (mempty                  , one (fn, p))
                  ) ps
    -- | Accumulate a final result map pairing each file with its
    -- 'makeContext' result.  The keys are the filenames, to enforce
    -- alphabetical sorting.
    contextMap :: Map Text (Project.Page, Project.Context)
    contextMap = ps
             & fmap (\p -> (Project.filename p, (p, makeContext p)))
             & Map.fromList
    -- | Actual function that pairs up each page with its context (next and
    -- previous links).
    --
    -- If the page is "index.html", use just the first item in 'pageMap',
    -- the map of non-index pages, using 'Map.lookupMin'
    --
    -- Otherwise, use 'Map.lookupLT' and 'Map.lookupGT' to find the "next
    -- and previous" pages in 'pageMap' (the map of non-index pages).  And,
    -- if there is no previous page, use the index page instead.
    makeContext :: Project.Page -> Project.Context
    makeContext p
        | fn == "index.html" = Map.lookupMin pageMap
                                 & fmap snd
                                 & fmap makeRef
                                 & Project.Context Nothing
        | otherwise          =
            let prev = Map.lookupLT fn pageMap
                        & fmap snd
                        & fmap makeRef
                next = Map.lookupGT fn pageMap
                        & fmap snd
                        & fmap makeRef
            in  Project.Context (prev <|> indexRef) next  -- if no prev, use index
      where
        fn = Project.filename p
    -- | Construct a link/reference from a page.
    makeRef :: Project.Page -> Project.PageRef
    makeRef p = Project.PageRef
      { refTitle    = Project.title p
      , refFilename = Project.filename p
      }