module Data.MediaBus.Series
    ( Series(..)
    , type Series'
    , _Next
    , _Start
    , AsSeries(..)
    , AsSeriesStart(..)
    , AsSeriesNext(..)
    , StartingFrom(..)
    , startingFromValue
    , toNextsC'
    , toStartsC'
    , monotoneSeriesC
    ) where

import           Control.Lens
import           Conduit
import           Control.Monad.Reader
import           Test.QuickCheck
import           Data.Bifunctor
import           GHC.Generics         ( Generic )
import           Control.DeepSeq

class (SetSeriesStart s (GetSeriesStart s) ~ s) =>
      AsSeriesStart s where
    type GetSeriesStart s
    type SetSeriesStart s t
    seriesStart :: Prism s (SetSeriesStart s n) (GetSeriesStart s) n

class (SetSeriesNext s (GetSeriesNext s) ~ s) =>
      AsSeriesNext s where
    type GetSeriesNext s
    type SetSeriesNext s t
    seriesNext :: Prism s (SetSeriesNext s n) (GetSeriesNext s) n

class AsSeries s a b | s -> a, s -> b where
    seriesStart' :: Prism' s a
    seriesNext' :: Prism' s b

instance AsSeries (Either a b) a b where
    seriesStart' = _Left
    seriesNext' = _Right

instance AsSeriesStart (Either a b) where
    type GetSeriesStart (Either a b) = a
    type SetSeriesStart (Either a b) n = (Either n b)
    seriesStart = _Left

instance AsSeriesNext (Either a b) where
    type GetSeriesNext (Either a b) = b
    type SetSeriesNext (Either a b) n = (Either a n)
    seriesNext = _Right

data Series a b = Next { _seriesValue :: !b }
                | Start { _seriesStartValue :: !a }
    deriving (Eq, Generic)

instance (NFData a, NFData b) =>
         NFData (Series a b)

instance (Show a, Show b) =>
         Show (Series a b) where
    show (Start !x) = "(START: " ++ show x ++ ")"
    show (Next !x) = show x

instance (Ord a, Ord b) =>
         Ord (Series a b) where
    compare (Next !l) (Next !r) =
        compare l r
    compare _ _ = EQ

type Series' a = Series a a

instance (Arbitrary a, Arbitrary b) =>
         Arbitrary (Series a b) where
    arbitrary = do
        isNext <- choose (0.0, 1.0)
        if isNext < (0.95 :: Double)
            then Next <$> arbitrary
            else Start <$> arbitrary

makePrisms ''Series

instance AsSeries (Series a b) a b where
    seriesNext' = _Next
    seriesStart' = _Start

instance AsSeriesNext (Series a b) where
    type GetSeriesNext (Series a b) = b
    type SetSeriesNext (Series a b) n = (Series a n)
    seriesNext = _Next

instance AsSeriesStart (Series a b) where
    type GetSeriesStart (Series a b) = a
    type SetSeriesStart (Series a b) n = (Series n b)
    seriesStart = _Start

instance Functor (Series a) where
    fmap = over _Next

instance Bifunctor Series where
    first = over _Start
    second = over _Next

newtype StartingFrom a = MkStartingFrom { _startingFromValue :: a }
    deriving (Eq, Ord, Arbitrary)

makeLenses ''StartingFrom

instance Show a =>
         Show (StartingFrom a) where
    show (MkStartingFrom !x) =
        "(STARTING-FROM: " ++ show x ++ ")"

toNextsC' :: Monad m => Conduit (Series a b) m b
toNextsC' = awaitForever go
  where
    go (Start !_a) = return ()
    go (Next !b) = yield b

toStartsC' :: Monad m => Conduit (Series a b) m a
toStartsC' = awaitForever go
  where
    go (Start !a) = yield a
    go (Next !_b) = return ()

monotoneSeriesC :: Monad m => m a -> (i -> m b) -> Conduit i m (Series a b)
monotoneSeriesC !initSeries !continueSeries = do
    !rStart <- lift initSeries
    yield (Start rStart)
    !mi <- await
    mapM_ (lift . continueSeries >=>
               yield . Next >=>
                   const (awaitForever (lift . continueSeries >=> yield . Next)))
          mi