{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE OverloadedStrings    #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-| Useful helpers to style and color text with ANSI escape sequences.
-}
module System.Console.Pretty
( Color(..) , Pretty(..) , Section(..) , Style(..)
, supportsPretty)
where

import qualified Data.Char          as C
import           Data.Monoid        ((<>))
import qualified Data.Text          as T
import           GHC.IO.Handle      (Handle)
import           System.Environment (lookupEnv)
import           System.IO          (hIsTerminalDevice, stdout)

---------------------------------------------------------------------------------
-- TYPES

-- | A section to be colored, either foreground or background.
data Section = Foreground | Background

-- | Colors for an ANSI terminal
data Color = Black | Red | Green | Yellow | Blue | Magenta | Cyan | White | Default
  deriving (Enum)

-- | SGR paramaters, aka text styles for an ANSI terminal
data Style
  = Normal | Bold | Faint | Italic
  | Underline | SlowBlink | ColoredNormal | Reverse
  deriving (Enum)


---------------------------------------------------------------------------------
-- CLASS

-- | A class to color and style
class Pretty a where
  -- | Helper to set foreground color
  color :: Color -> a -> a
  color = colorize Foreground
  -- | Helper to set background color
  bgColor :: Color -> a -> a
  bgColor = colorize Background
  -- | Set the color of the (fg/bg) with the color
  colorize :: Section -> Color -> a -> a
  -- | Set the style
  style :: Style -> a -> a

---------------------------------------------------------------------------------
-- TEXT

-- | Instance of `Pretty` for `T.Text`
instance Pretty T.Text where
  colorize section col str =
    "\x1b[" <>                                  -- escape code
    sectionNum <>                               -- bg/foreground
    (T.singleton $ C.intToDigit $ fromEnum col) -- color code
    <> "m" <>                                   -- delim
    str <>                                      -- inner string
    "\x1b[0m"                                   -- reset
    where
      sectionNum :: T.Text
      sectionNum = case section of
        Foreground -> "9"
        Background -> "4"

  style sty str =
    "\x1b[" <>                                  -- escape code
    (T.singleton $ C.intToDigit $ fromEnum sty) -- style
    <> "m" <>                                   -- delim
    str <>                                      -- inner string
    "\x1b[0m"                                   -- reset

---------------------------------------------------------------------------------
-- STRING

-- | Instance of `Pretty` for `String`
instance Pretty String where
  colorize section col str =
    "\x1b[" <>          -- escape code
    sectionNum <>       -- bg/foreground
    show (fromEnum col) -- color code
    <> "m" <>           -- delim
    str <>              -- inner string
    "\x1b[0m"           -- reset
    where
      sectionNum :: String
      sectionNum = case section of
        Foreground -> "9"
        Background -> "4"

  style sty str =
    "\x1b[" <>             -- escape code
    show (fromEnum sty)    -- style
    <> "m" <>              -- delim
    str <>                 -- string
    "\x1b[0m"              -- reset


---------------------------------------------------------------------------------
-- SUPPORTED CHECK

-- | Whether or not the current terminal supports pretty-terminal
supportsPretty :: IO Bool
supportsPretty =
  hSupportsANSI stdout
  where
    -- | Use heuristics to determine whether the functions defined in this
    -- package will work with a given handle.
    --
    -- The current implementation checks that the handle is a terminal, and
    -- that the @TERM@ environment variable doesn't say @dumb@ (whcih is what
    -- Emacs sets for its own terminal).
    hSupportsANSI :: Handle -> IO Bool
    -- Borrowed from an HSpec patch by Simon Hengel
    -- (https://github.com/hspec/hspec/commit/d932f03317e0e2bd08c85b23903fb8616ae642bd)
    hSupportsANSI h = (&&) <$> hIsTerminalDevice h <*> (not <$> isDumb)
      where
        isDumb = (== Just "dumb") <$> lookupEnv "TERM"