module Data.GraphViz.Attributes.Colors
       ( 
         ColorScheme(..)
         
       , Color(..)
       , ColorList
       , WeightedColor(..)
       , toWC
       , toColorList
       , NamedColor(toColor)
       , toWColor
         
       , toColour
       , fromColour
       , fromAColour
       ) where
import Data.GraphViz.Attributes.Colors.Brewer (BrewerColor (..))
import Data.GraphViz.Attributes.Colors.SVG    (SVGColor, svgColour)
import Data.GraphViz.Attributes.Colors.X11    (X11Color (Transparent),
                                               x11Colour)
import Data.GraphViz.Attributes.ColorScheme   (ColorScheme (..))
import Data.GraphViz.Exception
import Data.GraphViz.Internal.State
import Data.GraphViz.Internal.Util            (bool)
import Data.GraphViz.Parsing
import Data.GraphViz.Printing
import Data.Colour              (AlphaColour, alphaChannel, black, darken,
                                 opaque, over, withOpacity)
import Data.Colour.RGBSpace     (uncurryRGB)
import Data.Colour.RGBSpace.HSV (hsv)
import Data.Colour.SRGB         (Colour, sRGB, sRGB24, toSRGB24)
import           Data.Char      (isHexDigit)
import           Data.Maybe     (isJust)
import qualified Data.Text.Lazy as T
import           Data.Word      (Word8)
import           Numeric        (readHex, showHex)
data Color = RGB { red   :: Word8
                 , green :: Word8
                 , blue  :: Word8
                 }
           | RGBA { red   :: Word8
                  , green :: Word8
                  , blue  :: Word8
                  , alpha :: Word8
                  }
             
             
           | HSV { hue        :: Double
                 , saturation :: Double
                 , value      :: Double
                 }
           | X11Color X11Color
           | SVGColor SVGColor
           | BrewerColor BrewerColor
           deriving (Eq, Ord, Show, Read)
instance PrintDot Color where
  unqtDot (RGB  r g b)     = hexColor [r,g,b]
  unqtDot (RGBA r g b a)   = hexColor [r,g,b,a]
  unqtDot (HSV  h s v)     = hcat . punctuate comma $ mapM unqtDot [h,s,v]
  unqtDot (SVGColor name)  = printNC False name
  unqtDot (X11Color name)  = printNC False name
  unqtDot (BrewerColor bc) = printNC False bc
  
  toDot (X11Color name)  = printNC True name
  toDot (SVGColor name)  = printNC True name
  toDot (BrewerColor bc) = printNC True bc
  toDot c                = dquotes $ unqtDot c
  unqtListToDot = hcat . punctuate colon . mapM unqtDot
  
  listToDot [X11Color name]  = printNC True name
  listToDot [SVGColor name]  = printNC True name
  listToDot [BrewerColor bc] = printNC True bc
  listToDot cs               = dquotes $ unqtListToDot cs
hexColor :: [Word8] -> DotCode
hexColor = (<>) (char '#') . hcat . mapM word8Doc
word8Doc   :: Word8 -> DotCode
word8Doc w = text $ padding `T.append` simple
  where
    simple = T.pack $ showHex w ""
    padding = T.replicate count (T.singleton '0')
    count = 2  findCols 1 w
    findCols c n
      | n < 16 = c
      | otherwise = findCols (c+1) (n `div` 16)
instance ParseDot Color where
  parseUnqt = oneOf [ parseHexBased
                    , parseHSV
                      
                    , parseNC (undefined :: BrewerColor) False
                    , parseNC (undefined :: SVGColor) False
                    , parseX11Color False
                    ]
              `onFail`
              fail "Could not parse Color"
    where
      parseHexBased
          = character '#' *>
            do cs <- many1 parse2Hex
               return $ case cs of
                          [r,g,b] -> RGB r g b
                          [r,g,b,a] -> RGBA r g b a
                          _ -> throw . NotDotCode
                               $ "Not a valid hex Color specification: "
                                  ++ show cs
      parseHSV = HSV <$> parseUnqt
                     <*  parseSep
                     <*> parseUnqt
                     <*  parseSep
                     <*> parseUnqt
      parseSep = character ',' *> whitespace <|> whitespace1
      parse2Hex = do c1 <- satisfy isHexDigit
                     c2 <- satisfy isHexDigit
                     let [(n, [])] = readHex [c1, c2]
                     return n
  parse = quotedParse parseUnqt
          `onFail` 
          oneOf [ parseNC (undefined :: BrewerColor) True
                , parseNC (undefined :: SVGColor) True
                , parseX11Color True
                ]
          `onFail`
          fail "Could not parse Color"
  parseUnqtList = sepBy1 parseUnqt (character ':')
                  `onFail`
                  do cs <- getColorScheme
                     failBad $ "Error parsing list of Colors with color scheme of "
                               ++ show cs
  parseList = fmap (:[])
              
              (oneOf [ parseNC (undefined :: BrewerColor) True
                     , parseNC (undefined :: SVGColor) True
                     , parseX11Color True
                     ]
              )
              `onFail`
              quotedParse parseUnqtList
              `onFail`
              do cs <- getColorScheme
                 failBad $ "Error parsing list of Colors with color scheme of "
                           ++ show cs
type ColorList = [WeightedColor]
data WeightedColor = WC { wColor    :: Color
                          
                        , weighting :: Maybe Double
                        }
                   deriving (Eq, Ord, Show, Read)
