{-# LANGUAGE RankNTypes      #-}
{-# LANGUAGE RecordWildCards #-}

-- | Module containing renderable objects and plots for our maps
module GIS.Graphics.Plot ( mkMapR
                         , mkRenderablePlots
                         , mkRenderableLens
                         , mkRenderableLabelled
                         , plotDataPoints
                         , labelByLens
                         , plotLabels
                         ) where

import           Control.Arrow
import           Data.Colour.Names
import           Data.Foldable                 (fold)
import           GIS.Graphics.Types            hiding (title)
import           GIS.Types                     as GIS
import           GIS.Utils
import           Graphics.Rendering.Chart
import           Graphics.Rendering.Chart.Easy hiding (lens, points)

-- | Averages the coördinates of a polygon, returning a point.
shittyCentroid :: Polygon -> GIS.Point
shittyCentroid poly = (avg $ fmap fst poly, avg $ fmap snd poly)

-- | Average over a foldable container
avg :: (RealFrac a, Foldable t) => t a -> a
avg list = sum list / (fromIntegral . length $ list)

-- | Given a map, return a `Renderable ()` for use with the
-- """Graphics.Rendering.Char""" module.
mkMapR :: Map -> Renderable ()
mkMapR Map{..} = if _labelEntities
    then mkRenderableLabelled _title _labelledDistricts
    else mkRenderableLabelled _title $ second (pure "") <$> _labelledDistricts

mkRenderablePlots :: String -> [Plot Double Double] -> Renderable ()
mkRenderablePlots title plots = toRenderable $
    layout_title .~ title $ layout_plots .~ plots $ def

mkRenderableLens :: (Show a) => Lens' District a -> [District] -> String -> Renderable ()
mkRenderableLens lens districts title = mkRenderableLabelled title (labelByLens lens districts)

mkRenderableLabelled :: String -> [([Polygon], String)] -> Renderable ()
mkRenderableLabelled title points = mkRenderablePlots title [ plotDataPoints (fst =<< points), plotLabels points ]

-- | Helper function to plot data points as appropriate for a map, i.e. using
-- contiguous lines.
plotDataPoints :: [Polygon] -> Plot Double Double
plotDataPoints points = toPlot $
    plot_lines_values .~ points $ plot_lines_style . line_color .~ opaque blue $ plot_lines_title .~ "Border" $ def

-- | Set labels via a lens field.
labelByLens :: (Show a) => Lens' District a -> [District] -> [([Polygon], String)]
labelByLens lens districts = zip (fmap _shape districts) (fmap (show . view lens) districts)

-- | Helper function to plot labels on a map. The ordinate will be plotted at
-- the centroid of the abscissa, which may be outside the polygon if it is
-- concave.
plotLabels :: [([Polygon], String)] -> Plot Double Double
plotLabels points = toPlot texts
    where pairs = fmap (flatten . first (shittyCentroid . fold)) points
          fontStyle = font_size .~ 15 $ font_weight .~ FontWeightBold $ def
          texts = plot_annotation_values .~ pairs $ plot_annotation_style .~ fontStyle $ def