{-# OPTIONS_GHC -O0 #-}
-- 
-- Copyright (c) 2004-2010 Don Stewart - http://www.cse.unsw.edu.au/~dons
-- 
-- This program is free software; you can redistribute it and/or
-- modify it under the terms of the GNU General Public License as
-- published by the Free Software Foundation; either version 2 of
-- the License, or (at your option) any later version.
-- 
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-- General Public License for more details.
-- 
-- You should have received a copy of the GNU General Public License
-- along with this program; if not, write to the Free Software
-- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-- 02111-1307, USA.
-- 

--
-- | Color manipulation
--

module Style where

#include "config.h"

import qualified Curses
import Data.ByteString (ByteString)

import Data.Char                (toLower)
import Data.Word                (Word8)
import Data.Maybe               (fromJust)
import Data.IORef               (readIORef, writeIORef, newIORef, IORef)
import qualified Data.Map as M  (fromList, empty, lookup, Map)

import System.IO.Unsafe         (unsafePerformIO)
import Control.Exception        (handle)

------------------------------------------------------------------------

-- | User-configurable colours
-- Each component of this structure corresponds to a fg\/bg colour pair
-- for an item in the ui
data UIStyle = UIStyle {
     window      :: !Style  -- default window colour
   , helpscreen  :: !Style  -- help screen
   , titlebar    :: !Style  -- titlebar of window
   , selected    :: !Style  -- currently playing track
   , cursors     :: !Style  -- the scrolling cursor line
   , combined    :: !Style  -- the style to use when the cursor is on the current track
   , warnings    :: !Style  -- style for warnings
   , blockcursor :: !Style  -- style for the block cursor when typing text
   , progress    :: !Style  -- style for the progress bar
   }

------------------------------------------------------------------------

-- | Colors 
data Color
    = RGB {-# UNPACK #-} !Word8 !Word8 !Word8
    | Default
    | Reverse
    deriving (Eq,Ord)

-- | Foreground and background color pairs
data Style = Style {-# UNPACK #-} !Color !Color 
    deriving (Eq,Ord)

-- | A list of such values (the representation is optimised)
data StringA 
    = Fast   {-# UNPACK #-} !ByteString !Style
    | FancyS {-# UNPACK #-} ![(ByteString,Style)]  -- one line made up of segments

------------------------------------------------------------------------
--
-- | Some simple colours (derivied from proxima\/src\/common\/CommonTypes.hs)
--
-- But we don't have a light blue?
--
black, grey, darkred, red, darkgreen, green, brown, yellow          :: Color
darkblue, blue, purple, magenta, darkcyan, cyan, white, brightwhite :: Color
black       = RGB 0 0 0
grey        = RGB 128 128 128
darkred     = RGB 139 0 0
red         = RGB 255 0 0
darkgreen   = RGB 0 100 0
green       = RGB 0 128 0
brown       = RGB 165 42 42
yellow      = RGB 255 255 0
darkblue    = RGB 0 0 139
blue        = RGB 0 0 255
purple      = RGB 128 0 128
magenta     = RGB 255 0 255
darkcyan    = RGB 0 139 139 
cyan        = RGB 0 255 255
white       = RGB 165 165 165
brightwhite = RGB 255 255 255

defaultfg, defaultbg, reversefg, reversebg :: Color
#if defined(HAVE_USE_DEFAULT_COLORS)
defaultfg   = Default
defaultbg   = Default
#else
defaultfg   = white
defaultbg   = black
#endif
reversefg   = Reverse
reversebg   = Reverse

--
-- | map strings to colors
--
stringToColor :: String -> Maybe Color
stringToColor s = case map toLower s of
    "black"         -> Just black
    "grey"          -> Just grey
    "darkred"       -> Just darkred
    "red"           -> Just red
    "darkgreen"     -> Just darkgreen
    "green"         -> Just green
    "brown"         -> Just brown
    "yellow"        -> Just yellow
    "darkblue"      -> Just darkblue
    "blue"          -> Just blue
    "purple"        -> Just purple
    "magenta"       -> Just magenta
    "darkcyan"      -> Just darkcyan
    "cyan"          -> Just cyan
    "white"         -> Just white
    "brightwhite"   -> Just brightwhite
    "default"       -> Just Default
    "reverse"       -> Just Reverse
    _               -> Nothing

------------------------------------------------------------------------
--
-- | Set some colours, perform an action, and then reset the colours
--
withStyle :: Style -> (IO ()) -> IO ()
withStyle sty fn = uiAttr sty >>= setAttribute >> fn >> reset
{-# INLINE withStyle #-}

--
-- | manipulate the current attributes of the standard screen
-- Only set attr if it's different to the current one?
--
setAttribute :: (Curses.Attr, Curses.Pair) -> IO ()
setAttribute = uncurry Curses.attrSet
{-# INLINE setAttribute #-}

--
-- | Reset the screen to normal values
--
reset :: IO ()
reset = setAttribute (Curses.attr0, Curses.Pair 0)
{-# INLINE reset #-}

--
-- | And turn on the colours
--
initcolours :: UIStyle -> IO ()
initcolours sty = do
    let ls  = [helpscreen sty, warnings sty, window sty, 
               selected sty, titlebar sty, progress sty,
               blockcursor sty, cursors sty, combined sty ]
        (Style fg bg) = progress sty    -- bonus style

    pairs <- initUiColors (ls ++ [Style bg bg, Style fg fg])
    writeIORef pairMap pairs
    -- set the background
    uiAttr (window sty) >>= \(_,p) -> Curses.bkgrndSet nullA p

------------------------------------------------------------------------
--
-- | Set up the ui attributes, given a ui style record
--
-- Returns an association list of pairs for foreground and bg colors,
-- associated with the terminal color pair that has been defined for
-- those colors.
--
initUiColors :: [Style] -> IO PairMap
initUiColors stys = do 
    ls <- sequence [ uncurry fn m | m <- zip stys [1..] ]
    return (M.fromList ls)
  where
    fn :: Style -> Int -> IO (Style, (Curses.Attr,Curses.Pair))
    fn sty p = do
        let (CColor (a,fgc),CColor (b,bgc)) = style2curses sty
        handle (\_ -> return ()) $ Curses.initPair (Curses.Pair p) fgc bgc
        return (sty, (a `Curses.attrPlus` b, Curses.Pair p))

------------------------------------------------------------------------
--
-- | Getting from nice abstract colours to ncurses-settable values

-- 20% of allocss occur here! But there's only 3 or 4 colours :/
-- Every call to uiAttr
--
uiAttr :: Style -> IO (Curses.Attr, Curses.Pair)
uiAttr sty = do
    m <- readIORef pairMap
    return $ lookupPair m sty
{-# INLINE uiAttr #-}

-- | Given a curses color pair, find the Curses.Pair (i.e. the pair
-- curses thinks these colors map to) from the state
lookupPair :: PairMap -> Style -> (Curses.Attr, Curses.Pair)
lookupPair m s = case M.lookup s m of
                    Nothing   -> (Curses.attr0, Curses.Pair 0) -- default settings
                    Just v    -> v
{-# INLINE lookupPair #-}

-- | Keep a map of nice style defs to underlying curses pairs, created at init time
type PairMap = M.Map Style (Curses.Attr, Curses.Pair)

-- | map of Curses.Color pairs to ncurses terminal Pair settings
pairMap :: IORef PairMap
pairMap = unsafePerformIO $ newIORef M.empty
{-# NOINLINE pairMap #-}

------------------------------------------------------------------------
--
-- Basic (ncurses) colours.
--
defaultColor :: Curses.Color
defaultColor = fromJust $ Curses.color "default"

cblack, cred, cgreen, cyellow, cblue, cmagenta, ccyan, cwhite :: Curses.Color
cblack     = fromJust $ Curses.color "black"
cred       = fromJust $ Curses.color "red"
cgreen     = fromJust $ Curses.color "green"
cyellow    = fromJust $ Curses.color "yellow"
cblue      = fromJust $ Curses.color "blue"
cmagenta   = fromJust $ Curses.color "magenta"
ccyan      = fromJust $ Curses.color "cyan"
cwhite     = fromJust $ Curses.color "white"

--
-- Combine attribute with another attribute
--
setBoldA, setReverseA ::  Curses.Attr -> Curses.Attr
setBoldA     = flip Curses.setBold    True
setReverseA  = flip Curses.setReverse True

--
-- | Some attribute constants
--
boldA, nullA, reverseA :: Curses.Attr
nullA       = Curses.attr0
boldA       = setBoldA      nullA
reverseA    = setReverseA   nullA

------------------------------------------------------------------------

newtype CColor = CColor (Curses.Attr, Curses.Color)
-- 
-- | Map Style rgb rgb colours to ncurses pairs
-- TODO a generic way to turn an rgb into the nearest curses color
--
style2curses :: Style -> (CColor, CColor)
style2curses (Style fg bg) = (fgCursCol fg, bgCursCol bg)
{-# INLINE style2curses #-}

fgCursCol :: Color -> CColor
fgCursCol c = case c of
    RGB 0 0 0         -> CColor (nullA, cblack)
    RGB 128 128 128   -> CColor (boldA, cblack)
    RGB 139 0 0       -> CColor (nullA, cred)
    RGB 255 0 0       -> CColor (boldA, cred)
    RGB 0 100 0       -> CColor (nullA, cgreen)
    RGB 0 128 0       -> CColor (boldA, cgreen)
    RGB 165 42 42     -> CColor (nullA, cyellow)
    RGB 255 255 0     -> CColor (boldA, cyellow)
    RGB 0 0 139       -> CColor (nullA, cblue)
    RGB 0 0 255       -> CColor (boldA, cblue)
    RGB 128 0 128     -> CColor (nullA, cmagenta)
    RGB 255 0 255     -> CColor (boldA, cmagenta)
    RGB 0 139 139     -> CColor (nullA, ccyan)
    RGB 0 255 255     -> CColor (boldA, ccyan)
    RGB 165 165 165   -> CColor (nullA, cwhite)
    RGB 255 255 255   -> CColor (boldA, cwhite)
    Default           -> CColor (nullA, defaultColor)
    Reverse           -> CColor (reverseA, defaultColor)
    _                 -> CColor (nullA, cblack) -- NB

bgCursCol :: Color -> CColor
bgCursCol c = case c of
    RGB 0 0 0         -> CColor (nullA, cblack)
    RGB 128 128 128   -> CColor (nullA, cblack)
    RGB 139 0 0       -> CColor (nullA, cred)
    RGB 255 0 0       -> CColor (nullA, cred)
    RGB 0 100 0       -> CColor (nullA, cgreen)
    RGB 0 128 0       -> CColor (nullA, cgreen)
    RGB 165 42 42     -> CColor (nullA, cyellow)
    RGB 255 255 0     -> CColor (nullA, cyellow)
    RGB 0 0 139       -> CColor (nullA, cblue)
    RGB 0 0 255       -> CColor (nullA, cblue)
    RGB 128 0 128     -> CColor (nullA, cmagenta)
    RGB 255 0 255     -> CColor (nullA, cmagenta)
    RGB 0 139 139     -> CColor (nullA, ccyan)
    RGB 0 255 255     -> CColor (nullA, ccyan)
    RGB 165 165 165   -> CColor (nullA, cwhite)
    RGB 255 255 255   -> CColor (nullA, cwhite)
    Default           -> CColor (nullA, defaultColor)
    Reverse           -> CColor (reverseA, defaultColor)
    _                 -> CColor (nullA, cwhite)    -- NB

defaultSty :: Style
defaultSty = Style Default Default

------------------------------------------------------------------------
--
-- Support for runtime configuration
-- We choose a simple strategy, read/showable record types, with strings
-- to represent colors
--
-- The fields must map to UIStyle
--
-- It is this data type that is stored in 'show' format in ~/.hmp3
--
data Config = Config {
         hmp3_window      :: (String,String)
       , hmp3_helpscreen  :: (String,String)
       , hmp3_titlebar    :: (String,String)
       , hmp3_selected    :: (String,String)
       , hmp3_cursors     :: (String,String)
       , hmp3_combined    :: (String,String)
       , hmp3_warnings    :: (String,String)
       , hmp3_blockcursor :: (String,String)
       , hmp3_progress    :: (String,String)
     } deriving (Show,Read)

--
-- | Read the ~/.hmp3 file, and construct a UIStyle from it, to insert
-- into 
--
buildStyle :: Config -> UIStyle
buildStyle bs = UIStyle {
         window      = f $ hmp3_window      bs
       , helpscreen  = f $ hmp3_helpscreen  bs
       , titlebar    = f $ hmp3_titlebar    bs
       , selected    = f $ hmp3_selected    bs
       , cursors     = f $ hmp3_cursors     bs
       , combined    = f $ hmp3_combined    bs
       , warnings    = f $ hmp3_warnings    bs
       , blockcursor = f $ hmp3_blockcursor bs
       , progress    = f $ hmp3_progress    bs
    }

    where 
        f (x,y) = Style (g x) (g y)
        g x     = case stringToColor x of
                    Nothing -> Default
                    Just y  -> y
