{-# LANGUAGE DeriveDataTypeable     #-}
{-# LANGUAGE FlexibleContexts       #-}
{-# LANGUAGE FlexibleInstances      #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses  #-}
{-# LANGUAGE TemplateHaskell        #-}
{-# LANGUAGE TypeFamilies           #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Plots.Axis.Grid
-- Copyright   :  (C) 2015 Christopher Chalmers
-- License     :  BSD-style (see the file LICENSE)
-- Maintainer  :  Christopher Chalmers
-- Stability   :  experimental
-- Portability :  non-portable
--
-- Lines that go along the axis. Supports major and minor grid lines
-- separately for each axis.
--
----------------------------------------------------------------------------
module Plots.Axis.Grid
  ( -- * Grid lines
    GridLines
  , HasGridLines (..)

  , MajorGridLines
  , HasMajorGridLines (..)
  , MinorGridLines
  , HasMinorGridLines (..)

    -- * Extra traversals
  , gridLinesStyle
  , gridLinesVisible

  , hideGridLines
  , showGridLines

    -- * Grid line functions
  , GridLineFunction
  , onTicksGridLineFunction
  , emptyGridLineFunction

  ) where

import           Control.Lens        hiding (( # ))
import           Data.Data
import           Data.Default
import           Control.Monad.State

import           Diagrams.Prelude
import           Plots.Types

-- | A grid line function takes the positions of the respective ticks
--   (minor ticks for minor grid lines, major ticks for major grid
--   lines) and the bounds of the axis and returns the positions of the
--   grid lines.
--
--   These functions are used in conjuction with 'majorGridLineFunction'
--   and 'minorGridLineFunction' to control how the lines are drawn.
type GridLineFunction n = [n] -> (n, n) -> [n]

------------------------------------------------------------------------
-- Major grid lines
------------------------------------------------------------------------

data MajorGridLines v n = MajorGridLines
  { magFun     :: GridLineFunction n
  , magStyle   :: Style v n
  , magVisible :: Bool
  } deriving Typeable

type instance V (MajorGridLines v n) = v
type instance N (MajorGridLines v n) = n

class HasMajorGridLines f a where
  -- | The options for how to draw the grid lines. This can be used on
  --   various levels of the axis:
  --
  -- @
  -- 'majorGridLines' :: 'Traversal'' ('Axis' b c n)       ('GridLines' ('BaseSpace' c) n)
  -- 'majorGridLines' :: 'Lens''      ('SingleAxis' b v n) ('GridLines' v n)
  -- 'majorGridLines' :: 'Lens''      ('GridLines' v n)    ('GridLines' v n)
  -- @
  majorGridLines :: LensLike' f a (MajorGridLines (V a) (N a))

  -- | The function to calculate location of the major grid lines given
  --   location of the major ticks and bounds.
  majorGridLinesFunction :: Functor f => LensLike' f a (GridLineFunction (N a))
  majorGridLinesFunction = majorGridLines . lens magFun (\gl maf -> gl {magFun = maf})

  -- | The style applied to the major grid lines.
  majorGridLinesStyle :: Functor f => LensLike' f a (Style (V a) (N a))
  majorGridLinesStyle = majorGridLines . lens magStyle (\gl sty -> gl {magStyle = sty})

instance HasMajorGridLines f (MajorGridLines v n) where
  majorGridLines = id

instance (Typeable n, Floating n) => Default (MajorGridLines v n) where
  def = MajorGridLines
    { magFun     = onTicksGridLineFunction
    , magStyle   = mempty # lwO 0.8
    , magVisible = True
    }

instance HasVisibility (MajorGridLines v n) where
  visible = lens magVisible (\gl b -> gl {magVisible = b})

instance Typeable n => HasStyle (MajorGridLines v n) where
  applyStyle s = majorGridLinesStyle %~ applyStyle s

------------------------------------------------------------------------
-- Minor grid lines
------------------------------------------------------------------------

data MinorGridLines v n = MinorGridLines
  { migFun     :: GridLineFunction n
  , migStyle   :: Style v n
  , migVisible :: Bool
  } deriving Typeable

type instance V (MinorGridLines v n) = v
type instance N (MinorGridLines v n) = n

class HasMinorGridLines f a where
  -- | The options for how to draw the grid lines. This can be used on
  --   various levels of the axis:
  --
  -- @
  -- 'minorGridLines' :: 'Traversal'' ('Axis' b c n)       ('GridLines' ('BaseSpace' c) n)
  -- 'minorGridLines' :: 'Lens''      ('SingleAxis' b v n) ('GridLines' v n)
  -- 'minorGridLines' :: 'Lens''      ('GridLines' v n)    ('GridLines' v n)
  -- @
  minorGridLines :: LensLike' f a (MinorGridLines (V a) (N a))

  -- | The function to calculate location of the minor grid lines given
  --   location of the minor ticks and bounds.
  minorGridLinesFunction :: Functor f => LensLike' f a (GridLineFunction (N a))
  minorGridLinesFunction = minorGridLines . lens migFun (\gl mif -> gl {migFun = mif})


  -- | The style applied to the minor grid lines.
  minorGridLinesStyle :: Functor f => LensLike' f a (Style (V a) (N a))
  minorGridLinesStyle = minorGridLines . lens migStyle (\gl sty -> gl {migStyle = sty})

instance HasMinorGridLines f (MinorGridLines v n) where
  minorGridLines = id

instance (Typeable n, Floating n) => Default (MinorGridLines v n) where
  def = MinorGridLines
    { migFun     = onTicksGridLineFunction
    , migStyle   = mempty # lwO 0.5
    , migVisible = False
    }

-- | Hidden by default.
instance HasVisibility (MinorGridLines v n) where
  visible = lens migVisible (\gl b -> gl {migVisible = b})

instance Typeable n => HasStyle (MinorGridLines v n) where
  applyStyle s = minorGridLinesStyle %~ applyStyle s

------------------------------------------------------------------------
-- Grid lines helpers
------------------------------------------------------------------------

-- | Place grid lines at the same position as the respective ticks. This
--   is the 'Default'.
onTicksGridLineFunction :: GridLineFunction n
onTicksGridLineFunction = const

-- | The 'GridLineFunction' such that no grid lines appear.
--
--   See 'hideGridLines', 'majorGridLineVisible' or
--   'minorGridLineVisible' if you just want to hide the grid lines.
emptyGridLineFunction :: GridLineFunction n
emptyGridLineFunction _ _ = []

-- | Traversal over both the major and minor grid styles.
--
-- @
-- 'gridLinesVisible' :: 'Traversal'' ('Axis' b c n) 'Bool'
-- 'gridLinesVisible' :: 'Traversal'' ('SingleAxis' b v n) 'Bool'
-- 'gridLinesVisible' :: 'Traversal'' ('GridLines' v n) 'Bool'
-- @
gridLinesVisible :: (HasGridLines f a, Applicative f) => LensLike' f a Bool
gridLinesVisible = gridLines . vis where
  vis :: Traversal' (GridLines v n) Bool
  vis f a =
       (\m mn -> a & majorGridLines . visible .~ m & minorGridLines . visible .~ mn)
         <$> f (a ^. majorGridLines . visible) <*> f (a ^. minorGridLines . visible)

------------------------------------------------------------------------
-- Both grid lines
------------------------------------------------------------------------

-- | Type holding information about both major and minor grid lines.
data GridLines v n = GridLines
  { majGrid     :: MajorGridLines v n
  , minGrid     :: MinorGridLines v n
  } deriving Typeable

type instance V (GridLines v n) = v
type instance N (GridLines v n) = n

class (HasMinorGridLines f a, HasMajorGridLines f a) => HasGridLines f a where
  gridLines :: LensLike' f a (GridLines (V a) (N a))

instance Functor f => HasGridLines f (GridLines v n) where
  gridLines = id

instance (Typeable n, Floating n) => Default (GridLines v n) where
  def = GridLines
    { majGrid = def
    , minGrid = def
    }

instance Functor f => HasMajorGridLines f (GridLines v n) where
  majorGridLines = lens majGrid (\g a -> g {majGrid = a})

instance Functor f => HasMinorGridLines f (GridLines v n) where
  minorGridLines = lens minGrid (\g a -> g {minGrid = a})

instance Typeable n => HasStyle (GridLines v n) where
  applyStyle s = (majorGridLines %~ applyStyle s) . (minorGridLines %~ applyStyle s)

-- | Hide both major and minor grid lines.
--
-- @
-- 'hideGridLines' :: 'Axis' b c n       -> 'Axis' b c n
-- 'hideGridLines' :: 'SingleAxis' b c n -> 'SingleAxis' b c n
-- 'hideGridLines' :: 'GridLines' b c n  -> 'GridLines' b c n
-- @
hideGridLines :: (HasGridLines Identity a, MonadState a m) => m ()
hideGridLines = do
  minorGridLines . visible .= False
  majorGridLines . visible .= False

-- | Show both major and minor grid lines.
--
-- @
-- 'showGridLines' :: 'Axis' b c n       -> 'Axis' b c n
-- 'showGridLines' :: 'SingleAxis' b c n -> 'SingleAxis' b c n
-- 'showGridLines' :: 'GridLines' b c n  -> 'GridLines' b c n
-- @
showGridLines :: (HasGridLines Identity a, MonadState a m) => m ()
showGridLines = do
  minorGridLines . visible .= True
  majorGridLines . visible .= True

-- | Traversal over both the major and minor grid styles. This can be used at several levels in the axis:
gridLinesStyle :: (HasGridLines f a, Applicative f) => LensLike' f a (Style (V a) (N a))
gridLinesStyle = gridLines . styles where
  styles :: Traversal' (GridLines v n) (Style v n)
  styles f a =
    (\m mn -> a & majorGridLinesStyle .~ m & minorGridLinesStyle .~ mn)
      <$> f (a ^. majorGridLinesStyle) <*> f (a ^. minorGridLinesStyle)