module Language.Symantic.Document.Term
 ( module Language.Symantic.Document.Sym
 , module Language.Symantic.Document.Term
 ) where

import Control.Applicative (Applicative(..))
import Data.Bool
import Data.Function (($), (.), id)
import Data.Maybe (Maybe(..))
import Data.Monoid (Monoid(..))
import Data.Ord (Ord(..))
import Data.Semigroup (Semigroup(..))
import Data.String (IsString(..))
import GHC.Exts (IsList(..))
import Prelude (pred, fromIntegral, Num(..))
import System.Console.ANSI
import qualified Data.List as List
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Builder as TLB

import Language.Symantic.Document.Sym

-- * Type 'Reader'
data Reader
 =   Reader
 {   reader_indent    :: !Indent         -- ^ Current indentation level, used by 'newline'.
 ,   reader_newline   :: Term            -- ^ How to display 'newline'.
 ,   reader_sgr       :: ![SGR]          -- ^ Active ANSI codes.
 ,   reader_breakable :: !(Maybe Column) -- ^ 'Column' after which to break, or 'Nothing'
 ,   reader_colorable :: !Bool           -- ^ Whether colors are activated or not.
 ,   reader_decorable :: !Bool           -- ^ Whether decorations are activated or not.
 }

-- | Default 'Reader'.
defReader :: Reader
defReader = Reader
 { reader_indent    = 0
 , reader_newline   = newlineWithIndent
 , reader_sgr       = []
 , reader_breakable = Nothing
 , reader_colorable = True
 , reader_decorable = True
 }

-- * Type 'State'
type State = Column

-- | Default 'State'.
defState :: State
defState = 0

-- * Type 'Term'
newtype Term
 =      Term
 {    unTerm :: Reader ->
                State  ->
                (State -> TLB.Builder -> TLB.Builder) -> -- normal continuation
                (State -> TLB.Builder -> TLB.Builder) -> -- should-break continuation
                TLB.Builder }

-- | Render a 'Term' into a 'TL.Text'.
textTerm :: Term -> TL.Text
textTerm = TLB.toLazyText . runTerm

-- | Render a 'Term' into a 'TLB.Builder'.
runTerm :: Term -> TLB.Builder
runTerm (Term t) = t defReader defState oko oko
	where oko _st = id

instance IsList Term where
	type Item Term = Term
	fromList = mconcat
	toList   = pure
instance Semigroup Term where
	x <> y = Term $ \ro st ok ko ->
		unTerm x ro st
		 (\sx tx -> unTerm y ro sx
			 (\sy ty -> ok sy (tx<>ty))
			 (\sy ty -> ko sy (tx<>ty)))
		 (\sx tx -> unTerm y ro sx
			 (\sy ty -> ko sy (tx<>ty))
			 (\sy ty -> ko sy (tx<>ty)))
instance Monoid Term where
	mempty  = empty
	mappend = (<>)
instance IsString Term where
	fromString = string

writeH :: Column -> TLB.Builder -> Term
writeH len t =
	Term $ \ro st ok ko ->
		let newCol = st + len in
		(case reader_breakable ro of
		 Just breakCol | breakCol < newCol -> ko
		 _ -> ok)
		newCol t

instance Textable Term where
	empty     = Term $ \_ro st ok _ko -> ok st mempty
	charH   t = writeH (Nat 1) (TLB.singleton t)
	stringH t = writeH (length t) (fromString t)
	textH   t = writeH (length t) (TLB.fromText t)
	ltextH  t = writeH (length t) (TLB.fromLazyText t)
	replicate cnt t | cnt <= 0  = empty
	                | otherwise = t <> replicate (pred cnt) t
	newline = Term $ \ro -> unTerm (reader_newline ro) ro
instance Indentable Term where
	align t = Term $ \ro st -> unTerm t ro{reader_indent=st} st
	withNewline nl  t = Term $ \ro -> unTerm t ro{reader_newline=nl}
	withIndent  ind t = Term $ \ro -> unTerm t ro{reader_indent=ind}
	incrIndent  ind t = Term $ \ro -> unTerm t ro{reader_indent=reader_indent ro + ind}
	column f = Term $ \ro st -> unTerm (f st) ro st
	indent f = Term $ \ro -> unTerm (f (reader_indent ro)) ro
	newlineWithoutIndent = Term $ \_ro _st ok _ko ->
		ok 0 $ TLB.singleton '\n'
	newlineWithIndent = Term $ \ro _st ok _ko ->
		ok (reader_indent ro) $
			TLB.singleton '\n' <>
			fromString (List.replicate (fromIntegral $ reader_indent ro) ' ')
