{-# LANGUAGE CPP                 #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Skylighting.Format.ConTeXt
  ( formatConTeXtInline
  , formatConTeXtBlock
  , styleToConTeXt
  ) where

import Control.Monad (mplus)
import Data.Char (isSpace)
import Data.List (sort)
import qualified Data.Map as Map
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import qualified Data.Text as Text
import Skylighting.Types
import Text.Printf
#if !MIN_VERSION_base(4,11,0)
import Data.Semigroup
#endif

formatConTeXt :: [SourceLine] -> Text
formatConTeXt = Text.intercalate (Text.singleton '\n')
                       . map sourceLineToConTeXt

-- | Formats tokens as ConTeXt using custom commands inside a @\type{}@.
-- A @KeywordTok@ is rendered using @\\KeywordTok{..}@, and so on.
formatConTeXtInline :: FormatOptions -> [SourceLine] -> Text
formatConTeXtInline _opts ls =
  "\\highlight{" <> formatConTeXt ls <> "}"

sourceLineToConTeXt :: SourceLine -> Text
sourceLineToConTeXt =
  Text.replace "/ETEX/BTEX" "" .
  Text.replace "/ETEX /BTEX" " " .
  mconcat . map tokenToConTeXt

tokenToConTeXt :: Token -> Text
tokenToConTeXt (NormalTok, txt)
  | Text.all isSpace txt = escapeConTeXt txt
tokenToConTeXt (toktype, txt)   = "/BTEX\\" <>
  (Text.pack (show toktype) <> "{" <> fixSpaces (escapeConTeXt txt) <> "}/ETEX")
 where
  -- Always place the second of two consecutive spaces in a group. The
  -- ConTeXt parser would otherwise collapse all spaces into a single
  -- space.
  fixSpaces = Text.replace "  " " { }"

escapeConTeXt :: Text -> Text
escapeConTeXt = Text.concatMap escapeConTeXtChar
  where escapeConTeXtChar c =
         case c of
           '\\' -> "\\letterbackslash{}"
           '{'  -> "\\letteropenbrace{}"
           '}'  -> "\\letterclosebrace{}"
           '|'  -> "\\letterbar{}"
           '$'  -> "\\letterdollar{}"
           '_'  -> "\\letterunderscore{}"
           '%'  -> "\\letterpercent{}"
           '#'  -> "\\letterhash{}"
           '/'  -> "\\letterslash{}"
           '~'  -> "\\lettertilde{}"
           _    -> Text.singleton c

-- ConTeXt

-- | Format tokens as a ConTeXt @highlighting@ typing environment. The
-- @highlighting@ environemnt is defined by the macros produced by
-- 'styleToConTeXt'; it is a @typing@ environment with default escaping
-- enabled, i.e., @/@ is the escape character.
formatConTeXtBlock :: FormatOptions -> [SourceLine] -> Text
formatConTeXtBlock opts ls = Text.unlines
  [ "\\starthighlighting" <>
    (if numberLines opts
     then "[numbering=line]"
     else Text.empty)
  , formatConTeXt ls
  , "\\stophighlighting"
  ]

-- | Converts a 'Style' to a set of ConTeXt command definitions,
-- which should be placed in the document's preamble.
styleToConTeXt :: Style -> Text
styleToConTeXt f = Text.unlines $
  ( case backgroundColor f of
         Nothing          -> id
         Just (RGB r g b) -> (:)
           (Text.pack $ printf "\\definecolor[shadecolor][x=%x%x%x]" r g b)
  ) $
  [ "\\defineframedtext [shaded]"
  , "  [backgroundcolor=shadecolor,"
  , "   background=color,"
  , "   frame=off,"
  , "   offset=0pt,"
  , "   width=local]"
  , "\\definetyping [highlighting]"
  , "  [escape=yes,"
  , "   before={\\startshaded},"
  , "   after={\\stopshaded}]"
  , "\\definetype [highlight]"
  , "  [escape=yes]"
  ] ++

  sort (map (macrodef (defaultColor f) (Map.toList (tokenStyles f)))
            (enumFromTo KeywordTok NormalTok))

macrodef :: Maybe Color -> [(TokenType, TokenStyle)] -> TokenType -> Text
macrodef defaultcol tokstyles tokt = "\\define[1]\\"
  <> Text.pack (show tokt)
  <> "{"
  <> Text.pack (co . ul . bf . it $ "#1")
  <> "}"
  where tokf = fromMaybe defStyle $ lookup tokt tokstyles
        ul x = if tokenUnderline tokf
                  then "\\underbar{" <> x <> "}"
                  else x
        it x = if tokenItalic tokf
                  then "\\em " <> x
                  else x
        bf x = if tokenBold tokf
                  then "\\bf " <> x
                  else x
        col  = fromColor `fmap` (tokenColor tokf `mplus` defaultcol)
                 :: Maybe (Double, Double, Double)
        co x = case col of
                 Nothing        -> x
                 Just (r, g, b) ->
                   printf "\\colored[r=%0.2f,g=%0.2f,b=%0.2f]{%s}" r g b x

