{-# LANGUAGE ScopedTypeVariables #-}
-- | Simulate state machines using streams.
module Copilot.Library.StateMachines where

import Copilot.Language (Stream, Typed, constant, ifThenElse, (&&), (++), (==))
import Prelude          hiding ((&&), (++), (==))

-- | A definition of a state machine where some elements are defined
-- as streams.
--
-- A state machine is defined by an initial state, a final state, a no
-- transition stream (true when tere is no input coming in), a list of
-- transitions, and a bad state.
type StateMachine a = (a, a, Stream Bool, [(a, Stream Bool, a)], a)

-- | Produce a stream that, at any given time, contains the current state of
-- the state machine.
stateMachine :: forall a . (Eq a, Typed a) => StateMachine a -> Stream a
stateMachine :: forall a. (Eq a, Typed a) => StateMachine a -> Stream a
stateMachine (a
initial, a
final, Stream Bool
noInputData, [(a, Stream Bool, a)]
transitions, a
bad) = Stream a
state
  where
    state :: Stream a
state         = [(a, Stream Bool, a)] -> Stream a
ifThenElses [(a, Stream Bool, a)]
transitions
    previousState :: Stream a
previousState = [a
initial] [a] -> Stream a -> Stream a
forall a. Typed a => [a] -> Stream a -> Stream a
++ Stream a
state

    ifThenElses :: [(a, Stream Bool, a)] -> Stream a
    ifThenElses :: [(a, Stream Bool, a)] -> Stream a
ifThenElses [] =
      Stream Bool -> Stream a -> Stream a -> Stream a
forall a.
Typed a =>
Stream Bool -> Stream a -> Stream a -> Stream a
ifThenElse (Stream a
previousState Stream a -> Stream a -> Stream Bool
forall a. (Eq a, Typed a) => Stream a -> Stream a -> Stream Bool
== a -> Stream a
forall a. Typed a => a -> Stream a
constant a
final Stream Bool -> Stream Bool -> Stream Bool
&& Stream Bool
noInputData)
        (a -> Stream a
forall a. Typed a => a -> Stream a
constant a
final)
        (a -> Stream a
forall a. Typed a => a -> Stream a
constant a
bad)

    ifThenElses ((a
s1, Stream Bool
i, a
s2):[(a, Stream Bool, a)]
ss) =
      Stream Bool -> Stream a -> Stream a -> Stream a
forall a.
Typed a =>
Stream Bool -> Stream a -> Stream a -> Stream a
ifThenElse
        (Stream a
previousState Stream a -> Stream a -> Stream Bool
forall a. (Eq a, Typed a) => Stream a -> Stream a -> Stream Bool
== a -> Stream a
forall a. Typed a => a -> Stream a
constant a
s1 Stream Bool -> Stream Bool -> Stream Bool
&& Stream Bool
i)
        (a -> Stream a
forall a. Typed a => a -> Stream a
constant a
s2)
        ([(a, Stream Bool, a)] -> Stream a
ifThenElses [(a, Stream Bool, a)]
ss)

-- | Produce a stream that, at any given time, contains the current state of
-- the state machine as the numeric representation of an enum.
stateMachineEnum :: (Eq b, Typed b, Num b, Enum a)
                 => StateMachine a
                 -> Stream b
stateMachineEnum :: forall b a.
(Eq b, Typed b, Num b, Enum a) =>
StateMachine a -> Stream b
stateMachineEnum (a
initial, a
final, Stream Bool
noInputData, [(a, Stream Bool, a)]
transitions, a
bad) =
    StateMachine b -> Stream b
forall a. (Eq a, Typed a) => StateMachine a -> Stream a
stateMachine (a -> b
fe a
initial, a -> b
fe a
final, Stream Bool
noInputData, [(b, Stream Bool, b)]
transitionsE, a -> b
fe a
bad)
  where
    transitionsE :: [(b, Stream Bool, b)]
transitionsE = ((a, Stream Bool, a) -> (b, Stream Bool, b))
-> [(a, Stream Bool, a)] -> [(b, Stream Bool, b)]
forall a b. (a -> b) -> [a] -> [b]
map (\(a
s1, Stream Bool
t, a
s2) -> (a -> b
fe a
s1, Stream Bool
t, a -> b
fe a
s2)) [(a, Stream Bool, a)]
transitions
    fe :: a -> b
fe           = Int -> b
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int -> b) -> (a -> Int) -> a -> b
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> Int
forall a. Enum a => a -> Int
fromEnum