{-|
    This module is about continuous signals.

    For a general introduction to signals, see the documentation of "FRP.Grapefruit.Signal".
-}
module FRP.Grapefruit.Signal.Continuous (

    -- * Continuous signal type
    CSignal,

    -- * Conversion
    fromSSignal,

    -- * Connectors
    producer

) where

    -- Control
    import Control.Applicative as Applicative
#if __GLASGOW_HASKELL__ >= 610
    import Control.Arrow       as Arrow
#else
    import Control.Arrow       as Arrow   hiding (pure)
#endif
    import Control.Compose     as Compose

    -- Data
    import Data.Unique as Unique

    -- Internal
    import           Internal.Capsule                   as Capsule
    import           Internal.CSeg                      as CSeg    hiding (producer)
    import qualified Internal.CSeg                      as CSeg
    import           Internal.Signal                    as Signal
    import           Internal.Signal.Discrete (DSignal)
    import qualified Internal.Signal.Discrete           as DSignal
    import           Internal.Signal.Segmented          as SSignal

    -- Internal
    import Internal.Circuit as Circuit

    -- * Continuous signal type
    {-|
        The type of continuous signals.

        A continuous signal denotes a mapping from times to values. You can think of @CSignal /era/
        /val/@ as being equivalent to @Time /era/ -> /val/@ where @Time /era/@ is the type of all
        times of the given era.

        Continuous signals are used to describe continuously changing values. They are also used for
        values changing at discrete times if there is no possibility of being notified about such
        changes. If there is a notification mechanism then segemented signals, provided by
        "FRP.Grapefruit.Signal.Segmented", should be used.
    -}
    data CSignal era val = CSignal (Capsule val) !(SSignal era (CSeg val))
    {-
        The strictness annotation ensures that reducing the CSignal reduces the SSignal, thereby
        triggering reading of continous sources the SSignal depends on.
    -}

    instance Functor (CSignal era) where

        fmap fun (CSignal initCap segs) = CSignal (fmap fun initCap) ((fmap . fmap) fun segs)

    instance Applicative (CSignal era) where

        pure val = CSignal (pure val) ((pure . pure) val)

        CSignal funInitCap funSegs <*> CSignal argInitCap argSegs = CSignal initCap' segs' where

            initCap' = funInitCap <*> argInitCap

            segs'    = liftA2 (<*>) funSegs argSegs

    instance Signal CSignal where

        osfSwitch signal@(SSignal init _) = CSignal (initCap init) segs' where

            segs' = osfSwitch (segsSignal signal)

        ssfSwitch (SSignal init upd) (CSignal initCap segs) = ssfSwitch sampler segs where

            sampler = polySSignal (fixInitCapForInit init initCap)
                                  (polyTimeIDApp (fixInitCapForUpd <$> upd) <#> segs)

    initCap :: CSignal era val -> Capsule val
    initCap (CSignal initCap _) = initCap

    segsSignal :: SSignal era (forall era'. CSignal era' val)
               -> SSignal era (forall era'. SSignal era' (CSeg val))
    segsSignal = fmap polySegs

    polySegs :: (forall era'. CSignal era' val) -> (forall era'. SSignal era' (CSeg val))
    polySegs signal = segs signal

    segs :: CSignal era' val -> SSignal era' (CSeg val)
    segs (CSignal _ segs) = segs

    polySSignal :: (forall era'. SSignal era' (CSeg val) -> SignalFun era' shape)
                -> DSignal era (forall era'. SSignal era' (CSeg val) -> SignalFun era' shape)
                -> SSignal era (forall era'. SSignal era' (CSeg val) -> SignalFun era' shape)
    polySSignal init upd = SSignal init upd

    fixInitCapForInit :: (forall era'. CSignal era' val -> signalFun era' shape)
                      -> Capsule val
                      -> (forall era'. SSignal era' (CSeg val) -> signalFun era' shape)
    fixInitCapForInit fun initCap segs = fun (CSignal initCap segs)

    fixInitCapForUpd :: (forall era'. CSignal era' val -> signalFun era' shape)
                     -> Unique
                     -> CSeg val
                     -> (forall era'. SSignal era' (CSeg val) -> signalFun era' shape)
    fixInitCapForUpd fun timeID initSeg segs = fun (CSignal (currentValCapsule timeID initSeg) segs)

    polyTimeIDApp :: DSignal era (Unique ->
                                  CSeg val ->
                                  forall era'. SSignal era' (CSeg val) -> SignalFun era' shape)
                  -> DSignal era (CSeg val ->
                                  forall era'. SSignal era' (CSeg val) -> SignalFun era' shape)
    polyTimeIDApp signal = DSignal.timeIDApp signal

    instance Samplee CSignal where

        dSample sampler (CSignal _ segs) = (DSignal.crackCapsules . DSignal.timeIDApp) $
                                           timeIDToCapsule <$> sampler <#> segs where

            timeIDToCapsule fun seg = fmap fun . flip currentValCapsule seg

        sSample (SSignal samplerInit samplerUpd) signal@(CSignal (Capsule init) _) = signal' where

            signal' = SSignal (samplerInit init) (samplerUpd <#> signal)

    -- * Conversion
    {-|
        Converts a segmented signal into a continous signal, dropping the information about update
        points.
    -}
    fromSSignal :: SSignal era val -> CSignal era val
    fromSSignal signal@(SSignal init _) = CSignal (pure init) (fmap pure signal)

    -- * Connectors
    {-|
        Converts a value read action into a continuous signal producer.

        The producer @producer /readVal/@ produces a continuous signal whose current value is
        determined by executing @/readVal/@.
    -}
    producer :: IO val -> Producer CSignal val
    producer readVal = Producer $
                       proc _ -> do
                           seg <- CSeg.producer readVal -< ()
                           startTimeID <- getStartTimeID -< ()
                           returnA -< CSignal (currentValCapsule startTimeID seg) (pure seg)