module Goal.Core.Plot
    ( -- * Module Exports
      module Graphics.Rendering.Chart
    , module Data.Colour
    , module Data.Colour.Names
    , module Data.Colour.SRGB.Linear
    , module Graphics.Rendering.Chart.Backend.Cairo
    , module Graphics.Rendering.Chart.Grid
    , module Graphics.Rendering.Chart.Gtk
    , module Graphics.Rendering.Chart.State
    , module Goal.Core.Plot.Contour
    -- * Plots
    -- ** PixMap
    , pixMapPlot
    -- ** Histograms
    , histogramPlot
    , histogramPlot0
    , logHistogramPlot
    , logHistogramPlot0
    -- * Layouts
    -- ** PixMap
    , pixMapLayout
    -- ** Histogram
    , histogramLayout
    , logHistogramLayout
    , histogramLayoutLR
    -- * Util
    , rgbaGradient
    -- * Rendering
    , renderableToAspectWindow
    ) where


--- Imports ---

import Data.List hiding (sum)

import Control.Monad
import Control.Lens.Getter
import Control.Lens.Setter hiding (Identity)

import Data.Default.Class
import Numeric


-- Re-exports --

import Graphics.Rendering.Chart hiding (x0,y0,Point)
import Data.Colour
import Data.Colour.Names
import Data.Colour.SRGB.Linear
import Graphics.Rendering.Chart.Backend.Cairo
import Graphics.Rendering.Chart.State
import Graphics.Rendering.Chart.Grid
import Graphics.Rendering.Chart.Gtk

-- Scientific --

import Goal.Core.Plot.Contour

-- Qualified --

import qualified Graphics.UI.Gtk as G


-- Unqualified --

import Graphics.Rendering.Cairo (liftIO)


--- Util ---

rgbaGradient :: (Double, Double, Double, Double) -> (Double, Double, Double, Double) -> Int
    -> [AlphaColour Double]
-- | Returns an ordered list of colours useful for plotting.
rgbaGradient (rmn,gmn,bmn,amn) (rmx,gmx,bmx,amx) n =
    zipWith (flip withOpacity) [amn,amn + astp .. amx]
    $ zipWith3 rgb [rmn,rmn + rstp .. rmx] [gmn,gmn + gstp .. gmx] [bmn,bmn + bstp .. bmx]
    where rstp = (rmx - rmn) / fromIntegral n
          gstp = (gmx - gmn) / fromIntegral n
          bstp = (bmx - bmn) / fromIntegral n
          astp = (amx - amn) / fromIntegral n


--- Plots ---


-- PixMap --

pixMapPlot :: (Double,Double) -> [[AlphaColour Double]] -> Plot Double Double
-- | Returns a pixmap representation of a matrix style set of doubles. Based on the
-- defaults, the list of colours are assumed to be in (y,x) coordinates, where the
-- origin is at the lower left of the image. If matrix style coordinates are desired,
-- The containing layout should be given a reversed y axis, so that the origin is at the
-- top left of the image.

--  The pair of doubles indicates where corner of the given image should be located.
pixMapPlot (x0,y0) pss = foldr1 joinPlot
    $ concat [ [ boxPlot r c . solidFillStyle $ p | (c,p) <- zip [x0..] ps ] | (r,ps) <- zip [y0..] pss ]
    where boxPlot y x stl = toPlot
              $ plot_fillbetween_style .~ stl
              $ plot_fillbetween_values .~ [(x-0.5,(y-0.5,y+0.5)),(x+0.5,(y-0.5,y+0.5))]
              $ def

pixMapLayout :: Int -> Int -> Layout Double Double -> Layout Double Double
-- | A nice base layout for a pixMap, with a box around the pixmap with one 'pixel'
-- padding, and a reversed y axis for matrix style coordinates.
pixMapLayout rws0 cls0 lyt =
    layout_top_axis_visibility .~ AxisVisibility True True False
    $ layout_left_axis_visibility .~ AxisVisibility True True False
    $ layout_right_axis_visibility .~ AxisVisibility True True False
    $ layout_bottom_axis_visibility .~ AxisVisibility True True False
    $ layout_y_axis . laxis_reverse .~ True
    $ layout_y_axis . laxis_generate .~
        const (makeAxis (const "") ([-1.5,rws+0.5],[-1.5,rws+0.5],[-1.5,rws+0.5]))
    $ layout_x_axis . laxis_generate .~
        const (makeAxis (const "") ([-1.5,cls+0.5],[-1.5,cls+0.5],[-1.5,cls+0.5]))
    $ lyt
    where cls = fromIntegral cls0
          rws = fromIntegral rws0

-- Histogram --

histogramPlot0 :: (Num a,BarsPlotValue a) =>Int -> [[Double]] -> PlotBars Double a -> PlotBars Double a
-- | Generates a histogram plot where the min and max bin value is taken from the data set.
histogramPlot0 n xss plt =
    let mx = maximum $ maximum <$> xss
        mn = minimum $ minimum <$> xss
    in histogramPlot n mn mx xss plt

logHistogramPlot0 :: Int -> [[Double]] -> PlotBars Double Double -> PlotBars Double Double
-- | Generates a histogram plot where the min and max bin value is taken from the data set.
logHistogramPlot0 n xss =
    let mx = maximum $ maximum <$> xss
        mn = minimum $ minimum <$> xss
    in logHistogramPlot n mn mx xss

histogramPlot
    :: (Num a,BarsPlotValue a)
    => Int -- ^ Number of bins
    -> Double -- ^ Min range
    -> Double -- ^ Max range
    -> [[Double]] -- ^ Data set
    -> PlotBars Double a -- ^ Plot
    -> PlotBars Double a -- ^ New Plot