instance Breakable Term where
	breakable f       = Term $ \ro -> unTerm (f (reader_breakable ro)) ro
	withBreakable b t = Term $ \ro -> unTerm t ro{reader_breakable=b}
	ifBreak y x = Term $ \ro st ok ko ->
		unTerm x ro st ok $
		case reader_breakable ro of
		 Nothing -> ko
		 Just{} -> (\_sx _tx -> unTerm y ro st ok ko)
	breakpoint onNoBreak onBreak t = Term $ \ro st ok ko ->
		unTerm (onNoBreak <> t) ro st ok $
		case reader_breakable ro of
		 Nothing -> ko
		 Just{}  -> (\_sp _tp -> unTerm (onBreak <> t) ro st ok ko)

writeSGR :: (Reader -> Bool) -> SGR -> Term -> Term
writeSGR isOn s (Term t) =
	Term $ \ro ->
		if isOn ro
		then unTerm (o <> m <> c) ro
		else t ro
	where
	o = Term $ \_ro st ok _ko -> ok st $ fromString $ setSGRCode [s]
	m = Term $ \ro -> t ro{reader_sgr=s:reader_sgr ro}
	c = Term $ \ro st ok _ko -> ok st $ fromString $ setSGRCode $ Reset:List.reverse (reader_sgr ro)

instance Colorable Term where
	colorable f       = Term $ \ro -> unTerm (f (reader_colorable ro)) ro
	withColorable b t = Term $ \ro -> unTerm t ro{reader_colorable=b}
	reverse     = writeSGR reader_colorable $ SetSwapForegroundBackground True
	black       = writeSGR reader_colorable $ SetColor Foreground Dull  Black
	red         = writeSGR reader_colorable $ SetColor Foreground Dull  Red
	green       = writeSGR reader_colorable $ SetColor Foreground Dull  Green
	yellow      = writeSGR reader_colorable $ SetColor Foreground Dull  Yellow
	blue        = writeSGR reader_colorable $ SetColor Foreground Dull  Blue
	magenta     = writeSGR reader_colorable $ SetColor Foreground Dull  Magenta
	cyan        = writeSGR reader_colorable $ SetColor Foreground Dull  Cyan
	white       = writeSGR reader_colorable $ SetColor Foreground Dull  White
	blacker     = writeSGR reader_colorable $ SetColor Foreground Vivid Black
	redder      = writeSGR reader_colorable $ SetColor Foreground Vivid Red
	greener     = writeSGR reader_colorable $ SetColor Foreground Vivid Green
	yellower    = writeSGR reader_colorable $ SetColor Foreground Vivid Yellow
	bluer       = writeSGR reader_colorable $ SetColor Foreground Vivid Blue
	magentaer   = writeSGR reader_colorable $ SetColor Foreground Vivid Magenta
	cyaner      = writeSGR reader_colorable $ SetColor Foreground Vivid Cyan
	whiter      = writeSGR reader_colorable $ SetColor Foreground Vivid White
	onBlack     = writeSGR reader_colorable $ SetColor Background Dull  Black
	onRed       = writeSGR reader_colorable $ SetColor Background Dull  Red
	onGreen     = writeSGR reader_colorable $ SetColor Background Dull  Green
	onYellow    = writeSGR reader_colorable $ SetColor Background Dull  Yellow
	onBlue      = writeSGR reader_colorable $ SetColor Background Dull  Blue
	onMagenta   = writeSGR reader_colorable $ SetColor Background Dull  Magenta
	onCyan      = writeSGR reader_colorable $ SetColor Background Dull  Cyan
	onWhite     = writeSGR reader_colorable $ SetColor Background Dull  White
	onBlacker   = writeSGR reader_colorable $ SetColor Background Vivid Black
	onRedder    = writeSGR reader_colorable $ SetColor Background Vivid Red
	onGreener   = writeSGR reader_colorable $ SetColor Background Vivid Green
	onYellower  = writeSGR reader_colorable $ SetColor Background Vivid Yellow
	onBluer     = writeSGR reader_colorable $ SetColor Background Vivid Blue
	onMagentaer = writeSGR reader_colorable $ SetColor Background Vivid Magenta
	onCyaner    = writeSGR reader_colorable $ SetColor Background Vivid Cyan
	onWhiter    = writeSGR reader_colorable $ SetColor Background Vivid White
instance Decorable Term where
	decorable f       = Term $ \ro -> unTerm (f (reader_decorable ro)) ro
	withDecorable b t = Term $ \ro -> unTerm t ro{reader_decorable=b}
	bold      = writeSGR reader_decorable $ SetConsoleIntensity BoldIntensity
	underline = writeSGR reader_decorable $ SetUnderlining SingleUnderline
	italic    = writeSGR reader_decorable $ SetItalicized True