{- |
Periodic clocks are defined by a stream of ticks with periodic time differences.
They model subclocks of a fixed reference clock.
The time differences are supplied at the type level.
-}

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeSynonymInstances #-}
module FRP.Rhine.Clock.Periodic (Periodic (Periodic)) where

-- base
import Control.Monad (forever)
import Data.List.NonEmpty hiding (unfold)
import Data.Maybe (fromMaybe)
import GHC.TypeLits (Nat, KnownNat, natVal)

-- dunai
import Data.MonadicStreamFunction

-- rhine
import FRP.Rhine.Clock
import Control.Monad.Schedule

-- * The 'Periodic' clock

-- | A clock whose tick lengths cycle through
--   a (nonempty) list of type-level natural numbers.
--   E.g. @Periodic '[1, 2]@ ticks at times 1, 3, 4, 5, 7, 8, etc.
--
--   The waiting side effect is formal, in 'ScheduleT'.
--   You can use e.g. 'runScheduleIO' to produce an actual delay.
data Periodic (v :: [Nat]) where
  Periodic :: Periodic (n : ns)

instance (Monad m, NonemptyNatList v)
      => Clock (ScheduleT Integer m) (Periodic v) where
  type Time (Periodic v) = Integer
  type Tag  (Periodic v) = ()
  initClock cl = return
    ( cycleS (theList cl) >>> withSideEffect wait >>> sumS &&& arr (const ())
    , 0
    )

-- * Type-level trickery to extract the type value from the singleton

data HeadClProxy (n :: Nat) where
  HeadClProxy :: Periodic (n : ns) -> HeadClProxy n

headCl :: KnownNat n => Periodic (n : ns) -> Integer
headCl cl = natVal $ HeadClProxy cl

tailCl :: Periodic (n1 : n2 : ns) -> Periodic (n2 : ns)
tailCl Periodic = Periodic

class NonemptyNatList (v :: [Nat]) where
  theList :: Periodic v -> NonEmpty Integer

instance KnownNat n => NonemptyNatList '[n] where
  theList cl = headCl cl :| []

instance (KnownNat n1, KnownNat n2, NonemptyNatList (n2 : ns))
      => NonemptyNatList (n1 : n2 : ns) where
  theList cl = headCl cl <| theList (tailCl cl)


-- * Utilities

-- TODO Port back to dunai when naming issues are resolved
-- | Repeatedly outputs the values of a given list, in order.
cycleS :: Monad m => NonEmpty a -> MSF m () a
cycleS as = unfold (second (fromMaybe as) . uncons) as

{-
-- TODO Port back to dunai when naming issues are resolved
delayList :: [a] -> MSF a a
delayList [] = id
delayList (a : as) = delayList as >>> delay a
-}