{-# language OverloadedStrings #-}
{-# language FlexibleContexts #-}
module Graphics.Rendering.Plot.Light.PlotTypes.TimeSeries where

import Control.Monad (forM_)

-- import GHC.Real
import Data.Fixed (Pico)
import Data.Time

-- import Data.Scientific

import qualified Data.Text as T

import Graphics.Rendering.Plot.Light.Internal
-- import Graphics.Rendering.Plot.Light.Internal.Utils

-- For debugging
import Text.Blaze.Svg
import qualified Data.Colour as C
import qualified Data.Colour.Names as C
-- import Text.Blaze.Svg.Renderer.String (renderSvg)



-- | Compute the plotting coordinates of a timeseries point

-- | Preprocess the dataset for plotting 
-- 1. Remap the figure data to fit within the FigData ranges, expressed in pixels
-- 2. Flip the data along the y axis since the origin in SVG is the top-left corner of the screen


  
-- tsAxis
--   :: (Functor t, Foldable t, Show a, RealFrac a) =>
--      FigureData a
--      -> a
--      -> C.Colour Double
--      -> C.Colour Double
--      -> a
--      -> Maybe (t (LabeledPoint String a))
--      -> Maybe (t (LabeledPoint String a))
--      -> t (LabeledPoint String a)
--      -> Svg
-- tsAxis fd sw colAxis colData rot plabx plaby ps =
--   toPlot fd T.pack T.pack rot 0 sw colAxis plabx plaby fplot ps where
--     fplot lps = polyline  sw Continuous Round colData (_lp <$> lps)
  
tsAxis
  :: (Functor t, Foldable t, Show a, RealFrac a) =>
     FigureData a
     -> (l -> a)
     -> (l -> a)
     -> (l -> a)
     -> (l -> a)
     -> a
     -> C.Colour Double
     -> a
     -> Maybe (t (LabeledPoint l a))
     -> Maybe (t (LabeledPoint l a))
     -> t (LabeledPoint l a)
     -> Svg
tsAxis fd fboxmin fboxmax fmin fmax sw colAxis rot plabx plaby ps =
  toPlot fd baz baz rot 0 sw colAxis plabx plaby fplot ps where
    from = frameFromPoints $ _lp <$> ps
    to = frameFromFigData fd
    fdat = frameToFrameValue from to
    baz = const (T.pack "")
    fplot lps =
      forM_ lps (candlestick (>) fboxmin' fboxmax' fmin' fmax' 5 1 colUp colDown colAxis)
    colUp = shapeColBoth C.green colAxis 1 sw
    colDown = shapeColBoth C.red colAxis 1 sw
    fboxmin' = fdat . fboxmin
    fboxmax' = fdat . fboxmax
    fmin' = fdat . fmin
    fmax' = fdat . fmax



tsAxis' figdata fmin fmax col lps =
  toPlot figdata l0 l0 (-45) 0 5 C.black Nothing Nothing fplot lps where
  l0 = const (T.pack "")
  from = frameFromPoints $ _lp <$> lps
  to = frameFromFigData figdata
  fdat = frameToFrameValue from to
  fplot = filledBand col 0.4 fmax' fmin'
  fmax' = fdat . fmax
  fmin' = fdat . fmin





-- | An instant, defined by date (Day) and TimeOfDay
data Tick = Tick Day TimeOfDay
  deriving (Eq, Show, Ord)

-- | Create a Tick from valid (year, month, day, hour, minute, second)
mkTick :: Integer -> Int -> Int -> Int -> Int -> Pico -> Maybe Tick
mkTick yy mm dd hr mi se = do
   tim <- makeTimeOfDayValid hr mi se
   let d = fromGregorian yy mm dd
   return $ Tick d tim

-- | A point in a time series
data TsPoint a =
  Tsp {
    _tick :: Tick,
    _val :: a
    } deriving (Eq, Show)

tickToFractional :: Fractional b => TsPoint a -> b
tickToFractional = fromRational . fromTick . _tick

-- | Map a Tick onto the rationals
fromTick :: Tick -> Rational
fromTick (Tick d t) = fromIntegral (toModifiedJulianDay d) + timeOfDayToDayFraction t
    
-- | Map a rational onto a Tick
toTick :: Rational -> Tick
toTick n = Tick d t
  where
    t = dayFractionToTimeOfDay dec
    d = ModifiedJulianDay wh
    (wh, dec) = wholeDecimal n


hourTick, halfHourTick, quarterHourTick :: Rational
hourTick = 1/24
halfHourTick = 1/2 * hourTick
quarterHourTick = 1/4 * hourTick


-- locTime :: IO LocalTime
-- locTime = do
--   tz <- getCurrentTimeZone
--   ct <- getCurrentTime
--   return $ utcToLocalTime tz ct


-- tickRange :: Tick -> Tick -> Rational -> [Tick]
-- tickRange t1 t2 dt = toTick <$> [td1, td1 + dt .. td2] where
--   td1 = fromTick t1
--   td2 = fromTick t2




-- * Helpers

-- | Create a `LabeledPoint` from a time series point (`TsPoint`). The `_tick` (time axis) field will be used for the x coordinate, whereas both fields of TsPoint may be used to create the label field.
--
-- NB : The coordinates of the resulting LabelPoint still live in the original data space; they must be rescaled to fit in the figure viewport
tspToLP :: Fractional a => 
     (t -> a)
  -> (Tick -> t -> l)
  -> TsPoint t
  -> LabeledPoint l a
tspToLP fy g = LabeledPoint <$> pf <*> lf where
  pf = Point <$> tickToFractional <*> fy . _val
  lf = g <$> _tick <*> _val
  
  



labeledTsPointRange n p t1 q dt = zipWith LabeledPoint p_ t_
  where
    t_ = toTick <$> [td1, td1 + dt .. ]
    p_ = pointRange n p q
    td1 = fromTick t1







frameToFrameFxRow from to fxr = f <$> fxr
  where
    f = frameToFrameValue from to


data FxRow a  = FxRow {
    rateOpen :: a
  , rateHigh :: a
  , rateLow :: a
  , rateClose :: a
               } deriving (Eq, Show)

instance Functor FxRow where
  fmap f (FxRow o h l c) = FxRow (f o) (f h) (f l) (f c)

c1 = FxRow 1.0876 1.0880 1.0872 1.0874



-- test data

-- svg1 = renderSvg $ tsAxis fdat1 2 C.black C.red (-45) (Just ptx) (Just pty) dat1

-- svg2 = renderSvg $ tsAxis fdat1 2 C.black C.red (-45) Nothing (Just pty) dat1


fdat1 = FigureData 400 300 0.1 0.9 0.1 0.85 10

dat1 :: [ LabeledPoint String Double ]
dat1 = [LabeledPoint (Point 0 0) "blah",
        LabeledPoint (Point 0 1) "asdf",
        LabeledPoint (Point 1 1) "yo",
        LabeledPoint (Point 1 2) "blap",
        LabeledPoint (Point 2 2) "chow"]

ptx = labelPoint (show . _px) <$> pointRange 2 (Point 0 0) (Point 2 0)
pty = labelPoint (show . _py) <$> pointRange 2 (Point 0 0) (Point 0 2)

to, from :: Frame Double
from = frameFromPoints $ _lp <$> dat1
to = mkFrameOrigin 400 300





-- nth xss n = reverse $ go xss n [] where
--   go (_:xs) i l | i>0  = go xs (i-1) l
--   go (x:xs) i l | i==0 = go xs n (x : l)
--   go [] _ l = l

-- tsp1 :: Maybe (TsPoint (FxRow Double))
-- tsp1 = Tsp <$> mkTick 2017 16 3 20 30 01 <*> Just (FxRow pi 20 10 5.4)