-- | Pretty-printing implementation for 'Value'
module Config.Pretty where

import           Data.Char (isPrint, isDigit,intToDigit)
import           Data.List (mapAccumL)
import qualified Data.Text as Text
import           Text.PrettyPrint
import           Numeric(showIntAtBase)

import Config.Value

-- | Pretty-print a 'Value' as shown in the example.
-- Sections will nest complex values underneath with
-- indentation and simple values will be rendered on
-- the same line as their section.
pretty :: Value a -> Doc
pretty value =
  case value of
    Sections _ [] -> text "{}"
    Sections _ xs -> prettySections xs
    Number _ b n  -> prettyNum b n
    Floating _ c e-> prettyFloating c e
    Text _ t      -> prettyText (Text.unpack t)
    Atom _ t      -> text (Text.unpack (atomName t))
    List _ []     -> text "[]"
    List _ xs     -> vcat [ char '*' <+> pretty x | x <- xs ]


prettyNum :: Int -> Integer -> Doc
prettyNum b n
  | b == 16   = pref <> text "0x" <> num
  | b ==  8   = pref <> text "0o" <> num
  | b ==  2   = pref <> text "0b" <> num
  | otherwise = integer n
  where
  pref = if n < 0 then char '-' else empty
  num  = text (showIntAtBase (fromIntegral b) intToDigit (abs n) "")

prettyFloating :: Integer -> Integer -> Doc
prettyFloating c e = text (show c ++ "e" ++ show e)

prettyText :: String -> Doc
prettyText = doubleQuotes . cat . snd . mapAccumL ppChar True

  where ppChar s x
          | isDigit x = (True, if not s then text "\\&" <> char x else char x)
          | isPrint x = (True, char x)
          | otherwise = (False, char '\\' <> int (fromEnum x))


prettySections :: [Section a] -> Doc
prettySections ss = prettySmallSections small $$ rest
  where
  (small,big) = break (isBig . sectionValue) ss
  rest        = case big of
                  []     -> empty
                  b : bs -> prettyBigSection b $$ prettySections bs

prettyBigSection :: Section a -> Doc
prettyBigSection s =
  text (Text.unpack (sectionName s)) <> colon
  $$ nest 2 (pretty (sectionValue s))

prettySmallSections :: [Section a] -> Doc
prettySmallSections ss = vcat (map pp annotated)
  where
  annotate s = (Text.length (sectionName s), s)
  annotated  = map annotate ss
  indent     = 1 + maximum (0 : map fst annotated)
  pp (l,s)   = prettySmallSection (indent - l) s

prettySmallSection :: Int -> Section a -> Doc
prettySmallSection n s =
  text (Text.unpack (sectionName s)) <> colon <>
    text (replicate n ' ') <> pretty (sectionValue s)

isBig :: Value a -> Bool
isBig (Sections _ (_:_)) = True
isBig (List _ (_:_))     = True
isBig _                  = False