-- | Formats Haskell source code using LaTeX macros.
module Language.Haskell.HsColour.LaTeX (hscolour) where

import Language.Haskell.HsColour.Classify as Classify
import Language.Haskell.HsColour.Colourise
import Language.Haskell.HsColour.General

-- | Formats Haskell source code as a complete LaTeX document.
hscolour :: ColourPrefs -- ^ Colour preferences.
         -> Bool        -- ^ Whether output should be partial (= no prologue).
         -> String      -- ^ Haskell source code.
         -> String      -- ^ A LaTeX document\/fragment containing the coloured 
                        --   Haskell source code.
hscolour pref partial =
  ( if partial then id else top'n'tail)
  . concatMap (renderToken pref)
  . tokenise

top'n'tail :: String -> String
top'n'tail  = (latexPrefix++) . (++latexSuffix)

-- | Wrap each lexeme in the appropriate LaTeX macro.
--   TODO: filter dangerous characters like "{}_$"
renderToken :: ColourPrefs -> (TokenType,String) -> String
renderToken pref (Space,text) = filterSpace text
renderToken pref (cls,text)   =
  let symb = case cls of
              String -> "``" ++ (dropFirst '\"' $ dropLast '\"' $ text) ++ "''"
              _      -> text
      style = colourise pref cls
      (pre, post) = unzip $ map latexHighlight style
  in concat pre ++ filterSpecial symb ++ concat post

-- | Filter white space characters.
filterSpace :: String
            -> String
filterSpace ('\n':ss) = '\\':'\\':(filterSpace ss)
filterSpace (' ':ss)  = "\\hsspace "++(filterSpace ss)
filterSpace ('\t':ss) = "\\hstab "++(filterSpace ss)
filterSpace (c:ss)    = c:(filterSpace ss)
filterSpace []        = []

-- | Filters the characters "#$%&~_^\{}" which are special
--   in LaTeX.
filterSpecial :: String  -- ^ The string to filter. 
              -> String  -- ^ The LaTeX-safe string.
filterSpecial ('#':cs)  = '\\':'#':(filterSpecial cs)
filterSpecial ('$':cs)  = '\\':'$':(filterSpecial cs)
filterSpecial ('%':cs)  = '\\':'%':(filterSpecial cs)
filterSpecial ('&':cs)  = '\\':'&':(filterSpecial cs)
filterSpecial ('~':cs)  = "\\tilde{ }"++(filterSpecial cs)
filterSpecial ('_':cs)  = '\\':'_':(filterSpecial cs)
filterSpecial ('^':cs)  = "\\ensuremath{\\hat{ }}"++(filterSpecial cs)
filterSpecial ('\\':cs) = "$\\backslash$"++(filterSpecial cs)
filterSpecial ('{':cs)  = '\\':'{':(filterSpecial cs)
filterSpecial ('}':cs)  = '\\':'}':(filterSpecial cs)
filterSpecial ('|':cs)  = "\\ensuremath{|}"++(filterSpecial cs)
filterSpecial ('<':'-':cs)  = "\\ensuremath{\\leftarrow}"++(filterSpecial cs)
filterSpecial ('<':cs)  = "\\ensuremath{\\langle}"++(filterSpecial cs)
filterSpecial ('-':'>':cs)  = "\\ensuremath{\\rightarrow}"++(filterSpecial cs)
filterSpecial ('>':cs)  = "\\ensuremath{\\rangle}"++(filterSpecial cs)
filterSpecial (c:cs)    = c:(filterSpecial cs)
filterSpecial []        = []


-- | Constructs the appropriate LaTeX macro for the given style.
latexHighlight :: Highlight -> (String, String)
latexHighlight Normal         = ("{\\rm{}", "}")
latexHighlight Bold           = ("{\\bf{}", "}")
latexHighlight Dim            = ("", "")
latexHighlight Underscore     = ("\\underline{", "}")
latexHighlight Blink          = ("", "")
latexHighlight ReverseVideo   = ("", "")
latexHighlight Concealed      = ("\\conceal{", "}")
latexHighlight (Foreground c) = ("\\textcolor{"++ latexColour c ++"}{", "}")
latexHighlight (Background c) = ("\\colorbox{"++ latexColour c ++"}{", "}")

-- | Translate a 'Colour' into a LaTeX colour name.
latexColour :: Colour -> String
latexColour Black   = "black"
latexColour Red     = "red"
latexColour Green   = "green"
latexColour Yellow  = "yellow"
latexColour Blue    = "blue"
latexColour Magenta = "magenta"
latexColour Cyan    = "cyan"
latexColour White   = "white"

-- | Generic LaTeX document preamble.
latexPrefix = unlines
    ["\\documentclass[a4paper, 12pt]{article}"
    ,"\\usepackage[usenames]{color}"
    ,"\\usepackage{hyperref}"
    ,"\\newsavebox{\\spaceb}"
    ,"\\newsavebox{\\tabb}"
    ,"\\savebox{\\spaceb}[1ex]{~}"
    ,"\\savebox{\\tabb}[4ex]{~}"
    ,"\\newcommand{\\hsspace}{\\usebox{\\spaceb}}"
    ,"\\newcommand{\\hstab}{\\usebox{\\tabb}}"
    ,"\\newcommand{\\conceal}[1]{}"
    ,"\\begin{document}"
    ,"\\noindent"
    ]

-- | Generic LaTeX document postamble.
latexSuffix = unlines
    [""
    ,"\\end{document}"
    ]