-- Communicating Haskell Processes.
-- Copyright (c) 2008--2009, University of Kent.
-- All rights reserved.
-- 
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are
-- met:
--
--  * Redistributions of source code must retain the above copyright
--    notice, this list of conditions and the following disclaimer.
--  * Redistributions in binary form must reproduce the above copyright
--    notice, this list of conditions and the following disclaimer in the
--    documentation and/or other materials provided with the distribution.
--  * Neither the name of the University of Kent nor the names of its
--    contributors may be used to endorse or promote products derived from
--    this software without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
-- IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
-- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

-- | A module of operators for connecting processes together.
--
-- This whole module was added in version 1.7.0.
module Control.Concurrent.CHP.Connect
  (Connectable(..), (<=>), (|<=>), (<=>|), (|<=>|), pipelineConnect, pipelineConnectComplete,
    cycleConnect, ConnectableExtra(..), connectWith) where

import Control.Applicative
import Control.Arrow

import Control.Concurrent.CHP

-- | Like 'Connectable', but allows an extra parameter.
--
-- The API (and name) for this is still in flux, so do not rely on it just yet.
class ConnectableExtra l r where
  type ConnectableParam l
  -- | Runs the given code with the two items connected.
  connectExtra :: ConnectableParam l -> ((l, r) -> CHP ()) -> CHP ()

-- | Indicates that its two parameters can be joined together automatically.
--
-- Rather than use 'connect' directly, you will want to use the operators such
-- as '(<=>)'.  There are different forms of this operator for in the middle of
-- a pipeline (where you still need further parameters to each process), and at
-- the ends.  See also 'pipelineConnect' and 'pipelineConnectComplete'.
class Connectable l r where
  -- | Runs the given code with the two items connected.
  connect :: ((l, r) -> CHP ()) -> CHP ()

-- | Joins together the given two processes and runs them in parallel.
(|<=>|) :: Connectable l r => (l -> CHP ()) -> (r -> CHP ()) -> CHP ()
(|<=>|) p q = connect $ \(x, y) -> p x <|*|> q y

jpo :: ConnectableExtra l r => ConnectableParam l -> (l -> CHP ()) -> (r -> CHP ()) -> CHP ()
jpo o p q = connectExtra o $ \(x, y) -> p x <|*|> q y

-- | Joins together the given two processes and runs them in parallel.
(<=>) :: Connectable l r => (a -> l -> CHP ()) -> (r -> b -> CHP ()) -> a -> b -> CHP ()
(<=>) p q x y = p x |<=>| flip q y

-- | Joins together the given two processes and runs them in parallel.
(<=>|) :: Connectable l r => (a -> l -> CHP ()) -> (r -> CHP ()) -> a -> CHP ()
(<=>|) p q x = p x |<=>| q

-- | Joins together the given two processes and runs them in parallel.
(|<=>) :: Connectable l r => (l -> CHP ()) -> (r -> b -> CHP ()) -> b -> CHP ()
(|<=>) p q x = p |<=>| flip q x

-- | Like '(<=>)' but with 'ConnectableExtra'
connectWith :: ConnectableExtra l r => ConnectableParam l ->
  (a -> l -> CHP ()) -> (r -> b -> CHP ()) -> a -> b -> CHP ()
connectWith o p q x y = jpo o (p x) (flip q y)

-- | Like @foldl1 (<=>)@; connects a pipeline of processes together.  If the list
-- is empty, it returns a process that ignores both its arguments and returns instantly.
pipelineConnect :: Connectable l r => [r -> l -> CHP ()] -> r -> l -> CHP ()
pipelineConnect [] = const . const $ return ()
pipelineConnect [p] = p
pipelineConnect (p:ps) = p <=> pipelineConnect ps

-- | Connects the given beginning process, the list of middle processes, and
-- the end process into a pipeline and runs them all in parallel.  If the list
-- is empty, it connects the beginning directly to the end.
pipelineConnectComplete :: Connectable l r =>
  (l -> CHP ()) -> [r -> l -> CHP ()] -> (r -> CHP ()) -> CHP ()
pipelineConnectComplete begin [] end = begin |<=>| end
pipelineConnectComplete begin middle end
  = (begin |<=> pipelineConnect middle) |<=>| end

-- | Like 'pipelineConnect' but also connects the last process into the first.
--  If the list is empty, it returns immediately.
cycleConnect :: Connectable l r => [r -> l -> CHP ()] -> CHP ()
cycleConnect [] = return ()
cycleConnect ps = connect . uncurry . flip . pipelineConnect $ ps

instance Connectable (Chanout a) (Chanin a) where
  connect = (>>=) ((writer &&& reader) <$> oneToOneChannel)
