{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE Arrows #-}

-- | A set of examples of more complicated and problematic Churros.
-- 
module Churro.Test.Examples where

import Data.Map (fromList)
import Data.List

import Prelude hiding (id, (.))

import Control.Churro

-- $setup
-- 
-- >>> import System.Timeout     (timeout)
-- >>> import Control.Monad      (forever)
-- >>> import Control.Concurrent (threadDelay)

-- ** Tests

-- | Checks that the IO nature of the churros doesn't duplicate operations.
--   Actions within a pipeline should only occur once no matter how the
--   pipeline is composed.
--
-- >>> runWaitChan linear
-- Debugging [l1]: 1
-- Debugging [l2]: 1
-- Debugging [r1]: 1
-- Debugging [r2]: 1
-- 1 
--
linear :: Transport t => Churro t Void Void
linear = sourceList [1::Int]
    >>> ((processDebug "l1" >>> processDebug "l2") >>> processDebug "r1" >>> processDebug "r2")
    >>> sinkPrint

-- | A more complicated pipeline exampe involving maps.
-- 
-- >>> runWaitChan pipeline
-- (fromList [(0,0),(1,1)],fromList [(1,1),(2,2)])
-- (fromList [(1,1),(2,2)],fromList [(2,2),(3,3)])
pipeline :: ChurroChan Void Void
pipeline = sourceList (take 3 maps)
        >>> withPrevious
        >>> takeC (10 :: Int)
        >>> sinkPrint
    where
    maps    = map fromList $ zipWith zip updates updates
    updates = map (take 2) (tails [0 :: Int ..])

-- | Consumers terminaiting should kill sources from producing.
-- 
-- This can fail in the following scenarios if cancellation isn't implemented correctly:
-- 
-- >>> timeout 1500000 $ runWaitChan $ sourceList [1..5] >>> delay 1 >>> takeC 1 >>> sinkPrint
-- 1
-- Just ()
-- 
-- Cancells infinite producer with inbuilt delays:
-- 
-- >>> timeout 1500000 $ runWaitChan $ sourceIO (\cb -> forever (cb 1 >> threadDelay 100000)) >>> takeC 1 >>> sinkPrint
-- 1
-- Just ()
-- 
-- Cancells upstream infinite producer with no inbuilt delay:
-- 
-- >>> timeout 2500000 $ runWaitChan $ sourceList [1..] >>> delay 1 >>> takeC 1 >>> sinkPrint
-- 1
-- Just ()
-- 
-- Note that the timeout here has to be sufficient for a thread switch to occor and the
-- action be cancelled! See what happens if the timeout is only 1.5s:
-- 
-- >>> timeout 1500000 $ runWaitChan $ sourceList [1..] >>> delay 1 >>> takeC 1 >>> sinkPrint
-- 1
-- Nothing
-- 
-- What should happen is that the Category instance composition of:
-- 
--  ...  delay 1 >>> takeC 1  ...
--  ^^^ PRE ^^^^     ^^^ POST ^^^
-- 
-- When POST terminates it should cancel the computation in PRE.
-- 
-- Failures may be caused by one of the following:
-- 
-- * Nested Async actions don't cascade on cancellation (incorrect finally clauses)
-- * The associativity laws of Category are broken, meaning that cancellation doesn't behave as it should.
-- * Producer blocks cancellation from being requested
-- * Chan is blocked preventing indicating termination to consumers
-- * Infinite source is causing issues (ruled out with this test example)
-- 
-- This is currently working, but tests here should check that the implementation
-- isn't broken.