toWC :: Color -> WeightedColor
toWC = (`WC` Nothing)
toColorList :: [Color] -> ColorList
toColorList = map toWC
instance PrintDot WeightedColor where
  unqtDot (WC c mw) = unqtDot c
                      <> maybe empty ((semi<>) . unqtDot) mw
  toDot (WC c Nothing) = toDot c
  toDot wc             = dquotes $ unqtDot wc
  unqtListToDot = hcat . punctuate colon . mapM unqtDot
  
  listToDot [wc] = toDot wc
  listToDot wcs  = dquotes $ unqtListToDot wcs
instance ParseDot WeightedColor where
  parseUnqt = WC <$> parseUnqt <*> optional (character ';' *> parseUnqt)
  parse = quotedParse parseUnqt
          `onFail`
          
          
          (toWC <$> parse)
  parseUnqtList = sepBy1 parseUnqt (character ':')
                  `onFail`
                  do cs <- getColorScheme
                     failBad $ "Error parsing a ColorList with color scheme of "
                               ++ show cs
  parseList = quotedParse parseUnqtList
              `onFail`
              ((:[]) . toWC <$> parse)
              
              `onFail`
              do cs <- getColorScheme
                 failBad $ "Error parsing ColorList with color scheme of "
                           ++ show cs
class NamedColor nc where
    colorScheme :: nc -> ColorScheme
    toColor :: nc -> Color
    printNC :: Bool -> nc -> DotCode
    
    parseNC' :: Bool -> Parse nc
toWColor :: (NamedColor nc) => nc -> WeightedColor
toWColor = toWC . toColor
parseNC :: (NamedColor nc) => nc -> Bool -> Parse Color
parseNC nc q = fmap (toColor . (`asTypeOf` nc))
               $ parseNC' q
instance NamedColor BrewerColor where
    colorScheme (BC bs _) = Brewer bs
    toColor = BrewerColor
    printNC = printNamedColor (\ (BC _ l) -> l)
    parseNC' = parseNamedColor mBCS parseUnqt (const True) BC
        where
          mBCS (Brewer bs) = Just bs
          mBCS _           = Nothing
instance NamedColor X11Color where
    colorScheme = const X11
    toColor = X11Color
    printNC = printNamedColor id
    parseNC' = parseNamedColor mX11 (parseColorScheme False) (isJust . mX11) (const id)
        where
          mX11 X11 = Just X11
          mX11 _   = Nothing
instance NamedColor SVGColor where
    colorScheme = const SVG
    toColor = SVGColor
    printNC = printNamedColor id
    parseNC' = parseNamedColor mSVG (parseColorScheme False) (isJust . mSVG) (const id)
        where
          mSVG SVG = Just SVG
          mSVG _   = Nothing
printNamedColor :: (NamedColor nc, PrintDot lv) => (nc -> lv)
                   -> Bool -> nc -> DotCode
printNamedColor fl q c = do currentCS <- getColorScheme
                            if cs == currentCS
                               then (bool unqtDot toDot q) lv
                               else bool id dquotes q
                                    $ fslash <> printColorScheme False cs
                                      <> fslash <> unqtDot lv
    where
      cs = colorScheme c
      lv = fl c
parseNamedColor :: (NamedColor nc, ParseDot lv)
                   => (ColorScheme -> Maybe cs) -> Parse cs -> (cs -> Bool)
                   -> (cs -> lv -> nc) -> Bool -> Parse nc
parseNamedColor gcs parseCS vcs mkC q
    = do Just cs <- gcs <$> getColorScheme
         lv <- bool parseUnqt parse q
               `onFail`
               mQts (string "//" *> parseUnqt)
         return $ mkC cs lv
      `onFail`
      mQts ( do character '/'
                cs <- parseCS
                character '/'
                if vcs cs
                   then mkC cs <$>  parseUnqt
                   else fail "Explicit colorscheme not as expected."
           )
    where
      mQts = bool id quotedParse q
parseX11Color   :: Bool -> Parse Color
parseX11Color q = X11Color
                  <$> parseNC' q
                      `onFail`
                      bool id quotedParse q (character '/' *> parseUnqt)
                      `onFail`
                      
                      do cs <- getColorScheme
                         case cs of
                           Brewer{} -> bool parseUnqt parse q
                           _        -> fail "Unable to parse an X11 color within Brewer"
toColour                :: Color -> Maybe (AlphaColour Double)
toColour (RGB r g b)    = Just . opaque $ sRGB24 r g b
toColour (RGBA r g b a) = Just . withOpacity (sRGB24 r g b) $ toOpacity a
toColour (HSV h s v)    = Just . opaque . uncurryRGB sRGB $ hsv (h*360) s v
toColour (X11Color c)   = Just $ x11Colour c
toColour (SVGColor c)   = Just . opaque $ svgColour c
toColour BrewerColor{}  = Nothing
toOpacity   :: Word8 -> Double
toOpacity a = fromIntegral a / maxWord
fromColour :: Colour Double -> Color
fromColour = uncurryRGB RGB . toSRGB24
fromAColour :: AlphaColour Double -> Color
fromAColour ac
  | a == 0    = X11Color Transparent
  | otherwise = rgb $ round a'
  where
    a = alphaChannel ac
    a' = a * maxWord
    rgb = uncurryRGB RGBA $ toSRGB24 colour
    colour = darken (recip a) (ac `over` black)
maxWord :: Double
maxWord = fromIntegral (maxBound :: Word8)