{-# LANGUAGE DeriveGeneric, GeneralizedNewtypeDeriving #-}
-- | Colours and text attributes.
module Game.LambdaHack.Definition.Color
  ( -- * Colours
    Color(..)
  , defFG, isBright, darkCol, brightCol, stdCol, legalFgCol
  , cVeryBadEvent, cBadEvent, cRisk, cGraveRisk, cVeryGoodEvent, cGoodEvent
  , cVista, cSleep, cWakeUp, cGreed, cNeutralEvent, cRareNeutralEvent
  , cIdentification, cMeta, cBoring, cGameOver, cTutorialHint
  , colorToRGB
    -- * Complete text attributes
  , Highlight (..), Attr(..)
  , highlightToColor, defAttr
    -- * Characters with attributes
  , AttrChar(..), AttrCharW32(..)
  , attrCharToW32, attrCharFromW32
  , fgFromW32, bgFromW32, charFromW32, attrFromW32
  , spaceAttrW32, nbspAttrW32, trimmedLineAttrW32
  , attrChar2ToW32, attrChar1ToW32
  ) where

import Prelude ()

import Game.LambdaHack.Core.Prelude

import           Control.DeepSeq
import           Data.Binary
import           Data.Bits ((.&.))
import qualified Data.Char as Char
import           GHC.Generics (Generic)

-- | Colours supported by the major frontends.
data Color =
    Black
  | Red
  | Green
  | Brown
  | Blue
  | Magenta
  | Cyan
  | White
  | AltWhite  -- only use for frontend hacks
  | BrBlack
  | BrRed
  | BrGreen
  | BrYellow
  | BrBlue
  | BrMagenta
  | BrCyan
  | BrWhite
  deriving (Show, Read, Eq, Ord, Enum, Generic)

instance Binary Color where
  put = putWord8 . toEnum . fromEnum
  get = fmap (toEnum . fromEnum) getWord8

instance NFData Color

-- | The default colours, to optimize attribute setting.
defFG :: Color
defFG = White

-- | A helper for the terminal frontends that display bright via bold.
isBright :: Color -> Bool
isBright c = c > BrBlack

-- | Colour sets. Sorted.
darkCol, brightCol, stdCol, legalFgCol :: [Color]
darkCol = [Red .. Cyan]
brightCol = [BrRed .. BrCyan]  -- BrBlack is not really that bright
stdCol = darkCol ++ brightCol
legalFgCol = darkCol ++ [White, BrBlack] ++ brightCol ++ [BrWhite]

-- See the discussion of colours and the table of colours at
-- https://github.com/LambdaHack/LambdaHack/wiki/Display#colours
-- Another mention of colours, concerning terrain, is in PLAYING.md manual.
-- The manual and this code should follow the wiki.
cVeryBadEvent, cBadEvent, cRisk, cGraveRisk, cVeryGoodEvent, cGoodEvent, cVista, cSleep, cWakeUp, cGreed, cNeutralEvent, cRareNeutralEvent, cIdentification, cMeta, cBoring, cGameOver, cTutorialHint :: Color
cVeryBadEvent = Red
cBadEvent = BrRed
cRisk = Magenta
cGraveRisk = BrMagenta
cVeryGoodEvent = Green
cGoodEvent = BrGreen
cVista = BrGreen
cSleep = Blue
cWakeUp = BrBlue
cGreed = BrBlue
cNeutralEvent = Cyan
cRareNeutralEvent = BrCyan
cIdentification = Brown
cMeta = BrYellow
cBoring = White
cGameOver = BrWhite
cTutorialHint = BrMagenta

-- | Translationg to heavily modified Linux console color RGB values.
--
-- Warning: SDL frontend sadly duplicates this code.
colorToRGB :: Color -> Text
colorToRGB Black     = "#000000"
colorToRGB Red       = "#D50505"
colorToRGB Green     = "#059D05"
colorToRGB Brown     = "#CA4A05"
colorToRGB Blue      = "#0556F4"
colorToRGB Magenta   = "#AF0EAF"
colorToRGB Cyan      = "#059696"
colorToRGB White     = "#B8BFCB"
colorToRGB AltWhite  = "#C4BEB1"
colorToRGB BrBlack   = "#6F5F5F"
colorToRGB BrRed     = "#FF5555"
colorToRGB BrGreen   = "#65F136"
colorToRGB BrYellow  = "#EBD642"
colorToRGB BrBlue    = "#4D98F4"
colorToRGB BrMagenta = "#FF77FF"
colorToRGB BrCyan    = "#52F4E5"
colorToRGB BrWhite   = "#FFFFFF"

-- -- | For reference, the original Linux console colors.
-- -- Good old retro feel and more useful than xterm (e.g. brown).
-- colorToRGB :: Color -> Text
-- colorToRGB Black     = "#000000"
-- colorToRGB Red       = "#AA0000"
-- colorToRGB Green     = "#00AA00"
-- colorToRGB Brown     = "#AA5500"
-- colorToRGB Blue      = "#0000AA"
-- colorToRGB Magenta   = "#AA00AA"
-- colorToRGB Cyan      = "#00AAAA"
-- colorToRGB White     = "#AAAAAA"
-- colorToRGB AltWhite  = "#AAAAAA"
-- colorToRGB BrBlack   = "#555555"
-- colorToRGB BrRed     = "#FF5555"
-- colorToRGB BrGreen   = "#55FF55"
-- colorToRGB BrYellow  = "#FFFF55"
-- colorToRGB BrBlue    = "#5555FF"
-- colorToRGB BrMagenta = "#FF55FF"
-- colorToRGB BrCyan    = "#55FFFF"
-- colorToRGB BrWhite   = "#FFFFFF"

-- | Additional map cell highlight, e.g., a colorful square around the cell
-- or a colorful background.
--
-- Warning: the highlight underscored by the terminal cursor is
-- the maximal element of this type present on a screen,
-- so don't add new highlights to the end.
data Highlight =
    HighlightNone
  | HighlightBackground
  | HighlightGreen
  | HighlightBlue
  | HighlightBrown
  | HighlightCyan
  | HighlightGrey
  | HighlightWhite
  | HighlightMagenta
  | HighlightRed
  | HighlightYellow
  | HighlightYellowAim
  | HighlightRedAim
  | HighlightNoneCursor
  deriving (Show, Eq, Ord, Enum, Bounded)

highlightToColor :: Highlight -> Color
highlightToColor hi = case hi of
  HighlightNone -> Black  -- should be transparent, but is OK in web frontend
  HighlightBackground -> BrBlack  -- gets a special colour, but as a background
  HighlightGreen -> Green
  HighlightBlue -> Blue
  HighlightBrown -> Brown
  HighlightCyan -> Cyan
  HighlightGrey -> BrBlack
  HighlightWhite -> White  -- bright, but no saturation, so doesn't obscure much
  HighlightMagenta -> BrMagenta  -- very rare, so bright is fine
  HighlightRed -> Red
  HighlightYellow -> BrYellow  -- obscures, but mostly used around bright white
  HighlightYellowAim -> BrYellow
  HighlightRedAim -> Red
  HighlightNoneCursor -> Black  -- used in ANSI for cursor via @maxIndexByA@

-- | Text attributes: foreground color and highlight.
data Attr = Attr
  { fg :: Color      -- ^ foreground colour
  , bg :: Highlight  -- ^ highlight
  }
  deriving (Show, Eq)

-- | The default attribute, to optimize attribute setting.
defAttr :: Attr
defAttr = Attr defFG HighlightNone

-- | Character to display, with its attribute.
data AttrChar = AttrChar
  { acAttr :: Attr
  , acChar :: Char
  }
  deriving (Show, Eq)

-- This implementation is faster than @Int@, because some vector updates
-- can be done without going to and from @Int@.
-- | Optimized representation of 'AttrChar'.
newtype AttrCharW32 = AttrCharW32 {attrCharW32 :: Word32}
  deriving (Show, Eq, Ord, Enum, Binary)

attrCharToW32 :: AttrChar -> AttrCharW32
attrCharToW32 AttrChar{acAttr=Attr{..}, acChar} = AttrCharW32 $ toEnum $
  unsafeShiftL (fromEnum fg) 8 + fromEnum bg + unsafeShiftL (Char.ord acChar) 16

attrCharFromW32 :: AttrCharW32 -> AttrChar
attrCharFromW32 !w = AttrChar (attrFromW32 w) (charFromW32 w)

fgFromW32 :: AttrCharW32 -> Color
{-# INLINE fgFromW32 #-}
fgFromW32 w =
  toEnum $ unsafeShiftR (fromEnum $ attrCharW32 w) 8 .&. (2 ^ (8 :: Int) - 1)

bgFromW32 :: AttrCharW32 -> Highlight
{-# INLINE bgFromW32 #-}
bgFromW32 w =
  toEnum $ fromEnum $ attrCharW32 w .&. (2 ^ (8 :: Int) - 1)

charFromW32 :: AttrCharW32 -> Char
{-# INLINE charFromW32 #-}
charFromW32 w =
  Char.chr $ unsafeShiftR (fromEnum $ attrCharW32 w) 16

attrFromW32 :: AttrCharW32 -> Attr
{-# INLINE attrFromW32 #-}
attrFromW32 w = Attr (fgFromW32 w) (bgFromW32 w)

spaceAttrW32 :: AttrCharW32
spaceAttrW32 = attrCharToW32 $ AttrChar defAttr ' '

nbspAttrW32 :: AttrCharW32
nbspAttrW32 = attrCharToW32 $ AttrChar defAttr '\x00a0'

trimmedLineAttrW32 :: AttrCharW32
trimmedLineAttrW32 = attrChar2ToW32 BrBlack '$'

attrChar2ToW32 :: Color -> Char -> AttrCharW32
{-# INLINE attrChar2ToW32 #-}
attrChar2ToW32 fg =
  let fgNum = unsafeShiftL (fromEnum fg) 8
  in \acChar -> AttrCharW32 $ toEnum $ fgNum + unsafeShiftL (Char.ord acChar) 16
--
-- These hacks save one allocation (?) (before fits-in-32bits check) compared
-- to the above, but they fail in GHC 9.2.0 and possibly don't do anything
-- for JS, which is the only real bottleneck, so disabled:
--
--import GHC.Prim (int2Word#)
--  case unsafeShiftL (fromEnum fg) 8 + unsafeShiftL (Char.ord acChar) 16 of
--    I# i -> AttrCharW32 $ W32# (int2Word# i)

attrChar1ToW32 :: Char -> AttrCharW32
{-# INLINE attrChar1ToW32 #-}
attrChar1ToW32 =
  let fgNum = unsafeShiftL (fromEnum White) 8
  in \acChar -> AttrCharW32 $ toEnum $ fgNum + unsafeShiftL (Char.ord acChar) 16
--
--    case fgNum + unsafeShiftL (Char.ord acChar) 16 of
--      I# i -> AttrCharW32 $ W32# (int2Word# i)
