{-|
Module      : Reanimate.Builtin.TernaryPlot
Copyright   : Written by David Himmelstrup
License     : Unlicense
Maintainer  : lemmih@gmail.com
Stability   : experimental
Portability : POSIX

Implementation of ternary plots: <https://en.wikipedia.org/wiki/Ternary_plot>

-}
module Reanimate.Builtin.TernaryPlot
  ( ACoord
  , BCoord
  , CCoord
  , ternaryPlot
  -- , atCenter
  -- , radius
  , toCartesianCoords
  , toOffsetCartesianCoords
  , fromCartesianCoords
  ) where

import           Codec.Picture    (PixelRGBA8 (..), generateImage)
import           Graphics.SvgTree (Tree)
import           Reanimate.Raster (embedImage)
import           Reanimate.Svg    (flipYAxis, scaleToWidth, translate)

-- a+b+c=1
-- | Left-most coordinate.
type ACoord = Double
-- | Top-most coordinate.
type BCoord = Double
-- | Right-most coordinate.
type CCoord = Double

-- | Creates a centered ternary plot with a width of 5.
--
--   Example:
--
-- @
-- 'ternaryPlot' 100 $ \\aCoord bCoord cCoord -> 'Codec.Picture.Types.promotePixel' $
--   let red   = round $ aCoord*255
--       green = round $ bCoord*255
--       blue  = round $ cCoord*255
--   in PixelRGB8 red green blue
-- @
--
--   <<docs/gifs/doc_ternaryPlot.gif>>
ternaryPlot :: Int -- ^ Pixels in the X-axis. More pixels => higher quality.
            -> (ACoord -> BCoord -> CCoord -> PixelRGBA8)
            -- ^ a+b+c=1. A=1 is the left-most position,
            --   B=1 is the top-most position, and
            --   C=1 is the right-most position.
            -> Tree
ternaryPlot density fn =
    scaleToWidth stdWidth $
    translate (-cX) (-cY) $
    scaleToWidth 1 $
    flipYAxis $
    translate (fromIntegral density/2) (-fromIntegral height/2) $
    embedImage $ generateImage gen density height
  where
    stdWidth = 5
    (cX, cY) = toCartesianCoords (1/3) (1/3)
    height = round (fromIntegral density * (sqrt 3 / 2) :: Double)
    gen x y =
      let
          x' = (fromIntegral x / fromIntegral density)
          y' = (fromIntegral y / fromIntegral density)
          aCoord = (x'*2-bCoord)/2
          bCoord = y' / (sqrt 3 / 2)
          cCoord = 1 - aCoord - bCoord
      in if aCoord + bCoord > 1 || aCoord < 0 || bCoord < 0 || cCoord < 0
        then PixelRGBA8 0 0 0 0
        else fn aCoord bCoord cCoord

-- atCenter :: Double -> Tree -> Tree
-- atCenter stdWidth = translate (-cX*stdWidth) (-cY*stdWidth)
--   where
--     (cX, cY) = toCartesianCoords (1/3) (1/3)

-- radius :: Double
-- radius = sqrt (cX*cX + cY*cY)
--   where
--     (cX, cY) = toCartesianCoords (1/3) (1/3)

-- | Compute the XY coordinates from ternary coordinates.
--   Note that @CCoord@ is given because @a+b+c=1@.
toCartesianCoords :: ACoord -> BCoord -> (Double, Double)
toCartesianCoords a b = (x, y)
  where
    x = (a+2*b)/2
    y = (sqrt 3 / 2) * a

-- | Compute the XY coordinates relative from the center of the
--   ternary plot.
--   Note that @CCoord@ is given because @a+b+c=1@.
toOffsetCartesianCoords :: ACoord -> BCoord -> (Double, Double)
toOffsetCartesianCoords a b =
    (tx-zx, ty-zy)
  where
    (zx,zy) = toCartesianCoords (1/3) (1/3)
    (tx,ty) = toCartesianCoords a b

-- | Compute ternary coordinates from XY coordinates.
fromCartesianCoords :: Double -> Double -> (ACoord, BCoord, CCoord)
fromCartesianCoords x y = (a,b,1-a-b)
  where
    a = (x*2-b)/2
    b = y / (sqrt 3 / 2)