-- | `Automation` examples. View source for the code.
--
-- These examples are tested by doctest when building this library.
--
-- Patches adding examples welcomed!
module Reactive.Banana.Automation.Examples where

import Reactive.Banana
import Reactive.Banana.Frameworks
import Reactive.Banana.Automation
import Data.Time.Clock.POSIX

-- | We'll use a single Sensors type containing all the sensors
-- used by the examples below.
data Sensors = Sensors
	{ fridgeTemperature :: EventSource (Sensed Double)
	, motionSensor :: EventSource (Sensed (Timestamped POSIXTime Bool))
	}

-- | And a single Actuators type containing all the actuators used by the
-- examples below.
data Actuators
	= FridgePower PowerChange
	| LightSwitch PowerChange
	deriving (Show)

-- | For running the examples, you'll need this, to construct a `Sensors`
mkSensors :: IO Sensors
mkSensors = Sensors
	<$> newAddHandler
	<*> newAddHandler

-- | A fridge, containing the `fridgeTemperature` sensor and with
-- its power controlled by the `FridgePower` actuator.
--
-- The fridge starts running when its temperature exceeds a maximum
-- safe value. Once the temperature falls below a minimim value, the fridge
-- stops running. Note that opening the door of this fridge for a minute
-- typically won't cause it to run, unless it was already close to being
-- too warm. This behavior was chosen to minimise starts of the compressor,
-- but of course other fridge behaviors are also possible; this is only an
-- example.
--
-- To give this example a try, import this module in ghci and run:
--
-- >>> runner <- observeAutomation fridge mkSensors
-- >>> runner $ \sensors -> fridgeTemperature sensors =: 6
-- [FridgePower PowerOn]
-- >>> runner $ \sensors -> fridgeTemperature sensors =: 3
-- []
-- >>> runner $ \sensors -> fridgeTemperature sensors =: 0.5
-- [FridgePower PowerOff]
fridge :: Automation Sensors Actuators
fridge sensors actuators = do
	-- Create a Behavior that reflects the most recently reported
	-- temperature of the fridge.
	btemperature <- sensedBehavior (fridgeTemperature sensors)
	-- Calculate when the fridge should turn on and off.
	let bpowerchange = calcpowerchange <$> btemperature
	onBehaviorChangeMaybe bpowerchange (actuators . FridgePower)
  where
	calcpowerchange (Sensed temp)
		| temp `belowRange` allowedtemp = Just PowerOff
		| temp `aboveRange` allowedtemp = Just PowerOn
		| otherwise = Nothing
	calcpowerchange SensorUnavailable = Nothing
	allowedtemp = Range 1 4

-- | A light that comes on when the `motionSensor` detects movement,
-- and remains on for 5 minutes after the last movement.
--
-- If this were run in real code, the motion sensor would be triggered
-- by running `sensedNow`.
--
-- But, for testing, it's useful to specify the time that the sensor
-- is triggered, using `sensedAt`. Import this module in ghci and run:
--
-- >>> runner <- observeAutomation motionActivatedLight mkSensors
-- >>> runner $ \sensors -> sensedAt 0 (motionSensor sensors) True
-- [LightSwitch PowerOn]
-- >>> runner $ \sensors -> sensedAt 30 (motionSensor sensors) False
-- []
-- >>> runner $ \sensors -> sensedAt 60 (motionSensor sensors) True
-- [LightSwitch PowerOn]
-- >>> runner $ \sensors -> sensedAt 120 (motionSensor sensors) False
-- []
-- >>> runner $ \sensors -> sensedAt 400 (motionSensor sensors) False
-- [LightSwitch PowerOff]
motionActivatedLight :: Automation Sensors Actuators
motionActivatedLight sensors actuators = do
	-- Make an Event that contains the time elapsed since the last
	-- detected motion.
	timesincemotion <- elapsedTimeSince (== True)
		=<< sensedEvent (motionSensor sensors)
	-- Make a Behavior for the light switch.
	lightchange <- stepper Nothing $ calcchange <$> timesincemotion
	onBehaviorChangeMaybe lightchange (actuators . LightSwitch)
  where
	calcchange t
		| t == 0 = Just PowerOn -- motion was just detected
		| t > 300 = Just PowerOff -- 5 minutes since last motion
		| otherwise = Nothing