-- | Creates a histogram out of a data set. The data set is a list of list of values, where
-- each sublist is a collection of data along an axis. Under and overflow is put into the
-- first and last bin, respectively. The bars are centered at the mid point between each
-- pair of bins.
histogramPlot n mn mx xss plt =
    let bns = range mn mx (n+1)
        vls = transpose $ toHistogram (tail $ take n bns) . sort <$> xss
        stp = (head (tail bns) - head bns) / 2
        bns' = (+ stp) <$> take n bns
     in plot_bars_alignment .~ BarsCentered $ plot_bars_values .~ zip bns' vls $ plt
    where toHistogram _ [] = repeat 0
          toHistogram [] xs = [genericLength xs]
          toHistogram (bn:bns') xs =
              let (hds,xs') = span (< bn) xs
              in genericLength hds : toHistogram bns' xs'

range _ _ 0 = []
range mn mx 1 = [(mn + mx) / 2]
range mn mx n =
    [ x * mx + (1 - x) * mn | x <- (/ (fromIntegral n - 1)) . fromIntegral <$> [0 .. n-1] ]

logHistogramPlot
    :: Int -- ^ Number of bins
    -> Double -- ^ Min range
    -> Double -- ^ Max range
    -> [[Double]] -- ^ Data set
    -> PlotBars Double Double -- ^ Plot
    -> PlotBars Double Double -- ^ New Plot
logHistogramPlot n mn mx xss plt =
    let bplt = histogramPlot n mn mx xss plt
        vls = modVals <$> bplt ^. plot_bars_values
        --cl = fromIntegral . ceiling . maximum . concat $ snd <$> vls
    in plot_bars_values .~ vls $ bplt
    where lbs = 10
          modVals (x,ys) = (x, logBase lbs . (+1) <$> ys)

histogramLayout :: BarsPlotValue a => PlotBars Double a -> Layout Double a -> Layout Double a
-- | The base layout for a histogram.
histogramLayout pbrs lyt =
    let bns = fst <$> pbrs ^. plot_bars_values
        stp = (head (tail bns) - head bns) / 2
        bns' = (head bns - stp) : map (+stp) bns
        rng = abs $ maximum bns'
        labelFun x
            | rng >= 1000 = showEFloat (Just 2) x ""
            | rng <= 0.01 = showEFloat (Just 2) x ""
            | otherwise = reverse . dropWhile (== '.') . dropWhile (== '0') . reverse $ showFFloat (Just 2) x ""
    in layout_x_axis . laxis_generate .~ const (makeAxis labelFun (bns',[],bns'))
       $ layout_plots %~ (plotBars pbrs:)
       $ lyt

logHistogramLayout :: PlotBars Double Double -> Layout Double Double -> Layout Double Double
-- | Base layout for a log-histogram.
logHistogramLayout pbrs lyt =
    let vls = pbrs ^. plot_bars_values
        bns = fst <$> vls
        stp = (head (tail bns) - head bns) / 2
        bns' = (head bns - stp) : map (+stp) bns
        cl = fromIntegral . ceiling . maximum . concat $ snd <$> vls
        rng = abs $ maximum bns'
        xLabelFun x
            | rng >= 1000 = showEFloat (Just 2) x ""
            | rng <= 0.01 = showEFloat (Just 2) x ""
            | otherwise = reverse . dropWhile (== '.') . dropWhile (== '0') . reverse $ showFFloat (Just 2) x ""
    in layout_plots %~ (plotBars pbrs:)
        $ layout_y_axis . laxis_generate .~ const (makeAxis yLabelFun ([0..cl],[],[0..cl]))
        $ layout_x_axis . laxis_generate .~ const (makeAxis xLabelFun (bns',[],bns'))
        $ lyt
    where lbs = 10
          yLabelFun 0 = show 0
          yLabelFun x = show (round lbs) ++ "e" ++ show (round x) ++ "-1"

histogramLayoutLR :: (BarsPlotValue a,PlotValue b) => PlotBars Double a -> LayoutLR Double a b -> LayoutLR Double a b
-- | The base layout for a histogramLR.
histogramLayoutLR pbrs lyt =
    let bns = fst <$> pbrs ^. plot_bars_values
        stp = (head (tail bns) - head bns) / 2
        bns' = (head bns - stp) : map (+stp) bns
        rng = abs $ maximum bns'
        labelFun x
            | rng >= 1000 = showEFloat (Just 2) x ""
            | rng <= 0.01 = showEFloat (Just 2) x ""
            | otherwise = reverse . dropWhile (== '.') . dropWhile (== '0') . reverse $ showFFloat (Just 2) x ""
    in layoutlr_x_axis . laxis_generate .~ const (makeAxis labelFun (bns',[],bns'))
       $ layoutlr_plots %~ (Left (plotBars pbrs):)
       $ lyt

--- IO ---


renderableToAspectWindow
    :: Bool -- ^ Display Full Screen
    -> Int -- ^ Image width
    -> Int -- ^ Image height
    -> Renderable a -- ^ The Renderable
    -> IO () -- ^ Renders the renderable to the screen

-- | Displays a renderable in a GTK aspect window.
renderableToAspectWindow fs wdth hght rnbl = do

    G.initGUI

    win <- G.windowNew
    afrm <- G.aspectFrameNew 0.5 0.5 . Just $ realToFrac (fromIntegral wdth / fromIntegral hght)
    da <- G.drawingAreaNew

    G.set afrm [ G.containerChild G.:= da ]
    G.set win [ G.containerChild G.:= afrm ]
    when fs $ G.windowFullscreen win
    G.onDestroy win G.mainQuit

    (da `G.on` G.exposeEvent) . liftIO $ do

        updateCanvas rnbl da
        return True

    G.widgetShowAll win
    G.mainGUI