{-# LANGUAGE OverloadedStrings #-}
module Skylighting.Format.ANSI (
         formatANSI
       ) where
import Control.Monad (mplus)
import qualified Data.Map as Map
import Data.Maybe (fromMaybe, isNothing, maybeToList)
import Data.Monoid
import Data.Text (Text)
import qualified Data.Text as Text
import Skylighting.Types
import qualified System.Console.ANSI.Codes as ANSI
formatANSI :: FormatOptions -> Style -> [SourceLine] -> Text
formatANSI opts sty = (beforeText <>)
                        . (<> afterText)
                        . Text.intercalate (Text.singleton '\n')
                        . zipWith (sourceLineToANSI opts sty) [startNum..]
    where beforeText = ansiResetText <> ansiStyleText clv (defaultColor sty) (backgroundColor sty) False False False
          afterText = ansiResetText
          startNum = LineNo $ startNumber opts
          clv = ansiColorLevel opts
sourceLineToANSI :: FormatOptions -> Style -> LineNo -> SourceLine -> Text
sourceLineToANSI opts sty lno = prependLineNoText
                                 . mconcat
                                 . map (tokenToANSI clv sty)
    where prependLineNoText = if numberLines opts
                                 then (lineNoText <>)
                                 else id
          lineNoText = ansiStyleText clv lineNoFgc lineNoBgc False False False
                         <> Text.pack (show $ lineNo lno)
                         <> ansiStyleText clv (defaultColor sty) (backgroundColor sty) False False False
                         <> "\t"
          lineNoFgc = lineNumberColor sty `mplus` defaultColor sty
          lineNoBgc = lineNumberBackgroundColor sty `mplus` backgroundColor sty
          clv = ansiColorLevel opts
tokenToANSI :: ANSIColorLevel -> Style -> Token -> Text
tokenToANSI clv sty (tokTy, tokText) = ansiStyleText clv tokFgc tokBgc tokB tokI tokU
                                         <> tokText
                                         <> ansiStyleText clv (defaultColor sty) (backgroundColor sty) False False False
    where TokenStyle tokFgcRaw tokBgcRaw tokB tokI tokU = fromMaybe defStyle . Map.lookup tokTy $ tokenStyles sty
          tokFgc = tokFgcRaw `mplus` defaultColor sty
          tokBgc = tokBgcRaw `mplus` backgroundColor sty
ansiStyleText :: ANSIColorLevel 
            -> Maybe Color 
            -> Maybe Color 
            -> Bool 
            -> Bool 
            -> Bool 
            -> Text
ansiStyleText clv fgc bgc b i u = optReset <> sgrTextFg <> sgrTextBg
                                    <> (Text.pack . ANSI.setSGRCode $ concat [sgrCodeFg,
                                                                              sgrCodeBg,
                                                                              sgrCodeBold,
                                                                              sgrCodeItal,
                                                                              sgrCodeUndl])
    
    where (sgrCodeFg, sgrTextFg) = case clv of
            ANSITrueColor -> (maybeToList $ fmap (ANSI.SetRGBColor ANSI.Foreground . fromColor) fgc, "")
            ANSI256Color -> ([], fromMaybe "" $ fmap (\c -> Text.pack $ ANSI.csi [38, 5,
              fromIntegral . getXterm256ColorCode $ fromColor c] "m") fgc)
            ANSI16Color -> (maybeToList $ fmap (uncurry (ANSI.SetColor ANSI.Foreground) . fromColor) fgc, "")
          (sgrCodeBg, sgrTextBg) = case clv of
            ANSITrueColor -> (maybeToList $ fmap (ANSI.SetRGBColor ANSI.Background . fromColor) bgc, "")
            ANSI256Color -> ([], fromMaybe "" $ fmap (\c -> Text.pack $ ANSI.csi [48, 5,
              fromIntegral . getXterm256ColorCode $ fromColor c] "m") bgc)
            ANSI16Color -> (maybeToList $ fmap (uncurry (ANSI.SetColor ANSI.Background) . fromColor) bgc, "")
          optReset = if isNothing fgc && isNothing bgc then ansiResetText else ""
          sgrCodeBold = [ANSI.SetConsoleIntensity $ if b then ANSI.BoldIntensity else ANSI.NormalIntensity]
          sgrCodeItal = [ANSI.SetItalicized i] 
          sgrCodeUndl = [ANSI.SetUnderlining $ if u then ANSI.SingleUnderline else ANSI.NoUnderline]
ansiResetText :: Text
ansiResetText = Text.pack $ ANSI.setSGRCode [ANSI.Reset]