    {-# OPTIONS_GHC -fno-warn-orphans #-}
    {-# LANGUAGE FlexibleInstances, OverloadedStrings,
        ScopedTypeVariables, DeriveDataTypeable, CPP #-}
    #if MIN_VERSION_base(4,8,0)
    #else
    {-# LANGUAGE OverlappingInstances #-}
    #endif
    {- Copyright (C) 2012-2015 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
    -}

    {- |
       Module      : Text.Pandoc.Writers.Custom
       Copyright   : Copyright (C) 2012-2015 John MacFarlane
       License     : GNU GPL, version 2 or above

       Maintainer  : John MacFarlane <jgm@berkeley.edu>
       Stability   : alpha
       Portability : portable

    Conversion of 'Pandoc' documents to custom markup using
    a lua writer.
    -}
    module Text.Pandoc.Writers.Custom ( writeCustom ) where
    import Text.Pandoc.Definition
    import Text.Pandoc.Options
    import Data.List ( intersperse )
    import Data.Char ( toLower )
    import Data.Typeable
    import Scripting.Lua (LuaState, StackValue, callfunc)
    import Text.Pandoc.Writers.Shared
    import qualified Scripting.Lua as Lua
    import qualified Text.Pandoc.UTF8 as UTF8
    import Control.Monad (when)
    import Control.Exception
    import qualified Data.Map as M
    import Text.Pandoc.Templates
    import GHC.IO.Encoding (getForeignEncoding,setForeignEncoding, utf8)

    attrToMap :: Attr -> M.Map String String
    attrToMap (id',classes,keyvals) = M.fromList
        $ ("id", id')
        : ("class", unwords classes)
        : keyvals

    #if MIN_VERSION_hslua(0,4,0)
    #if MIN_VERSION_base(4,8,0)
    instance {-# OVERLAPS #-} StackValue [Char] where
    #else
    instance StackValue [Char] where
    #endif
      push lua cs = Lua.push lua (UTF8.fromString cs)
      peek lua i = do
                     res <- Lua.peek lua i
                     return $ UTF8.toString `fmap` res
      valuetype _ = Lua.TSTRING
    #else
    #if MIN_VERSION_base(4,8,0)
    instance {-# OVERLAPS #-} StackValue a => StackValue [a] where
    #else
    instance StackValue a => StackValue [a] where
    #endif
      push lua xs = do
        Lua.createtable lua (length xs + 1) 0
        let addValue (i, x) = Lua.push lua x >> Lua.rawseti lua (-2) i
        mapM_ addValue $ zip [1..] xs
      peek lua i = do
        top <- Lua.gettop lua
        let i' = if i < 0 then top + i + 1 else i
        Lua.pushnil lua
        lst <- getList lua i'
        Lua.pop lua 1
        return (Just lst)
      valuetype _ = Lua.TTABLE

    getList :: StackValue a => LuaState -> Int -> IO [a]
    getList lua i' = do
      continue <- Lua.next lua i'
      if continue
         then do
           next <- Lua.peek lua (-1)
           Lua.pop lua 1
           x <- maybe (fail "peek returned Nothing") return next
           rest <- getList lua i'
           return (x : rest)
         else return []
    #endif

    instance StackValue Format where
      push lua (Format f) = Lua.push lua (map toLower f)
      peek l n = fmap Format `fmap` Lua.peek l n
      valuetype _ = Lua.TSTRING

    instance (StackValue a, StackValue b) => StackValue (M.Map a b) where
      push lua m = do
        let xs = M.toList m
        Lua.createtable lua (length xs + 1) 0
        let addValue (k, v) = Lua.push lua k >> Lua.push lua v >>
                              Lua.rawset lua (-3)
        mapM_ addValue xs
      peek _ _ = undefined -- not needed for our purposes
      valuetype _ = Lua.TTABLE

    instance (StackValue a, StackValue b) => StackValue (a,b) where
      push lua (k,v) = do
        Lua.createtable lua 2 0
        Lua.push lua k
        Lua.push lua v
        Lua.rawset lua (-3)
      peek _ _ = undefined -- not needed for our purposes
      valuetype _ = Lua.TTABLE

    #if MIN_VERSION_base(4,8,0)
    instance {-# OVERLAPS #-} StackValue [Inline] where
    #else
    instance StackValue [Inline] where
    #endif
      push l ils = Lua.push l =<< inlineListToCustom l ils
      peek _ _ = undefined
      valuetype _ = Lua.TSTRING

    #if MIN_VERSION_base(4,8,0)
    instance {-# OVERLAPS #-} StackValue [Block] where
    #else
    instance StackValue [Block] where
    #endif
      push l ils = Lua.push l =<< blockListToCustom l ils
      peek _ _ = undefined
      valuetype _ = Lua.TSTRING

    instance StackValue MetaValue where
      push l (MetaMap m) = Lua.push l m
      push l (MetaList xs) = Lua.push l xs
      push l (MetaBool x) = Lua.push l x
      push l (MetaString s) = Lua.push l s
      push l (MetaInlines ils) = Lua.push l ils
      push l (MetaBlocks bs) = Lua.push l bs
      peek _ _ = undefined
      valuetype (MetaMap _) = Lua.TTABLE
      valuetype (MetaList _) = Lua.TTABLE
      valuetype (MetaBool _) = Lua.TBOOLEAN
      valuetype (MetaString _) = Lua.TSTRING
      valuetype (MetaInlines _) = Lua.TSTRING
      valuetype (MetaBlocks _) = Lua.TSTRING

    instance StackValue Citation where
      push lua cit = do
        Lua.createtable lua 6 0
        let addValue (k :: String, v) = Lua.push lua k >> Lua.push lua v >>
                              Lua.rawset lua (-3)
        addValue ("citationId", citationId cit)
        addValue ("citationPrefix", citationPrefix cit)
        addValue ("citationSuffix", citationSuffix cit)
        addValue ("citationMode", show (citationMode cit))
        addValue ("citationNoteNum", citationNoteNum cit)
        addValue ("citationHash", citationHash cit)
      peek = undefined
      valuetype _ = Lua.TTABLE

    data PandocLuaException = PandocLuaException String
        deriving (Show, Typeable)

    instance Exception PandocLuaException

    -- | Convert Pandoc to custom markup.
    writeCustom :: FilePath -> WriterOptions -> Pandoc -> IO String
    writeCustom luaFile opts doc@(Pandoc meta _) = do
      luaScript <- UTF8.readFile luaFile
      enc <- getForeignEncoding
      setForeignEncoding utf8
      lua <- Lua.newstate
      Lua.openlibs lua
      status <- Lua.loadstring lua luaScript luaFile
      -- check for error in lua script (later we'll change the return type
      -- to handle this more gracefully):
      when (status /= 0) $
    #if MIN_VERSION_hslua(0,4,0)
        Lua.tostring lua 1 >>= throw . PandocLuaException . UTF8.toString
    #else
        Lua.tostring lua 1 >>= throw . PandocLuaException
    #endif
      Lua.call lua 0 0
      -- TODO - call hierarchicalize, so we have that info
      rendered <- docToCustom lua opts doc
      context <- metaToJSON opts
                 (blockListToCustom lua)
                 (inlineListToCustom lua)
                 meta
      Lua.close lua
      setForeignEncoding enc
      let body = rendered
      if writerStandalone opts
         then do
           let context' = setField "body" body context
           return $ renderTemplate' (writerTemplate opts) context'
         else return body

    docToCustom :: LuaState -> WriterOptions -> Pandoc -> IO String
    docToCustom lua opts (Pandoc (Meta metamap) blocks) = do
      body <- blockListToCustom lua blocks
      callfunc lua "Doc" body metamap (writerVariables opts)

    -- | Convert Pandoc block element to Custom.
    blockToCustom :: LuaState      -- ^ Lua state
                  -> Block         -- ^ Block element
                  -> IO String

    blockToCustom _ Null = return ""

    blockToCustom lua (Plain inlines) = callfunc lua "Plain" inlines

    blockToCustom lua (Para [Image _ txt (src,tit)]) =
      callfunc lua "CaptionedImage" src tit txt

    blockToCustom lua (Para inlines) = callfunc lua "Para" inlines

    blockToCustom lua (RawBlock format str) =
      callfunc lua "RawBlock" format str

    blockToCustom lua HorizontalRule = callfunc lua "HorizontalRule"

    blockToCustom lua (Header level attr inlines) =
      callfunc lua "Header" level inlines (attrToMap attr)

    blockToCustom lua (CodeBlock attr str) =
      callfunc lua "CodeBlock" str (attrToMap attr)

    blockToCustom lua (BlockQuote blocks) = callfunc lua "BlockQuote" blocks

    blockToCustom lua (Table capt aligns widths headers rows') =
      callfunc lua "Table" capt (map show aligns) widths headers rows'

    blockToCustom lua (BulletList items) = callfunc lua "BulletList" items

    blockToCustom lua (OrderedList (num,sty,delim) items) =
      callfunc lua "OrderedList" items num (show sty) (show delim)

    blockToCustom lua (DefinitionList items) =
      callfunc lua "DefinitionList" items

    blockToCustom lua (Div attr items) =
      callfunc lua "Div" items (attrToMap attr)

    -- | Convert list of Pandoc block elements to Custom.
    blockListToCustom :: LuaState -- ^ Options
                      -> [Block]       -- ^ List of block elements
                      -> IO String
    blockListToCustom lua xs = do
      blocksep <- callfunc lua "Blocksep"
      bs <- mapM (blockToCustom lua) xs
      return $ mconcat $ intersperse blocksep bs

    -- | Convert list of Pandoc inline elements to Custom.
    inlineListToCustom :: LuaState -> [Inline] -> IO String
    inlineListToCustom lua lst = do
      xs <- mapM (inlineToCustom lua) lst
      return $ concat xs

    -- | Convert Pandoc inline element to Custom.
    inlineToCustom :: LuaState -> Inline -> IO String

    inlineToCustom lua (Str str) = callfunc lua "Str" str

    inlineToCustom lua Space = callfunc lua "Space"

    inlineToCustom lua SoftBreak = callfunc lua "SoftBreak"

    inlineToCustom lua (Emph lst) = callfunc lua "Emph" lst

    inlineToCustom lua (Strong lst) = callfunc lua "Strong" lst

    inlineToCustom lua (Strikeout lst) = callfunc lua "Strikeout" lst

    inlineToCustom lua (Superscript lst) = callfunc lua "Superscript" lst

    inlineToCustom lua (Subscript lst) = callfunc lua "Subscript" lst

    inlineToCustom lua (SmallCaps lst) = callfunc lua "SmallCaps" lst

    inlineToCustom lua (Quoted SingleQuote lst) = callfunc lua "SingleQuoted" lst

    inlineToCustom lua (Quoted DoubleQuote lst) = callfunc lua "DoubleQuoted" lst

    inlineToCustom lua (Cite cs lst) = callfunc lua "Cite" lst cs

    inlineToCustom lua (Code attr str) =
      callfunc lua "Code" str (attrToMap attr)

    inlineToCustom lua (Math DisplayMath str) =
      callfunc lua "DisplayMath" str

    inlineToCustom lua (Math InlineMath str) =
      callfunc lua "InlineMath" str

    inlineToCustom lua (RawInline format str) =
      callfunc lua "RawInline" format str

    inlineToCustom lua (LineBreak) = callfunc lua "LineBreak"

    inlineToCustom lua (Link attr txt (src,tit)) =
      callfunc lua "Link" txt src tit (attrToMap attr)

    inlineToCustom lua (Image attr alt (src,tit)) =
      callfunc lua "Image" alt src tit (attrToMap attr)

    inlineToCustom lua (Note contents) = callfunc lua "Note" contents

    inlineToCustom lua (Span attr items) =
      callfunc lua "Span" items (attrToMap attr)