{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoImplicitPrelude #-}

module Readme.Lhs
  ( para
  , plain
  , table
  , code
  , Flavour(..)
  , readPandoc
  , renderMarkdown
  , output
  , runOutput
  , tweakHaskellCodeBlock
  ) where

import Protolude
import Data.Text as Text
import Text.Pandoc.Definition
import Text.Pandoc.Options
import Text.Pandoc
import qualified Data.Map as Map

type Output = Map Text Text

-- | doctest
-- >>> :set -XOverloadedStrings

-- | turn text into a Pandoc Paragraph Block
-- >>> para "hello"
-- Para [Str "hello"]
para :: Text -> Block
para = Para . fmap (Str . Text.unpack) . Text.lines

-- | turn text into a Pandoc Plain Block
-- >>> plain "hello"
-- Plain [Str "hello"]
plain :: Text -> Block
plain = Plain . fmap (Str . Text.unpack) . Text.lines

-- |
-- >>> inline "two\nlines"
-- [Str "two",Str "lines"]
inline :: Text -> [Inline]
inline = fmap (Str . Text.unpack) . Text.lines

-- | table caption headers alignments widths rows
-- >>> table "an example table" ["first column", "second column"] [AlignLeft, AlignRight] [0,0] [["first row", "1"], ["second row", "1000"]]
-- Table [Str "an example table"] [AlignLeft,AlignRight] [0.0,0.0] [[Para [Str "first column"]],[Para [Str "second column"]]] [[[Para [Str "first row"]],[Para [Str "1"]]],[[Para [Str "second row"]],[Para [Str "1000"]]]]
table :: Text -> [Text] -> [Alignment] -> [Int] -> [[Text]] -> Block
table caption hs as ws rs =
      Table
      (inline caption)
      as
      (fromIntegral <$> ws)
      ((:[]) . para <$> hs)
      (fmap ((:[]) . para) <$> rs)

-- | code identifier classes text
-- >>> code "name" ["sourceCode", "literate", "haskell"] "x = 1\n"
-- CodeBlock ("name",["sourceCode","literate","haskell"],[]) "x = 1\n"
code :: Text -> [Text] -> Text -> Block
code name classes =
  CodeBlock (Text.unpack name, Text.unpack <$> classes, []) . Text.unpack

-- | use LHS when you want to just add output to a *.lhs
-- | use GitHubMarkdown for rendering code and results on github
data Flavour = GitHubMarkdown | LHS

-- | exts LHS is equivalent to 'markdown+lhs'
--  exts GitHubMarkdown is equivalent to 'gfm'
exts :: Flavour -> Extensions
exts LHS = enableExtension Ext_literate_haskell $ getDefaultExtensions "markdown"
exts GitHubMarkdown = githubMarkdownExtensions

{- |
literate haskell code blocks comes out of markdown+lhs to native pandoc with the following classes:

["sourceCode","literate","haskell"]

  and then conversion to github flavour gives:

``` sourceCode
```

which doesn't lead to nice code highlighting on github (and elsewhere).  This function tweaks the list so that ["haskell"] is the class, and it all works.

-}
tweakHaskellCodeBlock :: Block -> Block
tweakHaskellCodeBlock (CodeBlock (id', cs, kv) b) =
  CodeBlock (id', bool cs ["haskell"] (Protolude.any ("haskell" ==) cs), kv) b
tweakHaskellCodeBlock x = x

-- | read a file into the pandoc AST
readPandoc :: FilePath -> Flavour -> IO (Either PandocError Pandoc)
readPandoc fp f = do
  t <- liftIO $ readFile fp
  runIO $ readMarkdown (def :: ReaderOptions) { readerExtensions = exts f} t

-- | render a pandoc AST
renderMarkdown :: Flavour -> Pandoc -> Either PandocError Text
renderMarkdown f (Pandoc meta bs) =
  runPure $
  writeMarkdown (def :: WriterOptions) { writerExtensions = exts f}
  (Pandoc meta (tweakHaskellCodeBlock <$> bs))

insertOutput :: Output -> Block -> Block
insertOutput m b = case b of
  (b'@ (CodeBlock (id', classes, kv) _)) ->
    bool b'
    (maybe
     (CodeBlock (id', classes, kv) mempty)
     (\x -> CodeBlock (id', classes, kv) . maybe mempty Text.unpack $ Map.lookup x m)
     (headMay . Protolude.filter ((`elem` classes) . Text.unpack) . Map.keys $ m))
    ("output" `elem` classes)
  b' -> b'

-- | add an output key-value pair to state
output :: (Monad m) => Text -> Text -> StateT (Map Text Text) m ()
output k v = modify (Map.insert k v)

-- | insert outputs into a new file
runOutput
  :: (FilePath, Flavour)
  -> (FilePath, Flavour)
  -> StateT Output IO ()
  -> IO (Either PandocError ())
runOutput (fi, flavi) (fo, flavo) out = do
  m <- execStateT out Map.empty
  p <- readPandoc fi flavi
  let w = do
              p' <- fmap (\(Pandoc meta bs) -> Pandoc meta (insertOutput m <$> bs)) p
              renderMarkdown flavo p'
  either (pure . Left) (\t -> writeFile fo t >> pure (Right ())) w