{-# LANGUAGE FlexibleContexts   #-}
{-# LANGUAGE FlexibleInstances  #-}
{-# LANGUAGE OverloadedStrings  #-}
{-# LANGUAGE TypeFamilies       #-}
{-# LANGUAGE TypeApplications   #-}

{-|
A limited Grammar of Graphics-like interface.

@
myPts :: [(Double, Double)]
myPts = [(1,2), (1.2, 3), (1.4,3.5)]



myTrace :: Trace
myTrace = points (aes & x .~ fst
                      & y .~ snd)
                 myPts
@
-}
module Graphics.Plotly.GoG where

import           Data.Aeson
import           Data.Text (Text)
import           Data.Time
import           Lens.Micro

import qualified Graphics.Plotly.Base as Plot


class ToJSON a => AxisValue a

instance AxisValue Double
instance AxisValue Float
instance AxisValue Text
instance AxisValue String
instance AxisValue Int
instance AxisValue Day

instance AxisValue a => AxisValue [a]

data RGB a = RGB a a a
data RGBA a = RGBA a a a a

instance ToJSON (RGB Int) where
  toJSON (RGB r g b) = toJSON $ concat ["rgb(",show r,",",show g, ",", show b,")"]

instance ToJSON (RGB Double) where
  toJSON (RGB r g b) = toJSON $ concat ["rgb(",showd r,",",showd g, ",", showd b,")"]
   where showd = show @Int. floor . (*256)

instance ToJSON (RGBA Int) where
  toJSON (RGBA r g b a) = toJSON $ concat ["rgba(",show r,",",show g, ",", show b,",", show a, ")"]

instance ToJSON (RGBA Double) where
  toJSON (RGBA r g b a) = toJSON $ concat ["rgb(",showd r,",",showd g, ",", showd b,",", showd a,")"]
   where showd = show @Int. floor . (*256)

class ToJSON a => IsColor a

instance IsColor Int
instance IsColor (RGB Int)
instance IsColor (RGB Double)
instance IsColor (RGBA Int)
instance IsColor (RGBA Double)

type family XVal a
type family YVal a
type family ZVal a
type family CVal a
type family SVal a

type instance XVal (x,y,z,c,s) = x
type instance YVal (x,y,z,c,s) = y
type instance ZVal (x,y,z,c,s) = z
type instance CVal (x,y,z,c,s) = c
type instance SVal (x,y,z,c,s) = s

data Aes t a = Aes
    { _x     :: a -> XVal t
    , _y     :: a -> YVal t
    , _z     :: a -> ZVal t
    , _color :: Maybe (a -> CVal t)
    , _size  :: Maybe (a -> SVal t)
    }


aes :: Aes ((), (), (), (), ()) a
aes = Aes (const ()) (const ()) (const ()) Nothing Nothing


setx :: (AxisValue v)
    => Aes (vx,vy,vz,vc,vs) a -> (a -> v) -> Aes (v, vy, vz, vc, vs) a
setx (Aes _ fy fz fc fs) f = Aes f fy fz fc fs


x :: (AxisValue v)
    => Lens (Aes (vx,vy, vz, vc, vs) a) (Aes (v,vy, vz, vc, vs) a) (a -> vx) (a -> v)
x = lens _x setx


sety :: (AxisValue v)
    => Aes (vx,vy, vz, vc, vs) a -> (a -> v) -> Aes (vx, v, vz, vc, vs) a
sety (Aes fx _ fz fc fs) f = Aes fx f fz fc fs


y :: (AxisValue v)
    => Lens (Aes (vx,vy, vz, vc, vs) a) (Aes (vx,v, vz, vc, vs) a) (a -> vy) (a -> v)
y = lens _y sety

setz :: (AxisValue v)
    => Aes (vx,vy, vz, vc, vs) a -> (a -> v) -> Aes (vx, vy, v, vc, vs) a
setz (Aes fx fy _ fc fs) f = Aes fx fy f fc fs


z :: (AxisValue v)
    => Lens (Aes (vx,vy, vz, vc, vs) a) (Aes (vx,vy, v, vc, vs) a) (a -> vz) (a -> v)
z = lens _z setz

setcol :: (IsColor v)
    => Aes (vx,vy, vz, vc, vs) a -> Maybe (a -> v) -> Aes (vx, vy, vz, v, vs) a
setcol (Aes fx fy fz _ fs) f = Aes fx fy fz f fs


color :: (IsColor v)
    => Lens (Aes (vx,vy, vz, vc, vs) a) (Aes (vx,vy,vz, v,vs) a) (Maybe (a -> vc)) (Maybe (a -> v))
color = lens _color setcol


setsize :: (AxisValue v, Num v)
    => Aes (vx,vy, vz, vc, vs) a -> Maybe (a -> v) -> Aes (vx, vy, vz, vc, v) a
setsize (Aes fx fy fz fc _) = Aes fx fy fz fc


size :: (AxisValue v, Num v)
    => Lens (Aes (vx,vy, vz, vc, vs) a) (Aes (vx,vy,vz, vc,v) a) (Maybe (a -> vs)) (Maybe (a -> v))
size = lens _size setsize


points :: (AxisValue (XVal t), AxisValue (YVal t), ToJSON (CVal t), ToJSON (SVal t))
       => Aes t a -> [a] -> Plot.Trace
points a xs =  setSize (_size a) $ setColors (_color a) $ Plot.scatter
                 & Plot.x ?~ map (toJSON . _x a) xs
                 & Plot.y ?~ map (toJSON . _y a) xs
                 & Plot.mode ?~ [Plot.Markers]
  where setColors Nothing p = p
        setColors (Just setC) p
          = p & Plot.marker . non Plot.defMarker . Plot.markercolor ?~ Plot.List (map (toJSON . setC) xs)
        setSize Nothing p = p
        setSize (Just setS) p
          = p & Plot.marker . non Plot.defMarker . Plot.size ?~ Plot.List (map (toJSON . setS) xs)

line :: (AxisValue (XVal t), AxisValue (YVal t))
       => Aes t a -> [a] -> Plot.Trace
line a xs = Plot.scatter & Plot.x ?~ map (toJSON . _x a) xs
                 & Plot.y ?~ map (toJSON . _y a) xs
                 & Plot.mode ?~ [Plot.Lines]

-- | Render an Aes (styling header) <a> and data <xs> into a `Plot.Trace box`
-- e.g. `hbox myAes [1,1,4,5,6,9,9]`
--
hbox  :: (AxisValue (XVal t), Num (XVal t))
      => Aes t a
      -> [a]
      -> Plot.Trace
hbox a xs = Plot.box
          & Plot.x    ?~ map (toJSON . _x a) xs
          & Plot.mode ?~ [Plot.Lines]

-- | Render an Aes (styling header) <a> and data <ys> into a `Plot.Trace box`
-- e.g. `vbox myAes [1,1,4,5,6,9,9]`
--
vbox  :: (AxisValue (YVal t), Num (YVal t))
      => Aes t a
      -> [a]
      -> Plot.Trace
vbox a ys = Plot.box
          & Plot.y    ?~ map (toJSON . _y a) ys
          & Plot.mode ?~ [Plot.Lines]

hbars :: (AxisValue (XVal t), AxisValue (YVal t))
       => Aes t a -> [a] -> Plot.Trace
hbars a xs = Plot.bars & Plot.x ?~ map (toJSON . _x a) xs
                 & Plot.y ?~ map (toJSON . _y a) xs
                 & Plot.orientation ?~ Plot.Horizontal

hcontour :: (AxisValue (XVal t), AxisValue (YVal t), AxisValue (ZVal t))
         => Aes t a -> [a] -> Plot.Trace
hcontour a xs = Plot.contour
                & Plot.x ?~ map (toJSON . _x a) xs
                & Plot.y ?~ map (toJSON . _y a) xs
                & Plot.z ?~ map (toJSON . _z a) xs
                & Plot.orientation ?~ Plot.Horizontal
