module Handler.Utils.Pandoc
  ( htmlField, htmlFieldSmall
  , renderMarkdownWith, parseMarkdownWith
  , htmlReaderOptions, markdownReaderOptions
  , markdownWriterOptions, htmlWriterOptions
  ) where

import Import.NoFoundation
import Handler.Utils.I18n

import qualified Data.Text as Text
import qualified Data.Text.Lazy as LT

import Control.Monad.Error.Class (liftEither)

import qualified Text.Pandoc as P

import Text.Blaze (preEscapedText)
import Text.Blaze.Html.Renderer.Text (renderHtml)
import Text.HTML.SanitizeXSS (sanitizeBalance)


data HtmlFieldKind
  = HtmlFieldNormal
  | HtmlFieldSmall
  deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
instance Universe HtmlFieldKind
instance Finite HtmlFieldKind

htmlField, htmlFieldSmall :: MonadLogger m => Field m StoredMarkup
htmlField = htmlField' HtmlFieldNormal
htmlFieldSmall = htmlField' HtmlFieldSmall


htmlField' :: MonadLogger m => HtmlFieldKind -> Field m StoredMarkup
htmlField' fieldKind = Field{..}
  where
    fieldEnctype = UrlEncoded

    fieldParse ((Text.strip -> t) : _) _ = runExceptT . runMaybeT $ do
      html <- assertM' (not . null . LT.strip . renderHtml) =<< liftEither (parseMarkdown t)
      return StoredMarkup
        { markupInputFormat = MarkupMarkdown
        , markupInput = fromStrict t
        , markupOutput = html
        }
    fieldParse []     _ = return $ Right Nothing

    fieldView theId name attrs val isReq = do
      val'
        <- let toMarkdown StoredMarkup{..} = case markupInputFormat of
                 MarkupMarkdown -> return $ toStrict markupInput
                 MarkupHtml -> renderMarkdown markupOutput
                 MarkupPlaintext -> plaintextToMarkdown $ toStrict markupInput
            in either return (maybeT (return mempty) . toMarkdown) val
                    
      let markdownExplanation = $(i18nWidgetFile "markdown-explanation")
      $(widgetFile "widgets/html-field")

    parseMarkdown  = parseMarkdownWith  markdownReaderOptions htmlWriterOptions
    renderMarkdown = renderMarkdownWith htmlReaderOptions     markdownWriterOptions
    plaintextToMarkdown = plaintextToMarkdownWith markdownWriterOptions

parseMarkdownWith :: P.ReaderOptions -> P.WriterOptions -> Text -> Either (SomeMessage site) Html
parseMarkdownWith readerOptions writerOptions text =
  bimap pandocError (preEscapedText . sanitizeBalance) . P.runPure $
    P.writeHtml5String writerOptions =<< P.readMarkdown readerOptions text
  where
    pandocError = SomeMessage . tshow

renderMarkdownWith :: (MonadLogger m, MonadPlus m) => P.ReaderOptions -> P.WriterOptions -> Html -> m Text
renderMarkdownWith readerOptions writerOptions html =
  either (\e -> logPandocError e >> mzero) return . P.runPure $
    P.writeMarkdown writerOptions =<< P.readHtml readerOptions (toStrict $ renderHtml html)
  where
    logPandocError = $logErrorS "renderMarkdown" . tshow

plaintextToMarkdownWith :: (MonadLogger m, MonadPlus m) => P.WriterOptions -> Text -> m Text
plaintextToMarkdownWith writerOptions text =
  either (\e -> logPandocError e >> mzero) return . P.runPure $
    P.writeMarkdown writerOptions pandoc
  where
    logPandocError = $logErrorS "renderMarkdown" . tshow
    pandoc = P.Pandoc mempty [P.Plain [P.Str text]]


htmlReaderOptions, markdownReaderOptions :: P.ReaderOptions
htmlReaderOptions = markdownReaderOptions
markdownReaderOptions = def
  { P.readerExtensions = P.pandocExtensions
      & P.enableExtension P.Ext_hard_line_breaks
      & P.enableExtension P.Ext_autolink_bare_uris
  , P.readerTabStop = 2
  }

markdownWriterOptions, htmlWriterOptions :: P.WriterOptions
markdownWriterOptions = def
  { P.writerExtensions = P.readerExtensions markdownReaderOptions
  , P.writerTabStop = P.readerTabStop markdownReaderOptions
  }
htmlWriterOptions = markdownWriterOptions
