{-# LANGUAGE TemplateHaskell, DeriveDataTypeable #-}
{-# OPTIONS_GHC -Wall #-}

-- | XXX test doesn't work, because failure exceptions don't get propagated. The
-- test always claims to succeed, even if it failed.

module Control.Distributed.Process.Tests.Receive (tests) where

import Network.Transport.Test (TestTransport(..))

import Network.Transport (Transport)
import Control.Distributed.Process
import Control.Distributed.Process.Closure
import Control.Distributed.Process.Node

import Control.Monad
import Text.Printf
import Data.Binary
import Data.Typeable

import Test.HUnit (Assertion, (@?=))
import Test.Framework (Test, defaultMain)
import Test.Framework.Providers.HUnit (testCase)

-- Tests:

-- 1. 2 matchChans, receive on each one
-- 2. matchChan/matchIf, receive on each one
-- 3. matchIf/matchChan, receive on each one
-- 4. matchIf/matchChan/matchIf, receive on each one

recTest1 :: ReceivePort ()
         -> SendPort String
         -> ReceivePort String -> ReceivePort String
         -> Process ()
recTest1 wait sync r1 r2 = do
  forever $ do
    receiveChan wait
    r <- receiveWait
      [ matchChan r1       $ \s -> return ("received1 " ++ s)
      , matchChan r2       $ \s -> return ("received2 " ++ s)
      ]
    sendChan sync r

recTest2 :: ReceivePort ()
         -> SendPort String
         -> ReceivePort String -> ReceivePort String
         -> Process ()
recTest2 wait sync r1 r2 = do
  forever $ do
    receiveChan wait
    r <- receiveWait
      [ matchChan r1       $ \s -> return ("received1 " ++ s)
      , matchIf (== "foo") $ \s -> return ("received2 " ++ s)
      ]
    sendChan sync r

recTest3 :: ReceivePort ()
         -> SendPort String
         -> ReceivePort String -> ReceivePort String
         -> Process ()
recTest3 wait sync r1 r2 = do
  forever $ do
    receiveChan wait
    r <- receiveWait
      [ matchIf (== "foo") $ \s -> return ("received1 " ++ s)
      , matchChan r1       $ \s -> return ("received2 " ++ s)
      ]
    sendChan sync r

recTest4 :: ReceivePort ()
         -> SendPort String
         -> ReceivePort String -> ReceivePort String
         -> Process ()
recTest4 wait sync r1 r2 = do
  forever $ do
    receiveChan wait
    r <- receiveWait
      [ matchIf (== "foo") $ \s -> return ("received1 " ++ s)
      , matchChan r1       $ \s -> return ("received2 " ++ s)
      , matchIf (== "bar") $ \s -> return ("received3 " ++ s)
      ]
    sendChan sync r

master :: Process ()
master = do
  (waits,waitr) <- newChan
  (syncs,syncr) <- newChan
  let go expect = do
         sendChan waits ()
         r <- receiveChan syncr
         liftIO $ print (r,expect, r == expect)
         liftIO $ r @?= expect

  liftIO $ putStrLn "---- Test 1 ----"
  (s1,r1) <- newChan
  (s2,r2) <- newChan
  p <- spawnLocal (recTest1 waitr syncs r1 r2)

  sendChan s1 "a" >> go "received1 a"
  sendChan s2 "b" >> go "received2 b"
  sendChan s1 "a" >>  sendChan s2 "b" >>  go "received1 a"
  go "received2 b"

  kill p "BANG"

  liftIO $ putStrLn "\n---- Test 2 ----"
  (s1,r1) <- newChan
  (s2,r2) <- newChan
  p <- spawnLocal (recTest2 waitr syncs r1 r2)

  sendChan s1 "a" >> go "received1 a"
  send p "foo" >> go "received2 foo"
  sendChan s1 "a" >> send p "foo" >> go "received1 a"
  sendChan s1 "a" >> send p "bar" >> go "received1 a"
  go "received2 foo"

  kill p "BANG"

  liftIO $ putStrLn "\n---- Test 3 ----"
  (s1,r1) <- newChan
  (s2,r2) <- newChan
  p <- spawnLocal (recTest3 waitr syncs r1 r2)

  sendChan s1 "a" >> go "received2 a"
  send p "foo" >> go "received1 foo"
  sendChan s1 "a" >> send p "foo" >> go "received1 foo"
  sendChan s1 "a" >> send p "bar" >> go "received2 a"
  go "received2 a"

  kill p "BANG"

  liftIO $ putStrLn "\n---- Test 4 ----"
  (s1,r1) <- newChan
  (s2,r2) <- newChan
  p <- spawnLocal (recTest4 waitr syncs r1 r2)

  sendChan s1 "a" >> go "received2 a"
  send p "foo" >> go "received1 foo"
  send p "bar" >> go "received3 bar"
  sendChan s1 "a" >> send p "foo" >> go "received1 foo"
  send p "bar" >> go "received2 a"
  send p "foo" >> go "received1 foo" >> go "received3 bar"

  kill p "BANG"

  terminate

testReceive :: Transport -> RemoteTable -> Assertion
testReceive transport rtable = do
  node <- newLocalNode transport rtable
  runProcess node $ master

tests :: TestTransport -> IO [Test]
tests TestTransport{..} = do
    let rtable = initRemoteTable
    return
        [ testCase "testReceive" (testReceive testTransport rtable) ]