{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeOperators #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  Text.BlogLiterately.Transform
-- Copyright   :  (c) 2008-2010 Robert Greayer, 2012 Brent Yorgey
-- License     :  GPL (see LICENSE)
-- Maintainer  :  Brent Yorgey <byorgey@gmail.com>
--
-- Tools for putting together a pipeline transforming the source for a
-- post into a completely formatted HTML document.
--
-----------------------------------------------------------------------------

module Text.BlogLiterately.Transform
    ( -- * Transforms
      Transform(..), runTransform, runTransforms

      -- * Standard transforms
      -- $standard

    , wptexifyXF
    , ghciXF
    , imagesXF
    , highlightXF
    , standardTransforms

      -- * Other transforms
      -- $other

    , centerImagesXF

      -- * Transforming documents
    , xformDoc

      -- * Utilities
    , whenA, fixLineEndings
    ) where

import           Control.Arrow              ( first, (>>>), arr
                                            , Kleisli(..), runKleisli )
import qualified Control.Category as C      ( Category, id )
import qualified Data.Traversable as T
import           Data.Default               ( def )
import qualified Data.Set         as S
import           Data.Bool.Extras           ( whenA )
import           Data.List                  ( isPrefixOf )

import           Text.Blaze.Html.Renderer.String      ( renderHtml )
import           Text.Pandoc
import           Text.Pandoc.Options

import           Text.BlogLiterately.Ghci
import           Text.BlogLiterately.Highlight
import           Text.BlogLiterately.Image
import           Text.BlogLiterately.LaTeX
import           Text.BlogLiterately.Options

-- | A document transformation consists of two parts: an actual
--   transformation, expressed as a function over Pandoc documents, and
--   a condition specifying whether the transformation should actually
--   be applied.
--
--   The transformation itself takes a 'BlogLiterately' configuration
--   as an argument.  You may of course ignore it if you do not need
--   to know anything about the configuration.  The @--xtra@ (or @-x@)
--   flag is also provided especially as a method of getting
--   information from the command-line to custom extensions. Arguments
--   passed via @-x@ on the command line are available from the 'xtra'
--   field of the 'BlogLiterately' configuration.
--
--   The transformation is then specified as a @'Kleisli' IO 'Pandoc'
--   'Pandoc'@ arrow, which is isomorphic to @Pandoc -> IO Pandoc@.  If
--   you have a pure function of type @Pandoc -> Pandoc@, wrap it in a
--   call to 'arr' to produce a 'Kleisli' arrow.  If you have a
--   function @Pandoc -> IO Pandoc@, wrap it in the 'Kleisli'
--   constructor.
--
--   For examples, see the implementations of the standard transforms
--   below.
data Transform = Transform
                 { getTransform :: BlogLiterately -> Kleisli IO Pandoc Pandoc
                   -- ^ A document transformation, which can depend on
                   --   BlogLiterately options and can have effects in
                   --   the @IO@ monad.
                 , xfCond       :: BlogLiterately -> Bool
                   -- ^ A condition under which to run the transformation.
                 }

-- | Run a 'Transform' (if its condition is met).
runTransform :: Transform -> BlogLiterately -> Kleisli IO Pandoc Pandoc
runTransform t bl = getTransform t bl `whenA` xfCond t bl

-- | Run a pipeline of 'Transform's.
runTransforms :: [Transform] -> BlogLiterately -> Kleisli IO Pandoc Pandoc
runTransforms ts = foldr (>>>) (C.id) . T.traverse runTransform ts

--------------------------------------------------
-- Standard transforms
--------------------------------------------------

-- $standard
-- These transforms are enabled by default in the standard
-- BlogLiterately executable.

-- | Format embedded LaTeX for WordPress (if the @wplatex@ flag is set).
wptexifyXF :: Transform
wptexifyXF = Transform (const (arr wpTeXify)) wplatex

-- | Format embedded @ghci@ sessions (if the @ghci@ flag is set).
ghciXF :: Transform
ghciXF = Transform (Kleisli . formatInlineGhci . file) ghci

-- | Upload embedded local images to the server (if the @uploadImages@
--   flag is set).
imagesXF :: Transform
imagesXF = Transform (Kleisli . uploadAllImages) uploadImages

-- | Perform syntax highlighting on code blocks.
highlightXF :: Transform
highlightXF = Transform
  (\bl -> arr (colourisePandoc (hsHighlight bl) (otherHighlight bl)))
  (const True)

-- | The standard set of transforms that are run by default:
--   'wptexifyXF', 'ghciXF', 'imagesXF', 'highlightXF'.
standardTransforms :: [Transform]
standardTransforms = [wptexifyXF, ghciXF, imagesXF, highlightXF]

--------------------------------------------------
-- Other transforms
--------------------------------------------------

-- $other
-- These transforms are not enabled by default.  To use them, see
-- "Text.BlogLiterately.Run".

-- | Center any images which occur in a paragraph by themselves.
--   Inline images are not affected.
centerImagesXF :: Transform
centerImagesXF = Transform (const . arr $ centerImages) (const True)

centerImages :: Pandoc -> Pandoc
centerImages = bottomUp centerImage
  where
    centerImage :: [Block] -> [Block]
    centerImage (img@(Para [Image altText (imgUrl, imgTitle)]) : bs) =
        RawBlock "html" "<div style=\"text-align: center;\">"
      : img
      : RawBlock "html" "</div>"
      : bs
    centerImage bs = bs

-- | Transform a complete input document string to an HTML output
--   string, given a list of transformation passes.
xformDoc :: BlogLiterately -> [Transform] -> (String -> IO String)
xformDoc bl xforms = runKleisli $
        arr     fixLineEndings
    >>> arr     (readMarkdown parseOpts)

    >>> runTransforms xforms bl

    >>> arr     (writeHtml writeOpts)
    >>> arr     renderHtml
  where
    parseOpts = def
                { readerExtensions = Ext_literate_haskell
                                     `S.insert` readerExtensions def
                , readerSmart      = True
                }
    writeOpts = def
                { writerReferenceLinks = True
                , writerHTMLMathMethod =
                  case math bl of
                    ""  -> PlainMath
                    opt -> mathOption opt }
    mathOption opt
      | opt `isPrefixOf` "latexmathml" ||
        opt `isPrefixOf` "asciimathml" = LaTeXMathML (mathUrlMaybe opt)
      | opt `isPrefixOf` "mathml"      = MathML (mathUrlMaybe opt)
      | opt `isPrefixOf` "mimetex"     =
          WebTeX (mathUrl "/cgi-bin/mimetex.cgi?" opt)
      | opt `isPrefixOf` "webtex"      = WebTeX (mathUrl webTeXURL opt)
      | opt `isPrefixOf` "jsmath"      = JsMath (mathUrlMaybe opt)
      | opt `isPrefixOf` "mathjax"     = MathJax (mathUrl mathJaxURL opt)
      | opt `isPrefixOf` "gladtex"     = GladTeX

    webTeXURL  = "http://chart.apis.google.com/chart?cht=tx&chl="
    mathJaxURL = "http://cdn.mathjax.org/mathjax/latest/MathJax.js"
                 ++ "?config=TeX-AMS-MML_HTMLorMML"

    urlPart = drop 1 . dropWhile (/='=')

    mathUrlMaybe opt = case urlPart opt of "" -> Nothing; x -> Just x
    mathUrl def opt  = case urlPart opt of "" -> def; x -> x

-- | Turn @CRLF@ pairs into a single @LF@.  This is necessary since
--   'readMarkdown' is picky about line endings.
fixLineEndings :: String -> String
fixLineEndings [] = []
fixLineEndings ('\r':'\n':cs) = '\n':fixLineEndings cs
fixLineEndings (c:cs) = c:fixLineEndings cs