{-# LANGUAGE UndecidableInstances #-}
module Symantic.Document.AnsiText where

import Control.Applicative (Applicative(..), liftA2)
import Control.Monad (Monad(..), sequence)
import Control.Monad.Trans.Reader
import Data.Bool
import Data.Char (Char)
import Data.Function (($), (.), id)
import Data.Functor ((<$>))
import Data.Monoid (Monoid(..))
import Data.Semigroup (Semigroup(..))
import Data.String (String, IsString(..))
import Data.Text (Text)
import System.Console.ANSI
import Text.Show (Show(..))
import qualified Data.List as List
import qualified Data.Text.Lazy as TL

import Symantic.Document.API

-- * Type 'AnsiText'
newtype AnsiText d = AnsiText { unAnsiText :: Reader [SGR] d }
instance Show d => Show (AnsiText d) where
        show (AnsiText d) = show $ runReader d []

ansiText :: AnsiText d -> AnsiText d
ansiText = id

runAnsiText :: AnsiText d -> d
runAnsiText (AnsiText d) = (`runReader` []) d

instance From Char d => From Char (AnsiText d) where
        from = AnsiText . return . from
instance From String d => From String (AnsiText d) where
        from = AnsiText . return . from
instance From Text d => From Text (AnsiText d) where
        from = AnsiText . return . from
instance From TL.Text d => From TL.Text (AnsiText d) where
        from = AnsiText . return . from
instance From s (AnsiText d) => From (Line s) (AnsiText d) where
        from = from . unLine
instance From s (AnsiText d) => From (Word s) (AnsiText d) where
        from = from . unWord
instance From String d => IsString (AnsiText d) where
        fromString = from
instance Semigroup d => Semigroup (AnsiText d) where
        AnsiText x <> AnsiText y = AnsiText $ liftA2 (<>) x y
instance Monoid d => Monoid (AnsiText d) where
        mempty = AnsiText (return mempty)
        mappend = (<>)
instance Lengthable d => Lengthable (AnsiText d) where
        -- NOTE: AnsiText's Reader can be run with an empty value
        -- because all 'SGR' are ignored anyway.
        width (AnsiText ds) = width $ runReader ds mempty
        nullWidth (AnsiText ds) = nullWidth $ runReader ds mempty
instance Spaceable d => Spaceable (AnsiText d) where
        newline = AnsiText $ return newline
        space   = AnsiText $ return space
        spaces  = AnsiText . return . spaces
instance (Semigroup d, From [SGR] d) => Colorable16 (AnsiText d) where
        reverse     = ansiTextSGR $ SetSwapForegroundBackground True
        black       = ansiTextSGR $ SetColor Foreground Dull  Black
        red         = ansiTextSGR $ SetColor Foreground Dull  Red
        green       = ansiTextSGR $ SetColor Foreground Dull  Green
        yellow      = ansiTextSGR $ SetColor Foreground Dull  Yellow
        blue        = ansiTextSGR $ SetColor Foreground Dull  Blue
        magenta     = ansiTextSGR $ SetColor Foreground Dull  Magenta
        cyan        = ansiTextSGR $ SetColor Foreground Dull  Cyan
        white       = ansiTextSGR $ SetColor Foreground Dull  White
        blacker     = ansiTextSGR $ SetColor Foreground Vivid Black
        redder      = ansiTextSGR $ SetColor Foreground Vivid Red
        greener     = ansiTextSGR $ SetColor Foreground Vivid Green
        yellower    = ansiTextSGR $ SetColor Foreground Vivid Yellow
        bluer       = ansiTextSGR $ SetColor Foreground Vivid Blue
        magentaer   = ansiTextSGR $ SetColor Foreground Vivid Magenta
        cyaner      = ansiTextSGR $ SetColor Foreground Vivid Cyan
        whiter      = ansiTextSGR $ SetColor Foreground Vivid White
        onBlack     = ansiTextSGR $ SetColor Background Dull  Black
        onRed       = ansiTextSGR $ SetColor Background Dull  Red
        onGreen     = ansiTextSGR $ SetColor Background Dull  Green
        onYellow    = ansiTextSGR $ SetColor Background Dull  Yellow
        onBlue      = ansiTextSGR $ SetColor Background Dull  Blue
        onMagenta   = ansiTextSGR $ SetColor Background Dull  Magenta
        onCyan      = ansiTextSGR $ SetColor Background Dull  Cyan
        onWhite     = ansiTextSGR $ SetColor Background Dull  White
        onBlacker   = ansiTextSGR $ SetColor Background Vivid Black
        onRedder    = ansiTextSGR $ SetColor Background Vivid Red
        onGreener   = ansiTextSGR $ SetColor Background Vivid Green
        onYellower  = ansiTextSGR $ SetColor Background Vivid Yellow
        onBluer     = ansiTextSGR $ SetColor Background Vivid Blue
        onMagentaer = ansiTextSGR $ SetColor Background Vivid Magenta
        onCyaner    = ansiTextSGR $ SetColor Background Vivid Cyan
        onWhiter    = ansiTextSGR $ SetColor Background Vivid White
instance (Semigroup d, From [SGR] d) => Decorable (AnsiText d) where
        bold      = ansiTextSGR $ SetConsoleIntensity BoldIntensity
        underline = ansiTextSGR $ SetUnderlining SingleUnderline
        italic    = ansiTextSGR $ SetItalicized True
instance Justifiable d => Justifiable (AnsiText d) where
        justify (AnsiText d) = AnsiText $ justify <$> d
instance Indentable d => Indentable (AnsiText d) where
        setIndent i (AnsiText d)  = AnsiText $ setIndent i <$> d
        incrIndent i (AnsiText d) = AnsiText $ incrIndent i <$> d
        fill w (AnsiText d)       = AnsiText $ fill w <$> d
        breakfill w (AnsiText d)  = AnsiText $ breakfill w <$> d
        align (AnsiText d)        = AnsiText $ align <$> d
instance Listable d => Listable (AnsiText d) where
        ul ds = AnsiText $ (ul <$>) $ sequence $ unAnsiText <$> ds
        ol ds = AnsiText $ (ol <$>) $ sequence $ unAnsiText <$> ds
instance Wrappable d => Wrappable (AnsiText d) where
        setWidth w (AnsiText d) = AnsiText $ setWidth w <$> d
        breakpoint = AnsiText $ return breakpoint
        breakspace = AnsiText $ return breakspace
        breakalt (AnsiText x) (AnsiText y) = AnsiText $ liftA2 breakalt x y

ansiTextSGR ::
 Semigroup d => From [SGR] d =>
 SGR -> AnsiText d -> AnsiText d
ansiTextSGR newSGR (AnsiText d) = AnsiText $ do
        oldSGR <- ask
        (\m -> from [newSGR] <> m <> from (Reset:List.reverse oldSGR))
         <$> local (newSGR :) d

-- * Type 'PlainText'
-- | Drop 'Colorable16' and 'Decorable'.
newtype PlainText d = PlainText { unPlainText :: d }
 deriving (Show)

plainText :: PlainText d -> PlainText d
plainText = id

runPlainText :: PlainText d -> d
runPlainText (PlainText d) = d

instance From Char d => From Char (PlainText d) where
        from = PlainText . from
instance From String d => From String (PlainText d) where
        from = PlainText . from
instance From Text d => From Text (PlainText d) where
        from = PlainText . from
instance From TL.Text d => From TL.Text (PlainText d) where
        from = PlainText . from
instance From s (PlainText d) => From (Line s) (PlainText d) where
        from = from . unLine
instance From s (PlainText d) => From (Word s) (PlainText d) where
        from = from . unWord
instance From String d => IsString (PlainText d) where
        fromString = from
instance Semigroup d => Semigroup (PlainText d) where
        PlainText x <> PlainText y = PlainText $ (<>) x y
instance Monoid d => Monoid (PlainText d) where
        mempty = PlainText mempty
        mappend = (<>)
instance Lengthable d => Lengthable (PlainText d) where
        -- NOTE: PlainText's Reader can be run with an empty value
        -- because all 'SGR' are ignored anyway.
        width (PlainText ds) = width ds
        nullWidth (PlainText ds) = nullWidth ds
instance Spaceable d => Spaceable (PlainText d) where
        newline = PlainText $ newline
        space   = PlainText $ space
        spaces  = PlainText . spaces
instance Semigroup d => Colorable16 (PlainText d) where
        reverse     = plainTextSGR
        black       = plainTextSGR
        red         = plainTextSGR
        green       = plainTextSGR
        yellow      = plainTextSGR
        blue        = plainTextSGR
        magenta     = plainTextSGR
        cyan        = plainTextSGR
        white       = plainTextSGR
        blacker     = plainTextSGR
        redder      = plainTextSGR
        greener     = plainTextSGR
        yellower    = plainTextSGR
        bluer       = plainTextSGR
        magentaer   = plainTextSGR
        cyaner      = plainTextSGR
        whiter      = plainTextSGR
        onBlack     = plainTextSGR
        onRed       = plainTextSGR
        onGreen     = plainTextSGR
        onYellow    = plainTextSGR
        onBlue      = plainTextSGR
        onMagenta   = plainTextSGR
        onCyan      = plainTextSGR
        onWhite     = plainTextSGR
        onBlacker   = plainTextSGR
        onRedder    = plainTextSGR
        onGreener   = plainTextSGR
        onYellower  = plainTextSGR
        onBluer     = plainTextSGR
        onMagentaer = plainTextSGR
        onCyaner    = plainTextSGR
        onWhiter    = plainTextSGR
instance Semigroup d => Decorable (PlainText d) where
        bold      = plainTextSGR
        underline = plainTextSGR
        italic    = plainTextSGR
instance Justifiable d => Justifiable (PlainText d) where
        justify (PlainText d) = PlainText $ justify d
instance Indentable d => Indentable (PlainText d) where
        setIndent i (PlainText d)  = PlainText $ setIndent i d
        incrIndent i (PlainText d) = PlainText $ incrIndent i d
        fill w (PlainText d)       = PlainText $ fill w d
        breakfill w (PlainText d)  = PlainText $ breakfill w d
        align (PlainText d)        = PlainText $ align d
instance Listable d => Listable (PlainText d) where
        ul ds = PlainText $ ul $ unPlainText <$> ds
        ol ds = PlainText $ ol $ unPlainText <$> ds
instance Wrappable d => Wrappable (PlainText d) where
        setWidth w (PlainText d) = PlainText $ setWidth w d
        breakpoint = PlainText breakpoint
        breakspace = PlainText breakspace
        breakalt (PlainText x) (PlainText y) = PlainText $ breakalt x y

plainTextSGR ::
 Semigroup d =>
 PlainText d -> PlainText d
plainTextSGR (PlainText d) = PlainText d