{-# LANGUAGE OverloadedStrings #-}
-- | Functions used to format text.  Typically you won't need these
-- unless you want tailored control over how your 'Prednote.Core.Pred'
-- are formatted.
module Prednote.Format where

import Rainbow
import Data.Text (Text)
import qualified Data.Text as X
import qualified Prednote.Core as C
import qualified Data.Tree as E
import Data.Monoid

-- # Labels and indentation

-- | A colorful label for 'True' values.
lblTrue :: [Chunk]
lblTrue = ["[", fore green <> "TRUE", "]"]

-- | A colorful label for 'False' values.
lblFalse :: [Chunk]
lblFalse = ["[", fore red <> "FALSE", "]"]

-- | Indent amount.
indentAmt :: Int
indentAmt = 2

-- | Prefixes the given 'Text' with colorful text to indicate 'True'
-- or 'False' as appropriate.
lblLine :: Bool -> Text -> [Chunk]
lblLine b t = lbl ++ [" ", fromText t]
  where
    lbl | b = lblTrue
        | otherwise = lblFalse

-- | Indents the given list of 'Chunk' by the given 'Int' multipled by
-- 'indentAmt'.  Appends a newline.
indent :: [Chunk] -> Int -> [Chunk]
indent cs i = spaces : cs ++ [fromText "\n"]
  where
    spaces = fromText . X.replicate (indentAmt * i)
      . X.singleton $ ' '

-- | A label for a short circuit.
shortCir :: Int -> [Chunk]
shortCir = indent ["[", fore yellow <> "short circuit", "]"]

-- | Indents a 'Text' by the given 'Int' multiplied by
-- 'indentAmt'.
indentTxt :: Text -> Int -> [Chunk]
indentTxt = indent . (:[]) . fromText

-- | Append two 'Text', with an intervening space if both 'Text' are
-- not empty.
(<+>) :: Text -> Text -> Text
l <+> r
  | full l && full r = l <> " " <> r
  | otherwise = l <> r
  where
    full = Prelude.not . X.null

-- | Create a new 'C.Pred' with a different static label.
rename :: Text -> C.Pred a -> C.Pred a
rename x p = p { C.static = (C.static p)
  { E.rootLabel = indentTxt x } }

-- | Creates a new 'C.Pred' with a result differing from the original
-- 'C.Pred'.
changeOutput
  :: (a -> C.Output -> C.Output)
  -- ^ Function to modify the 'C.Output'

  -> C.Pred a
  -- ^ Modify the 'C.Output' of this 'C.Pred'

  -> C.Pred a
changeOutput f p = p { C.evaluate = e' }
  where
    e' a = t'
      where
        t = C.evaluate p a
        t' = t { E.rootLabel = f a (E.rootLabel t) }

-- | Creates a new 'C.Pred' with a different dynamic label.
speak
  :: (a -> Text)
  -- ^ New dynamic label.  Do not indicate whether the result is
  -- 'True' or 'False'; this is done for you.

  -> C.Pred a

  -> C.Pred a
speak f = changeOutput g
  where
    g a o = o { C.dynamic = dyn }
      where
        dyn = indent $ lblLine (C.result o) (f a)


-- | Creates a new 'C.Pred' with any short circuits having a colorful
-- label.
speakShort :: C.Pred a -> C.Pred a
speakShort p = p { C.evaluate = e' }
  where
    e' a = t { E.rootLabel = (E.rootLabel t)
      { C.short = fmap (const shortCir) shrt } }
      where
        t = C.evaluate p a
        shrt = C.short . E.rootLabel $ t