module GraphUtil (
        Format(..),
        plot, plotf,
        renderToFile,
        myAutoAxis,
        theAxis,
        theOtherAxis
    ) where

import StatUtil

import Bio.Util.Numeric             ( wilson )
import Data.List                    ( group, sort )
import Diagrams.Backend.SVG         ( renderSVG )
import Diagrams.Backend.Rasterific  ( renderRasterific )
import Diagrams.Prelude
import Diagrams_Rasterific          ( Rasterific(..), Options(RasterificOptions) )
import Graphics.Rasterific          ( renderDrawingAtDpiToPDF )
import Numeric                      ( showFFloat )

import qualified Data.ByteString.Lazy                       as B
import qualified Graphics.Rendering.Chart                   as C
import qualified Graphics.Rendering.Chart.Backend.Diagrams  as C

data Format = Txt | Svg | Pdf | Png

plotf :: FilePath -> Format -> String -> [Curve Ratio] -> IO ()
plotf fn format t cs = let Just p = plot myAutoAxis (-10,39) t cs
                       in renderToFile fn format defaultWidth defaultHeight p

toCairoStyle :: LineStyle -> Maybe C.LineStyle
toCairoStyle (Solid r g b) = Just $ C.solidLine 1 (opaque $ sRGB r g b)
toCairoStyle (Dashed p r g b) = Just $ C.dashedLine 1 p (opaque $ sRGB r g b)
toCairoStyle Hidden = Nothing

toBackgroundStyle :: LineStyle -> Maybe C.FillStyle
toBackgroundStyle (Solid r g b) = Just $ C.solidFillStyle $ sRGB r g b `withOpacity` 0.1
toBackgroundStyle (Dashed _ r g b) = Just $ C.solidFillStyle $ sRGB r g b `withOpacity` 0.05
toBackgroundStyle Hidden = Nothing

myAutoAxis :: C.AxisFn Double
myAutoAxis = mod_labels . C.autoScaledAxis def

theAxis :: Double -> C.AxisFn Double
theAxis y = mod_labels . const (C.autoScaledAxis def [0,y])

theOtherAxis :: Double -> C.AxisFn Double
theOtherAxis y = mod_labels . const (C.autoScaledAxis def [l,h])
  where l  = max 0 $ 0.25 - y
        h  = min 1 $ 0.25 + y

mod_labels :: C.AxisData Double -> C.AxisData Double
mod_labels = C.axis_labels %~ map (format' . map fst)
 where
    format' vs | ulength vs == length vs = format 1 vs
               | otherwise               = zip vs (tryFmt 2 vs)

    format n vs | ulength lbls == length lbls = zip vs lbls
                | otherwise                   = format (n+1) vs
      where lbls = tryFmt n vs

    ulength xs = length . group . sort $ xs
    tryFmt   n = map $ \v -> showFFloat (Just n) v []

plot :: C.AxisFn Double -> (Int,Int) -> String -> [Curve Ratio] -> Maybe (C.Renderable ())
plot yaxis (begs,ends) title curves
    | null (graph ^. C.layout_plots) = Nothing
    | otherwise                      = Just $ C.toRenderable graph
  where
    graph =
        C.layout_title .~ title $
        C.layout_y_axis . C.laxis_style . C.axis_label_style . C.font_size %~ (*2) $
        C.layout_x_axis . C.laxis_style . C.axis_label_style . C.font_size %~ (*2) $
        C.layout_y_axis . C.laxis_generate .~ yaxis $
        C.layout_plots .~ plots $ def

    plots = [ C.toPlot $ C.plot_fillbetween_title .~ fnname
                       $ C.plot_fillbetween_style .~ style
                       $ C.plot_fillbetween_values .~ values $ def
            | (Curve fnname style0 row, style1) <- zip curves line_styles
            , style <- maybe [] return . toBackgroundStyle $ maybe style1 id style0
            , let values = [ (fromIntegral x :: Double, case wilson 0.05 n d of (u,_,v) -> (u,v))
                           | x <- [begs..ends], (n,d) <- [row `atS` x], d > 32 ]
            , (_:_:_) <- [ values ] ] ++
            [ C.toPlot $ C.plot_lines_title .~ fnname
                       $ C.plot_lines_style .~ style
                       $ C.plot_lines_values .~ [ values ] $ def
            | (Curve fnname style0 row, style1) <- zip curves line_styles
            , style <- maybe [] return . toCairoStyle $ maybe style1 id style0
            , let values = [ (fromIntegral x :: Double, fromIntegral n / fromIntegral d :: Double)
                            | x <- [begs..ends], (n,d) <- [row `atS` x], d > 32 ]
            , (_:_:_) <- [ values ] ]


line_styles :: [ LineStyle ]
line_styles = [ solid_red, solid_green, solid_blue
              , Solid 0.7 0.7 0, Solid 1 0 1, Solid 0 1 1
              , Dashed [3,3] 1 0 0, Dashed [3,3] 0 1 0
              , Dashed [3,3] 0 0 1, Dashed [3,3] 0.7 0.7 0
              , Dashed [3,3] 1 0 1, Dashed [3,3] 0 1 1 ]


renderToFile :: FilePath -> Format -> Int -> Int -> C.Renderable () -> IO ()
renderToFile fp fmt x y r = do
    env <- C.defaultEnv C.vectorAlignmentFns (fromIntegral x) (fromIntegral y) :: IO (C.DEnv Double)
    let (w,h) = C.envOutputSize env
        cb    = C.render r (w,h)
    case fmt of
        Txt -> fail $ "renderToFile should not be called for Txt format"

        Pdf -> B.writeFile (fp ++ ".pdf") . renderDrawingAtDpiToPDF x y 600 .
               renderDia Rasterific (RasterificOptions (dims (V2 w h))) . fst $ C.runBackend env cb

        Png -> renderRasterific (fp ++ ".png") (dims (V2 w h)) . fst $ C.runBackend env cb

        Svg -> renderSVG (fp ++ ".svg") (dims (V2 w h)) . fst $ C.runBackend env cb

