{-# LANGUAGE TypeFamilies     #-}
{-# LANGUAGE FlexibleContexts #-}
module System.Terminal.MonadPrinter where

import           Data.Text                     as T
import           Prelude                   hiding (putChar)

-- | This class describes an environment that Unicode text can be printed to.
--   This might either be file or a terminal.
--
--    * Instances shall implement the concept of lines and line width.
--    * Instances shall implement the concept of a carriage that can be
--      set to the beginning of the next line.
--    * It is assumed that the carriage automatically moves to the beginning
--      of the next line if the end of the current line is reached.
--    * Instances shall be Unicode aware or must at least be able to print
--      a replacement character.
--    * Implementations must be aware of infinite lazy `Prelude.String`s and
--      long `Data.Text.Text`s. `String`s should be printed character wise as
--      evaluating them might trigger exceptions at any point. Long text should
--      be printed chunk wise in order to stay interruptible.
--    * Implementations must not use an unbounded output buffer. Print operations
--      shall block and be interruptible when the output buffer is full.
--    * Instances shall not pass control characters in text to the printer (not even line break).
--      Control characters shall be replaced with �. Text formatting shall be done
--      with the designated classes extending `MonadMarkupPrinter`.
--      Allowing control sequences would cause a dependency on certain terminal
--      types, but also pose an underrated security risk as modern terminals are
--      highly programmable and should not be fed with untrusted input.
class Monad m => MonadPrinter m where
    -- | Move the carriage to the beginning of the next line.
    putLn              :: m ()
    -- | Print a single character.
    putChar            :: Char -> m ()
    -- | Print a `String`.
    putString          :: String -> m ()
    putString           = mapM_ putChar
    -- | Print a `String` and an additional newline.
    putStringLn        :: String -> m ()
    putStringLn s       = putString s >> putLn
    -- | Print a `Text`.
    putText            :: Text -> m ()
    putText             = putString . T.unpack
    -- | Print a `Text` and an additional newline.
    putTextLn          :: Text -> m ()
    putTextLn           = putStringLn . T.unpack
    -- | Flush the output buffer and make the all previous output actually
    --   visible after a reasonably short amount of time.
    --
    --    * The operation may return before the buffer has actually been flushed.
    flush              :: m ()
    flush               = pure ()
    -- | Get the current line width.
    --
    --    * The operation may return the last known line width and may not be
    --      completely precise when I/O is asynchronous.
    --    * This operations shall not block too long and rather be called more
    --      often in order to adapt to changes in line width.
    getLineWidth       :: m Int
    {-# MINIMAL putLn, putChar, putText, getLineWidth #-}

-- | This class introduces abstract constructors for text markup.
class MonadPrinter m => MonadMarkupPrinter m where
    -- | This associated type represents all possible attributes that are
    --   available in the current environment.
    --
    --   When writing polymorphic code against these monadic interfaces
    --   the concrete instantiation of this type is usually unknown and class
    --   instances are generally advised to not expose value constructors for
    --   this type.
    --
    --   Instead, subclasses like `MonadFormattingPrinter` and `MonadColorPrinter`
    --   offer abstract value constructors like `bold`, `underlined`, `inverted`
    --   which are then given meaning by the concrete class instance.
    data Attribute m
    setAttribute :: Attribute m -> m ()
    setAttribute _ = pure ()
    -- | Reset an attribute so that it does no longer affect subsequent output.
    --
    -- * Binary attributes like `bold` or `underlined` shall just be reset to their opposite.
    --
    -- * For non-binary attributes like colors all of their possible values shall be treated
    --   as equal, so that
    --
    --   @
    --   `setAttribute` (`foreground` $ `bright` `blue`) >> `resetAttribute` (`foreground` `red`)
    --   @
    --
    --   results in the foreground color attribute reset afterwards whereas after
    --
    --   @
    --   `setAttribute` (`foreground` $ `bright` `blue`) >> `resetAttribute` (`background` `red`)
    --   @
    --
    --   the foreground color is still set as `bright` `blue`.
    --
    resetAttribute  :: Attribute m -> m ()
    -- | Reset all attributes to their default.
    resetAttributes :: m ()
    -- | Shall determine wheter two attribute values would override each other
    --   or can be applied independently.
    --
    -- * Shall obey the laws of equivalence.
    resetsAttribute :: Attribute m -> Attribute m -> Bool

class MonadMarkupPrinter m => MonadFormattingPrinter m where
    -- | This attribute makes text appear __bold__.
    bold            :: Attribute m
    -- | This attribute makes text appear /italic/.
    italic          :: Attribute m
    -- | This attribute makes text appear underlined.
    underlined      :: Attribute m
    -- | This attribute swaps foreground and background (color).
    --
    --   * This operation is idempotent: Applying the attribute a second time
    --     won't swap it back. Use `resetAttribute` instead.
    inverted        :: Attribute m

-- | This class offers abstract value constructors for
--   foreground and background coloring.
class MonadMarkupPrinter m => MonadColorPrinter m where
    data Color m

    black      :: Color m
    red        :: Color m
    green      :: Color m
    yellow     :: Color m
    blue       :: Color m
    magenta    :: Color m
    cyan       :: Color m
    white      :: Color m

    bright     :: Color m -> Color m

    -- | This attribute sets the __foreground__ color (the text color).
    foreground :: Color m -> Attribute m
    -- | This attribute sets the __background__ color.
    background :: Color m -> Attribute m