{-# LANGUAGE CPP #-}

-- | 'LaTeX' values pretty printer.
--
--   Still experimental. Give it a try and send us your feedback! :)
module Text.LaTeX.Base.Pretty (
    -- * @LaTeX@ pretty printer
    prettyLaTeX
    -- * Configurable printer
  , docLaTeX
  ) where

import Text.LaTeX.Base.Syntax
import Text.LaTeX.Base.Render
import Data.Text.Prettyprint.Doc
  ( Doc, pretty
  , backslash, line, softline, hardline
  , braces, brackets
  , indent, align, vsep
  , list, encloseSep
  , LayoutOptions (..)
  , PageWidth (..)
  , layoutSmart
    )
import Data.Text.Prettyprint.Doc.Render.String (renderString)
import Data.Text (unpack,lines)
#if !MIN_VERSION_base(4,8,0)
import Data.Monoid (mconcat,mempty)
#endif

text :: Text -> Doc ann
text = pretty

-- | This function transforms a value of type 'LaTeX' to a 'Doc'.
--   You can then choose how to print this 'Doc' value using
--   the function from the "Text.PrettyPrint.Free" module.
docLaTeX :: LaTeX -> Doc ()
docLaTeX (TeXRaw t) = pretty $ unpack t
docLaTeX (TeXComm n as) = backslash <> pretty n <> align (mconcat (fmap docTeXArg as)) <> softline
docLaTeX (TeXCommS n) = backslash <> pretty n <> softline
docLaTeX (TeXEnv n as b) =
  let a = FixArg $ fromString n
  in  mconcat
       [ line
       , docLaTeX $ TeXComm "begin" $ a : as
       , indent 4 $ docLaTeX b
       , line
       , docLaTeX $ TeXComm "end" [a]
         ]
docLaTeX (TeXMath t b) =
  let (l,r) =
        case t of
          Parentheses -> ("\\(","\\)")
          Square -> ("\\[","\\]")
          Dollar -> ("$","$")
          DoubleDollar -> ("$$","$$")
  in  text l <> docLaTeX b <> text r
docLaTeX (TeXLineBreak m b) =
  text "\\\\" <> maybe mempty (brackets . pretty . unpack . render) m <> ( if b then text "*" else mempty )
docLaTeX (TeXBraces b) = braces $ docLaTeX b
docLaTeX (TeXComment t) =
  let ls = Data.Text.lines t
  in  if null ls
         then pretty '%' <> hardline
         else align (vsep $ fmap (pretty . ("% "++) . unpack) ls) <> hardline
docLaTeX (TeXSeq l1 l2) = docLaTeX l1 <> docLaTeX l2
docLaTeX TeXEmpty = mempty

docTeXArg :: TeXArg -> Doc ()
docTeXArg (FixArg l) = braces $ docLaTeX l
docTeXArg (OptArg l) = brackets $ docLaTeX l
docTeXArg (MOptArg ls) =
  if null ls then mempty
             else list $ fmap docLaTeX ls
docTeXArg (SymArg l) = docTeXArg $ MSymArg [l]
docTeXArg (MSymArg ls) = encloseSep (pretty '<') (pretty '>') (pretty ',') $ fmap docLaTeX ls
docTeXArg (ParArg l) = docTeXArg $ MParArg [l]
docTeXArg (MParArg ls) = encloseSep (pretty '(') (pretty ')') (pretty ',') $ fmap docLaTeX ls

-- | Pretty print a 'LaTeX' value. It produces a more human-friendly output than 'render'.
--
--   This function should be used only for debugging purposes since it may change
--   the semantics of the input in order to create a prettier output.
--   In other words, running a LaTeX compiler in the output file of @renderFile fp l@ may
--   produce a different document than running it in the output of @writeFile fp (prettyLaTeX l)@.
--   You should use 'renderFile' unless you really need to read the LaTeX file.
--
prettyLaTeX :: LaTeX -> String
prettyLaTeX = renderString . layoutSmart layout . docLaTeX

layout :: LayoutOptions
layout = LayoutOptions $ AvailablePerLine 60 1