-----------------------------------------------------------------------------
-- |
-- Module      :  Data.Monoid.Colorful.Flat
-- Copyright   :  Daniel Mendler (c) 2017,
-- License     :  MIT (see the file LICENSE)
--
-- Maintainer  :  mail@daniel-mendler.de
-- Stability   :  experimental
-- Portability :  portable
--
-- This module provides the flat 'Colored' type,
-- which is used internally for rendering the
-- nested 'Data.Monoid.Colorful.Colored' but is
-- also useful on its own. The API resembles
-- the API of 'Data.Monoid.Colorful'.
--
-----------------------------------------------------------

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DeriveGeneric #-}

module Data.Monoid.Colorful.Flat (
  -- * Colored datatypes
  Colored(..)
  , Style(..)
  , Color(..)

  -- * Terminal type
  , Term(..)
  , hGetTerm
  , getTerm

  -- ** Colorful printing to file handle
  , hPrintColored
  , printColored
  , hPrintColoredIO
  , printColoredIO
  , hPrintColoredS
  , printColoredS

  -- ** Show with ANSI escape sequences
  , showColored
  , showColoredA
  , showColoredS

  -- * Reexport from Data.Semigroup
  , (<>)
) where

import System.IO (Handle, stdout, hPutStr)
import Data.Monoid.Colorful.Term
import Data.Monoid.Colorful.Settings
import Data.Monoid.Colorful.Color
import Data.Monoid.Colorful.SGR
import Data.Functor.Identity
import Data.Bifunctor (first, second)
import GHC.Generics (Generic, Generic1)
import Data.Semigroup ((<>))

data Colored a
  = Value a
  | Style   !Style
  | Unstyle !Style
  | Fg      !Color
  | Bg      !Color
  | Push
  | Pop
  | Reset
  deriving (Show, Eq, Ord, Functor, Foldable, Traversable, Generic, Generic1)

hPrintColoredIO :: Handle -> Term -> [Colored (IO ())] -> IO ()
hPrintColoredIO h = showColoredA id (hPutStr h)

printColoredIO :: Term -> [Colored (IO ())] -> IO ()
printColoredIO = hPrintColoredIO stdout

hPrintColored :: (Handle -> a -> IO ()) -> Handle -> Term -> [Colored a] -> IO ()
hPrintColored f h = showColoredA (f h) (hPutStr h)

printColored :: (a -> IO ()) -> Term -> [Colored a] -> IO ()
printColored f = hPrintColored (const f) stdout

hPrintColoredS :: Handle -> Term -> [Colored String] -> IO ()
hPrintColoredS h = showColoredA (hPutStr h) (hPutStr h)

printColoredS :: Term -> [Colored String] -> IO ()
printColoredS = hPrintColoredS stdout

showColoredS :: Term -> [Colored String] -> ShowS
showColoredS = showColored (++) (++)

showColored :: Monoid o => (a -> o) -> (SGRCode -> o) -> Term -> [Colored a] -> o
showColored str code term flat = runIdentity $ showColoredA (pure . str) (pure . code) term flat

showColoredA :: (Applicative f, Monoid o) => (a -> f o) -> (SGRCode -> f o) -> Term -> [Colored a] -> f o
showColoredA str code term = go (defaultSettings, (defaultSettings, []))
  where go s (Style   a:b) = go ((second.first) (setStyle a True) s) b
        go s (Unstyle a:b) = go ((second.first) (setStyle a False) s) b
        go s (Fg      a:b) = go ((second.first) (setFg a) s) b
        go s (Bg      a:b) = go ((second.first) (setBg a) s) b
        go s (Push     :b) = go (second pushStack s) b
        go s (Pop      :b) = go (second popStack s) b
        go s (Reset    :b) = go (second resetStack s) b
        go s (Value   a:b) = let (old, stack@(new, _)) = s in
          mappend <$> (mappend <$> code (sgrCode term old new) <*> str a) <*> go (new, stack) b
        go s [] = let (old, (new, _)) = s in code (sgrCode term old new)