content: {-|
  Module      : Monomer.Graphics.Util
  Copyright   : (c) 2018 Francisco Vallarino
  License     : BSD-3-Clause (see the LICENSE file)
  Maintainer  : fjvallarino@gmail.com
  Stability   : experimental
  Portability : non-portable
  
  Helper functions for graphics related operations.
  -}
  {-# LANGUAGE Strict #-}
  
  module Monomer.Graphics.Util (
    clampChannel,
    clampAlpha,
    rgb,
    rgba,
    rgbHex,
    rgbaHex,
    hsl,
    hsla,
    colorToHsl,
    rgbToHsl,
    transparent,
    alignInRect,
    alignHInRect,
    alignVInRect
  ) where
  
  import Data.Char (digitToInt)
  
  import Monomer.Common.BasicTypes
  import Monomer.Graphics.Types
  import Monomer.Helper
  
  -- | Restricts a color channel to its valid range.
  clampChannel :: Int -> Int
  clampChannel channel = clamp 0 255 channel
  
  -- | Restricts an alpha channel to its valid range.
  clampAlpha :: Double -> Double
  clampAlpha alpha = clamp 0 1 alpha
  
  {-|
  Creates a Color from red, green and blue components. Valid range for each
  component is [0, 255].
  -}
  rgb :: Int -> Int -> Int -> Color
  rgb r g b = Color (clampChannel r) (clampChannel g) (clampChannel b) 1.0
  
  {-|
  Creates a Color from red, green and blue components plus alpha channel. Valid
  range for each component is [0, 255], while alpha is [0, 1].
  -}
  rgba :: Int -> Int -> Int -> Double -> Color
  rgba r g b a = Color {
    _colorR = clampChannel r,
    _colorG = clampChannel g,
    _colorB = clampChannel b,
    _colorA = clampAlpha a
  }
  
  -- | Creates a Color from a hex string. It may include a # prefix or not.
  rgbHex :: String -> Color
  rgbHex hex
    | length hex == 7 = rgbHexSix (tail hex)
    | length hex == 6 = rgbHexSix hex
    | otherwise = rgb 0 0 0
  
  -- | Creates a color from a six characters hex string. Fails if len is invalid.
  rgbHexSix :: [Char] -> Color
  rgbHexSix hex = rgb r g b where
    [r1, r2, g1, g2, b1, b2] = hex
    r = digitToInt r1 * 16 + digitToInt r2
    g = digitToInt g1 * 16 + digitToInt g2
    b = digitToInt b1 * 16 + digitToInt b2
  
  {-|
  Creates a Color from a hex string plus an alpha component. It may include a #
  prefix or not.
  -}
  rgbaHex :: String -> Double -> Color
  rgbaHex hex alpha = (rgbHex hex) {
      _colorA = clampAlpha alpha
    }
  
  {-|
  Creates a Color instance from HSL components. The valid ranges are:
  
  - Hue: [0, 360]
  - Saturation: [0, 100]
  - Lightness: [0, 100]
  
  Alpha is set to 1.0.
  -}
  hsl :: Int -> Int -> Int -> Color
  hsl h s l = Color r g b 1.0 where
    vh = clamp 0 360 (fromIntegral h)
    vs = clamp 0 100 (fromIntegral s / 100)
    vl = clamp 0 100 (fromIntegral l / 100)
    a = vs * min vl (1 - vl)
    f n = vl - a * max mink (-1) where
      k = fromIntegral $ round (n + vh / 30) `mod` 12
      mink = minimum [k - 3, 9 - k, 1]
    i n = clampChannel . round $ 255 * f n
    (r, g, b) = (i 0, i 8, i 4)
  
  {-|
  Creates a Color instance from HSL components. The valid ranges are:
  
  - Hue: [0, 360]
  - Saturation: [0, 100]
  - Lightness: [0, 100]
  - Alpha: [0, 1]
  -}
  hsla :: Int -> Int -> Int -> Double -> Color
  hsla h s l a = (hsl h s l) {
      _colorA = clampAlpha a
    }
  
  -- | Converts a 'Color' instance to a tuple representing HSL values
  colorToHsl :: Color -> (Int, Int, Int)
  colorToHsl (Color cr cg cb ca) = rgbToHsl cr cg cb
  
  -- | Converts RGB values to a tuple representing HSL values
  rgbToHsl :: Int -> Int -> Int -> (Int, Int, Int)
  rgbToHsl cr cg cb = (h, round (s * 255), round (l * 255)) where
    r = fromIntegral cr / 255
    g = fromIntegral cg / 255
    b = fromIntegral cb / 255
    minc = minimum [r, g, b]
    maxc = maximum [r, g, b]
    rngc = maxc - minc
    h
      | maxc == minc = 0
      | maxc == r = round (60 * (g - b) / rngc + 360) `mod` 360
      | maxc == g = round (60 * (b - r) / rngc + 120) `mod` 360
      | otherwise = round (60 * (r - g) / rngc + 240) `mod` 360
    l = (minc + maxc) * 0.5
    s
      | maxc == minc = 0
      | l < 0.5 = rngc / (2 * l)
      | otherwise = rngc / (2 - 2 * l)
  
  -- | Creates a non visible color.
  transparent :: Color
  transparent = rgba 0 0 0 0
  
  {-|
  Aligns the child rect inside the parent given the alignment constraints.
  
  Note: The child rect can overflow the parent.
  -}
  alignInRect :: Rect -> Rect -> AlignH -> AlignV -> Rect
  alignInRect parent child ah av = newRect where
    tempRect = alignVInRect parent child av
    newRect = alignHInRect parent tempRect ah
  
  -- | Aligns the child rect horizontally inside the parent.
  alignHInRect :: Rect -> Rect -> AlignH -> Rect
  alignHInRect parent child ah = newRect where
    Rect px _ pw _ = parent
    Rect _ cy cw ch = child
    newX = case ah of
      ALeft -> px
      ACenter -> px + (pw - cw) / 2
      ARight -> px + pw - cw
    newRect = Rect newX cy cw ch
  
  -- | Aligns the child rect vertically inside the parent.
  alignVInRect :: Rect -> Rect -> AlignV -> Rect
  alignVInRect parent child av = newRect where
    Rect _ py _ ph = parent
    Rect cx _ cw ch = child
    newY = case av of
      ATop -> py
      AMiddle -> py + (ph - ch) / 2
      ABottom -> py + ph - ch
    newRect = Rect cx newY cw ch
  