{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ViewPatterns #-}
{-# OPTIONS_GHC -Wno-name-shadowing #-}

{-
  Copyright 2019 The CodeWorld Authors. All rights reserved.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
-}
module CodeWorld.Color where

import Control.DeepSeq
import GHC.Generics (Generic)

data Color
  = RGBA
      !Double
      !Double
      !Double
      !Double
  deriving (Generic, Show, Eq)

instance NFData Color

type Colour = Color

pattern RGB :: Double -> Double -> Double -> Color
pattern RGB r g b = RGBA r g b 1

pattern HSL :: Double -> Double -> Double -> Color
pattern HSL h s l <-
  (toHSL -> Just (h, s, l))
  where
    HSL h s l = fromHSL h s l

-- Utility functions for pattern synonyms.
fence :: Double -> Double
fence = max 0 . min 1

wrapNum :: Double -> Double -> Double
wrapNum lim x = x - fromInteger (floor (x / lim)) * lim

fenceColor :: Color -> Color
fenceColor (RGBA r g b a) = RGBA (fence r) (fence g) (fence b) (fence a)

-- Based on the algorithm from the CSS3 specification.
fromHSL :: Double -> Double -> Double -> Color
fromHSL (wrapNum (2 * pi) -> h) (fence -> s) (fence -> l) = RGBA r g b 1
  where
    m1 = l * 2 - m2
    m2
      | l <= 0.5 = l * (s + 1)
      | otherwise = l + s - l * s
    r = convert m1 m2 (h / 2 / pi + 1 / 3)
    g = convert m1 m2 (h / 2 / pi)
    b = convert m1 m2 (h / 2 / pi - 1 / 3)
    convert m1 m2 h
      | h < 0 = convert m1 m2 (h + 1)
      | h > 1 = convert m1 m2 (h - 1)
      | h * 6 < 1 = m1 + (m2 - m1) * h * 6
      | h * 2 < 1 = m2
      | h * 3 < 2 = m1 + (m2 - m1) * (2 / 3 - h) * 6
      | otherwise = m1

toHSL :: Color -> Maybe (Double, Double, Double)
toHSL c@(RGBA _ _ _ 1) = Just (hue c, saturation c, luminosity c)
toHSL _ = Nothing

mixed :: [Color] -> Color
mixed colors = go 0 0 0 0 0 colors
  where
    go rr gg bb aa n ((fenceColor -> RGBA r g b a) : cs) =
      go (rr + r * r * a) (gg + g * g * a) (bb + b * b * a) (aa + a) (n + 1) cs
    go rr gg bb aa n []
      | aa == 0 = RGBA 0 0 0 0
      | otherwise = RGBA (sqrt (rr / aa)) (sqrt (gg / aa)) (sqrt (bb / aa)) (aa / n)

-- Helper function that sets the alpha of the second color to that
-- of the first
sameAlpha :: Color -> Color -> Color
sameAlpha (fenceColor -> RGBA _ _ _ a1) (fenceColor -> RGBA r2 g2 b2 _) =
  RGBA r2 g2 b2 a1

lighter :: Double -> Color -> Color
lighter d c =
  sameAlpha c $ HSL (hue c) (saturation c) (fence (luminosity c + d))

light :: Color -> Color
light = lighter 0.15

darker :: Double -> Color -> Color
darker d = lighter (- d)

dark :: Color -> Color
dark = darker 0.15

brighter :: Double -> Color -> Color
brighter d c =
  sameAlpha c $ HSL (hue c) (fence (saturation c + d)) (luminosity c)

bright :: Color -> Color
bright = brighter 0.25

duller :: Double -> Color -> Color
duller d = brighter (- d)

dull :: Color -> Color
dull = duller 0.25

translucent :: Color -> Color
translucent (fenceColor -> RGBA r g b a) = RGBA r g b (a / 2)

-- | An infinite list of colors.
assortedColors :: [Color]
assortedColors = [HSL (adjusted h) 0.75 0.5 | h <- [0, 2 * pi / phi ..]]
  where
    phi = (1 + sqrt 5) / 2
    adjusted x =
      x + a0
        + a1 * sin (1 * x)
        + b1 * cos (1 * x)
        + a2 * sin (2 * x)
        + b2 * cos (2 * x)
        + a3 * sin (3 * x)
        + b3 * cos (3 * x)
        + a4 * sin (4 * x)
        + b4 * cos (4 * x)
    a0 = -8.6870353473225553e-02
    a1 = 8.6485747604766350e-02
    b1 = -9.6564816819163041e-02
    a2 = -3.0072759267059756e-03
    b2 = 1.5048456422494966e-01
    a3 = 9.3179137558373148e-02
    b3 = 2.9002513227535595e-03
    a4 = -6.6275768228887290e-03
    b4 = -1.0451841243520298e-02

hue :: Color -> Double
hue (fenceColor -> RGBA r g b _)
  | hi - lo < epsilon = 0
  | r == hi && g >= b = (g - b) / (hi - lo) * pi / 3
  | r == hi = (g - b) / (hi - lo) * pi / 3 + 2 * pi
  | g == hi = (b - r) / (hi - lo) * pi / 3 + 2 / 3 * pi
  | otherwise = (r - g) / (hi - lo) * pi / 3 + 4 / 3 * pi
  where
    hi = max r (max g b)
    lo = min r (min g b)
    epsilon = 0.000001

saturation :: Color -> Double
saturation (fenceColor -> RGBA r g b _)
  | hi - lo < epsilon = 0
  | otherwise = (hi - lo) / (1 - abs (hi + lo - 1))
  where
    hi = max r (max g b)
    lo = min r (min g b)
    epsilon = 0.000001

luminosity :: Color -> Double
luminosity (fenceColor -> RGBA r g b _) = (lo + hi) / 2
  where
    hi = max r (max g b)
    lo = min r (min g b)

alpha :: Color -> Double
alpha (RGBA _ _ _ a) = fence a

-- Old-style colors

white, black, red, green, blue, cyan, magenta, yellow :: Color

orange, rose, chartreuse, aquamarine, violet, azure :: Color

gray, grey, brown, purple, pink :: Color

white = HSL 0.00 0.00 1.00

black = HSL 0.00 0.00 0.00

gray = HSL 0.00 0.00 0.50

grey = HSL 0.00 0.00 0.50

red = HSL 0.00 0.75 0.50

orange = HSL 0.61 0.75 0.50

yellow = HSL 0.98 0.75 0.50

green = HSL 2.09 0.75 0.50

blue = HSL 3.84 0.75 0.50

purple = HSL 4.80 0.75 0.50

pink = HSL 5.76 0.75 0.75

brown = HSL 0.52 0.60 0.40

cyan = HSL (3 / 3 * pi) 0.75 0.5

magenta = HSL (5 / 3 * pi) 0.75 0.5

chartreuse = HSL (3 / 6 * pi) 0.75 0.5

aquamarine = HSL (5 / 6 * pi) 0.75 0.5

azure = HSL (7 / 6 * pi) 0.75 0.5

violet = HSL (9 / 6 * pi) 0.75 0.5

rose = HSL (11 / 6 * pi) 0.75 0.5

{-# WARNING
  magenta
  [ "Please use HSL(5 * pi / 3, 0.75, 0.5) instead of magenta.",
    "The variable magenta may be removed July 2020."
  ]
  #-}

{-# WARNING
  cyan
  [ "Please use HSL(pi, 0.75, 0.5) instead of cyan.",
    "The variable cyan may be removed July 2020."
  ]
  #-}

{-# WARNING
  chartreuse
  [ "Please use HSL(pi / 2, 0.75, 0.5) instead of chartreuse.",
    "The variable chartreuse may be removed July 2020."
  ]
  #-}

{-# WARNING
  aquamarine
  [ "Please use HSL(5 * pi / 6, 0.75, 0.5) instead of aquamarine.",
    "The variable aquamarine may be removed July 2020."
  ]
  #-}

{-# WARNING
  azure
  [ "Please use HSL(7 * pi / 6, 0.75, 0.5) instead of azure.",
    "The variable azure may be removed July 2020."
  ]
  #-}

{-# WARNING
  rose
  [ "Please use HSL(11 * pi / 6, 0.75, 0.5) instead of rose.",
    "The variable rose may be removed July 2020."
  ]
  #-}

{-# WARNING
  violet
  [ "Please use purple instead of violet.",
    "The variable violet may be removed July 2020."
  ]
  #-}