instance ConnectableExtra (Chanout a) (Chanin a) where
  type ConnectableParam (Chanout a) = ChanOpts a
  connectExtra o = (>>=) ((writer &&& reader) <$> oneToOneChannel' o)

instance Connectable (Chanin a) (Chanout a) where
  connect = (>>=) ((reader &&& writer) <$> oneToOneChannel)
instance ConnectableExtra (Chanin a) (Chanout a) where
  type ConnectableParam (Chanin a) = ChanOpts a
  connectExtra o = (>>=) ((reader &&& writer) <$> oneToOneChannel' o)

instance Connectable (Enrolled PhasedBarrier ()) (Enrolled PhasedBarrier ()) where
  connect m = do b <- newBarrier
                 enroll b $ \b0 -> enroll b $ \b1 -> m (b0, b1)

instance ConnectableExtra (Enrolled PhasedBarrier ph) (Enrolled PhasedBarrier ph) where
  type ConnectableParam (Enrolled PhasedBarrier ph) = (ph, BarOpts ph)
  connectExtra (ph, o) m
    = do b <- newPhasedBarrier' ph o
         enroll b $ \b0 -> enroll b $ \b1 -> m (b0, b1)


instance (Connectable al ar, Connectable bl br) => Connectable (al, bl) (ar, br) where
  connect m = connect $ \(ax, ay) -> connect $ \(bx, by) -> m ((ax, bx), (ay, by))
instance (ConnectableExtra al ar, ConnectableExtra bl br) => ConnectableExtra (al, bl) (ar, br) where
  type ConnectableParam (al, bl) = (ConnectableParam al, ConnectableParam bl)
  connectExtra (ao, bo) m = connectExtra ao $ \(ax, ay) -> connectExtra bo $ \(bx, by) -> m ((ax, bx), (ay, by))

instance (Connectable al ar, Connectable bl br, Connectable cl cr) =>
          Connectable (al, bl, cl) (ar, br, cr) where
  connect m = connect $ \(ax, ay) -> connect $ \(bx, by) ->
              connect $ \(cx, cy) -> m ((ax, bx, cx), (ay, by, cy))

instance (ConnectableExtra al ar, ConnectableExtra bl br, ConnectableExtra cl cr) =>
  ConnectableExtra (al, bl, cl) (ar, br, cr) where
  type ConnectableParam (al, bl, cl) = (ConnectableParam al, ConnectableParam bl, ConnectableParam cl)
  connectExtra (ao, bo, co) m
    = connectExtra ao $ \(ax, ay) -> connectExtra bo $ \(bx, by) ->
      connectExtra co $ \(cx, cy) -> m ((ax, bx, cx), (ay, by, cy))

instance (Connectable al ar, Connectable bl br, Connectable cl cr,
          Connectable dl dr) =>
          Connectable (al, bl, cl, dl) (ar, br, cr, dr) where
  connect m = connect $ \(ax, ay) -> connect $ \(bx, by) ->
              connect $ \(cx, cy) -> connect $ \(dx, dy) ->
                m ((ax, bx, cx, dx), (ay, by, cy, dy))
instance (ConnectableExtra al ar, ConnectableExtra bl br, ConnectableExtra cl cr,
          ConnectableExtra dl dr) =>
          ConnectableExtra (al, bl, cl, dl) (ar, br, cr, dr) where
  type ConnectableParam (al, bl, cl, dl)
    = (ConnectableParam al,
       ConnectableParam bl,
       ConnectableParam cl,
       ConnectableParam dl)
  connectExtra (ao, bo, co, do_) m
    = connectExtra ao $ \(ax, ay) -> connectExtra bo $ \(bx, by) ->
      connectExtra co $ \(cx, cy) -> connectExtra do_ $ \(dx, dy) ->
        m ((ax, bx, cx, dx), (ay, by, cy, dy))

instance (Connectable al ar, Connectable bl br, Connectable cl cr,
          Connectable dl dr, Connectable el er) =>
          Connectable (al, bl, cl, dl, el) (ar, br, cr, dr, er) where
  connect m = connect $ \(ax, ay) -> connect $ \(bx, by) ->
              connect $ \(cx, cy) -> connect $ \(dx, dy) ->
              connect $ \(ex, ey) -> m ((ax, bx, cx, dx, ex), (ay, by, cy, dy, ey))
instance (ConnectableExtra al ar, ConnectableExtra bl br, ConnectableExtra cl cr,
          ConnectableExtra dl dr, ConnectableExtra el er) =>
          ConnectableExtra (al, bl, cl, dl, el) (ar, br, cr, dr, er) where
  type ConnectableParam (al, bl, cl, dl, el)
    = (ConnectableParam al,
       ConnectableParam bl,
       ConnectableParam cl,
       ConnectableParam dl,
       ConnectableParam el)
  connectExtra (ao, bo, co, do_, eo) m
    = connectExtra ao $ \(ax, ay) -> connectExtra bo $ \(bx, by) ->
      connectExtra co $ \(cx, cy) -> connectExtra do_ $ \(dx, dy) ->
        connectExtra eo $ \(ex, ey) -> m ((ax, bx, cx, dx, ex), (ay, by, cy, dy, ey))