    {-# LANGUAGE CPP #-}
    {-
    Copyright (C) 2009 John MacFarlane <jgm@berkeley.edu>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    -}

    {- Functions for exporting wiki pages in various formats.
    -}

    module Network.Gitit.Export ( exportFormats ) where
    import Text.Pandoc hiding (HTMLMathMethod(..))
    import qualified Text.Pandoc as Pandoc
    import Text.Pandoc.PDF (makePDF)
    import Text.Pandoc.SelfContained as SelfContained
    import Text.Pandoc.Shared (readDataFileUTF8)
    import qualified Text.Pandoc.UTF8 as UTF8
    import Network.Gitit.Server
    import Network.Gitit.Framework (pathForPage, getWikiBase)
    import Network.Gitit.State (getConfig)
    import Network.Gitit.Types
    import Network.Gitit.Cache (cacheContents, lookupCache)
    import Control.Monad.Trans (liftIO)
    import Control.Monad (unless)
    import Text.XHtml (noHtml)
    import qualified Data.ByteString as B
    import qualified Data.ByteString.Lazy as L
    import System.FilePath ((</>), takeDirectory)
    import Control.Exception (throwIO)
    import System.Directory (doesFileExist)
    import Text.HTML.SanitizeXSS
    import Text.Pandoc.Writers.RTF (writeRTFWithEmbeddedImages)
    import qualified Data.Text as T
    import Data.List (isPrefixOf)
    import Text.Highlighting.Kate (styleToCss, pygments)
    import Paths_gitit (getDataFileName)

    defaultRespOptions :: WriterOptions
    defaultRespOptions = def { writerStandalone = True, writerHighlight = True }

    respond :: String
            -> String
            -> (Pandoc -> IO L.ByteString)
            -> String
            -> Pandoc
            -> Handler
    respond mimetype ext fn page doc = liftIO (fn doc) >>=
      ok . setContentType mimetype .
      (if null ext then id else setFilename (page ++ "." ++ ext)) .
      toResponseBS B.empty

    respondX :: String -> String -> String
              -> (WriterOptions -> Pandoc -> IO L.ByteString)
              -> WriterOptions -> String -> Pandoc -> Handler
    respondX templ mimetype ext fn opts page doc = do
      cfg <- getConfig
      template' <- liftIO $ getDefaultTemplate (pandocUserData cfg) templ
      template <- case template' of
                      Right t  -> return t
                      Left e   -> liftIO $ throwIO e
      doc' <- if ext `elem` ["odt","pdf","beamer","epub","docx","rtf"]
                 then fixURLs page doc
                 else return doc
      respond mimetype ext (fn opts{writerTemplate = template
                                   ,writerUserDataDir = pandocUserData cfg})
              page doc'

    respondS :: String -> String -> String -> (WriterOptions -> Pandoc -> String)
              -> WriterOptions -> String -> Pandoc -> Handler
    respondS templ mimetype ext fn =
      respondX templ mimetype ext (\o d -> return $ UTF8.fromStringLazy $ fn o d)

    respondSlides :: String -> HTMLSlideVariant -> String -> Pandoc -> Handler
    respondSlides templ slideVariant page doc = do
        cfg <- getConfig
        base' <- getWikiBase
        let math = case mathMethod cfg of
                       MathML       -> Pandoc.MathML Nothing
                       WebTeX u     -> Pandoc.WebTeX u
                       JsMathScript -> Pandoc.JsMath
                                        (Just $ base' ++ "/js/jsMath/easy/load.js")
                       _            -> Pandoc.PlainMath
        let opts' = defaultRespOptions {
                         writerSlideVariant = slideVariant
                        ,writerIncremental = True
                        ,writerHtml5 = templ == "dzslides"
                        ,writerHTMLMathMethod = math}
        -- We sanitize the body only, to protect against XSS attacks.
        -- (Sanitizing the whole HTML page would strip out javascript
        -- needed for the slides.)  We then pass the body into the
        -- slide template using the 'body' variable.
        Pandoc meta blocks <- fixURLs page doc
        let body' = writeHtmlString opts'{writerStandalone = False}
                       (Pandoc meta blocks) -- just body
        let body'' = T.unpack
                   $ (if xssSanitize cfg then sanitizeBalance else id)
                   $ T.pack body'
        variables' <- if mathMethod cfg == MathML
                         then do
                            s <- liftIO $ readDataFileUTF8 (pandocUserData cfg)
                                      "MathMLinHTML.js"
                            return [("mathml-script", s)]
                         else return []
        template' <- liftIO $ getDefaultTemplate (pandocUserData cfg) templ
        template <- case template' of
                         Right t  -> return t
                         Left e   -> liftIO $ throwIO e
        dzcore <- if templ == "dzslides"
                      then do
                        dztempl <- liftIO $ readDataFileUTF8 (pandocUserData cfg)
                               $ "dzslides" </> "template.html"
                        return $ unlines
                            $ dropWhile (not . isPrefixOf "<!-- {{{{ dzslides core")
                            $ lines dztempl
                      else return ""
        let opts'' = opts'{
                    writerVariables =
                      ("body",body''):("dzslides-core",dzcore):("highlighting-css",pygmentsCss):variables'
                   ,writerTemplate = template
                   ,writerUserDataDir = pandocUserData cfg
                   }
        let h = writeHtmlString opts'' (Pandoc meta [])
    #if MIN_VERSION_pandoc(1,13,0)
        h' <- liftIO $ makeSelfContained opts'' h
    #else
        h' <- liftIO $ makeSelfContained (pandocUserData cfg) h
    #endif
        ok . setContentType "text/html;charset=UTF-8" .
          -- (setFilename (page ++ ".html")) .
          toResponseBS B.empty $ UTF8.fromStringLazy h'

    respondLaTeX :: String -> Pandoc -> Handler
    respondLaTeX = respondS "latex" "application/x-latex" "tex"
      writeLaTeX defaultRespOptions

    respondConTeXt :: String -> Pandoc -> Handler
    respondConTeXt = respondS "context" "application/x-context" "tex"
      writeConTeXt defaultRespOptions

    respondRTF :: String -> Pandoc -> Handler
    respondRTF = respondX "rtf" "application/rtf" "rtf"
      (\o d -> UTF8.fromStringLazy `fmap` writeRTFWithEmbeddedImages o d) defaultRespOptions

    respondRST :: String -> Pandoc -> Handler
    respondRST = respondS "rst" "text/plain; charset=utf-8" ""
      writeRST defaultRespOptions{writerReferenceLinks = True}

    respondMarkdown :: String -> Pandoc -> Handler
    respondMarkdown = respondS "markdown" "text/plain; charset=utf-8" ""
      writeMarkdown defaultRespOptions{writerReferenceLinks = True}

    #if MIN_VERSION_pandoc(1,14,0)
    respondCommonMark :: String -> Pandoc -> Handler
    respondCommonMark = respondS "commonmark" "text/plain; charset=utf-8" ""
      writeCommonMark defaultRespOptions{writerReferenceLinks = True}
    #endif

    respondPlain :: String -> Pandoc -> Handler
    respondPlain = respondS "plain" "text/plain; charset=utf-8" ""
      writePlain defaultRespOptions

    respondMan :: String -> Pandoc -> Handler
    respondMan = respondS "man" "text/plain; charset=utf-8" ""
      writeMan defaultRespOptions

    respondTexinfo :: String -> Pandoc -> Handler
    respondTexinfo = respondS "texinfo" "application/x-texinfo" "texi"
      writeTexinfo defaultRespOptions

    respondDocbook :: String -> Pandoc -> Handler
    respondDocbook = respondS "docbook" "application/docbook+xml" "xml"
      writeDocbook defaultRespOptions

    respondOrg :: String -> Pandoc -> Handler
    respondOrg = respondS "org" "text/plain; charset=utf-8" ""
      writeOrg defaultRespOptions

    #if MIN_VERSION_pandoc(1,16,0)
    respondICML :: String -> Pandoc -> Handler
    respondICML = respondX "icml" "application/xml; charset=utf-8" ""
                  (\o d -> fmap UTF8.fromStringLazy $ writeICML o d)
                             defaultRespOptions
    #else
    respondICML = respondS "icml" "application/xml; charset=utf-8" ""
                  writeICML defaultRespOptions
    #endif

    respondTextile :: String -> Pandoc -> Handler
    respondTextile = respondS "textile" "text/plain; charset=utf-8" ""
      writeTextile defaultRespOptions

    respondAsciiDoc :: String -> Pandoc -> Handler
    respondAsciiDoc = respondS "asciidoc" "text/plain; charset=utf-8" ""
      writeAsciiDoc defaultRespOptions

    respondMediaWiki :: String -> Pandoc -> Handler
    respondMediaWiki = respondS "mediawiki" "text/plain; charset=utf-8" ""
      writeMediaWiki defaultRespOptions

    respondODT :: String -> Pandoc -> Handler
    respondODT = respondX "opendocument" "application/vnd.oasis.opendocument.text"
                  "odt" writeODT defaultRespOptions

    respondEPUB :: String -> Pandoc -> Handler
    respondEPUB = respondX "html" "application/epub+zip" "epub" writeEPUB
                   defaultRespOptions

    respondDocx :: String -> Pandoc -> Handler
    respondDocx = respondX "native"
      "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
      "docx" writeDocx defaultRespOptions

    respondPDF :: Bool -> String -> Pandoc -> Handler
    respondPDF useBeamer page old_pndc = fixURLs page old_pndc >>= \pndc -> do
      cfg <- getConfig
      unless (pdfExport cfg) $ error "PDF export disabled"
      let cacheName = pathForPage page (defaultExtension cfg) ++ ".export.pdf"
      cached <- if useCache cfg
                   then lookupCache cacheName
                   else return Nothing
      pdf' <- case cached of
                Just (_modtime, bs) -> return $ Right $ L.fromChunks [bs]
                Nothing -> do
                  template' <- liftIO $ getDefaultTemplate (pandocUserData cfg)
                                      $ if useBeamer then "beamer" else "latex"
                  template  <- liftIO $ either throwIO return template'
                  let toc = tableOfContents cfg
                  res <- liftIO $ makePDF "pdflatex" writeLaTeX
                             defaultRespOptions{writerTemplate = template
                                               ,writerSourceURL = Just $ baseUrl cfg
                                               ,writerTableOfContents = toc
                                               ,writerBeamer = useBeamer} pndc
                  return res
      case pdf' of
           Left logOutput -> simpleErrorHandler ("PDF creation failed:\n"
                               ++ UTF8.toStringLazy logOutput)
           Right pdfBS -> do
                  case cached of
                    Nothing ->
                         cacheContents cacheName $ B.concat . L.toChunks $ pdfBS
                    _ -> return ()
                  ok $ setContentType "application/pdf" $ setFilename (page ++ ".pdf") $
                            (toResponse noHtml) {rsBody = pdfBS}

    -- | When we create a PDF or ODT from a Gitit page, we need to fix the URLs of any
    -- images on the page. Those URLs will often be relative to the staticDir, but the
    -- PDF or ODT processor only understands paths relative to the working directory.
    --
    -- Because the working directory will not in general be the root of the gitit instance
    -- at the time the Pandoc is fed to e.g. pdflatex, this function replaces the URLs of
    -- images in the staticDir with their correct absolute file path.
    fixURLs :: String -> Pandoc -> GititServerPart Pandoc
    fixURLs page pndc = do
        cfg <- getConfig
        defaultStatic <- liftIO $ getDataFileName $ "data" </> "static"

        let static = staticDir cfg
        let repoPath = repositoryPath cfg

    #if MIN_VERSION_pandoc(1,16,0)
        let go (Image attr ils (url, title)) = do
               fixedURL <- fixURL url
               return $ Image attr ils (fixedURL, title)
    #else
        let go (Image ils (url, title)) = do
               fixedURL <- fixURL url
               return $ Image ils (fixedURL, title)
    #endif
            go x                        = return x

            fixURL ('/':url) = resolve url
            fixURL url       = resolve $ takeDirectory page </> url

            resolve p = do
               sp <- doesFileExist $ static </> p
               dsp <- doesFileExist $ defaultStatic </> p
               return (if sp then static </> p
                       else (if dsp then defaultStatic </> p
                             else repoPath </> p))
        liftIO $ bottomUpM go pndc

    exportFormats :: Config -> [(String, String -> Pandoc -> Handler)]
    exportFormats cfg = if pdfExport cfg
                           then ("PDF", respondPDF False) :
                                ("Beamer", respondPDF True) :
                                rest
                           else rest
       where rest = [ ("LaTeX",     respondLaTeX)     -- (description, writer)
                    , ("ConTeXt",   respondConTeXt)
                    , ("Texinfo",   respondTexinfo)
                    , ("reST",      respondRST)
                    , ("Markdown",  respondMarkdown)
    #if MIN_VERSION_pandoc(1,14,0)
                    , ("CommonMark",respondCommonMark)
    #endif
                    , ("Plain text",respondPlain)
                    , ("MediaWiki", respondMediaWiki)
                    , ("Org-mode",  respondOrg)
                    , ("ICML",      respondICML)
                    , ("Textile",   respondTextile)
                    , ("AsciiDoc",  respondAsciiDoc)
                    , ("Man page",  respondMan)
                    , ("DocBook",   respondDocbook)
                    , ("DZSlides",  respondSlides "dzslides" DZSlides)
                    , ("Slidy",     respondSlides "slidy" SlidySlides)
                    , ("S5",        respondSlides "s5" S5Slides)
                    , ("EPUB",      respondEPUB)
                    , ("ODT",       respondODT)
                    , ("DOCX",      respondDocx)
                    , ("RTF",       respondRTF) ]

    pygmentsCss :: String
    pygmentsCss = styleToCss pygments