-- 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.

-- | This module contains helper functions for wiring up collections of processes
-- into a two-dimensional arrangement.
module Control.Concurrent.CHP.Connect.TwoDim
  (FourWay(..), wrappedGridFour, wrappedGridFour_,
   FourWayDiag(..), EightWay, wrappedGridEight, wrappedGridEight_) where

import Control.Arrow
import Control.Concurrent.CHP
import Control.Concurrent.CHP.Connect
import Control.Monad
import Data.List

import Prelude hiding (abs)

-- | A data type representing four-way connectivity for a process, with channels
-- to the left and right, above and below.
data FourWay above below left right
  = FourWay { above :: above, below :: below, left :: left, right :: right }
    deriving (Eq)

-- | A data type representing four-way diagonal connectivity for a process, with
-- channels above-left, below-right, above-right and below-left.
data FourWayDiag aboveLeft belowRight aboveRight belowLeft
  = FourWayDiag { aboveLeft :: aboveLeft, belowRight :: belowRight, aboveRight :: aboveRight, belowLeft :: belowLeft }
    deriving (Eq)

-- | EightWay is simply a synonym for a pair of 'FourWay' and 'FourWayDiag'.
type EightWay a b l r al br ar bl  = (FourWay a b l r, FourWayDiag al br ar bl)

-- | Wires the given grid of processes (that require four-way connectivity) together
-- into a wrapped around grid (a torus) and runs them all in parallel.
--
-- The parameter is a list of rows, and should be rectangular (i.e. all the rows
-- should be the same length).  If not, an error will result.  The return value
-- is guaranteed to be the same shape as the input.
--
-- It is worth remembering that if you have only one row or one column (or
-- both), processes can be connected to themselves, so make sure that if a
-- process is connected to itself (e.g. its left channel connects to its right
-- channel), it is coded such that it won't deadlock -- or if needed, checks for this
-- possibility using 'sameChannel'.  Processes may also be connected to each other
-- multiple times -- in a two-wide grid, each process's left channel connects to
-- the same process as its right.
wrappedGridFour :: (Connectable above below, Connectable left right) =>
  [[FourWay above below left right -> CHP a]] -> CHP [[a]]
wrappedGridFour ps
  -- If ps == [], this will succeed, and map connectRowCycle ps will be [],
  -- and thus connectColumnsCycle _ [] will return [] (without forcing the
  -- head call), and it will all work correctly.
  | length (nub $ map length ps) <= 1
     = connectColumnsCycle (length (head ps)) $ map connectRowCycle ps
  | otherwise
     = error $ "Control.Concurrent.CHP.Connect.TwoDim.wrappedGrid: Non-rectangular input "
               ++ " height: " ++ show (length ps) ++ " widths: " ++ show (map length ps)

-- | Like 'wrappedGridFour' but discards the return values.
wrappedGridFour_ :: (Connectable above below, Connectable left right) =>
  [[FourWay above below left right -> CHP a]] -> CHP ()
wrappedGridFour_ ps = wrappedGridFour ps >> return () --TODO fix this

-- | Like 'wrappedGridFour' but provides eight-way connectivity.
--
-- The note on 'wrappedGridFour' about processes being connected to themselves
-- applies here too -- as does the note about processes being connected to
-- each other multiple times.  If you have one row, a process's left,
-- above-left and below-left channels all connect to the same process.  If you
-- have a two-by-two grid, a process's four diagonal channels all connect to
-- the same process.
wrappedGridEight :: (Connectable above below, Connectable left right,
              Connectable aboveLeft belowRight, Connectable belowLeft aboveRight) =>
  [[EightWay above below left right aboveLeft belowRight aboveRight belowLeft -> CHP a]] -> CHP [[a]]
wrappedGridEight ps
  | length (nub $ map length ps) <= 1
     = connectColumnsCycleDiag (length (head ps)) $ map connectRowCycleDiag ps
  | otherwise
     = error $ "Control.Concurrent.CHP.Connect.TwoDim.wrappedGridDiag: Non-rectangular input "
               ++ " height: " ++ show (length ps) ++ " widths: " ++ show (map length ps)

-- | Like 'wrappedGridEight' but discards the output.
wrappedGridEight_ :: (Connectable above below, Connectable left right,
              Connectable aboveLeft belowRight, Connectable belowLeft aboveRight) =>
  [[EightWay above below left right aboveLeft belowRight aboveRight belowLeft -> CHP a]] -> CHP ()
wrappedGridEight_ ps = wrappedGridEight ps >> return ()


connectRowCycle :: Connectable left right =>
  [FourWay above below left right -> CHP a] -> ([(above, below)] -> CHP [a])
connectRowCycle [] _ = return []
connectRowCycle allps abs = connect $
  foldr connLR
        -- The last process is special because it must take both channels for itself:
        (liftM (:[]) . last allps . uncurry (uncurry FourWay $ last abs))
        (zip (init allps) (init abs))

connLR :: Connectable left right =>
          (FourWay above below left right -> CHP a, (above, below))
       -> ((left, right) -> CHP [a])
       -> ((left, right) -> CHP [a])
connLR (p, (a, b)) q (l, r)
  = liftM (uncurry (:)) . connect $ \(l', r') -> p (FourWay a b l r') <||> q (l', r)

connectColumnsCycle :: Connectable above below => Int -> [[(above, below)] -> CHP [a]] -> CHP [[a]]
connectColumnsCycle _ [] = return []
connectColumnsCycle n ps = connectList n $ foldl1 (connAB n) (map (liftM (:[]) .) ps)

connAB :: Connectable above below => Int -> ([(above, below)] -> CHP [a]) -> ([(above, below)] -> CHP [a]) -> ([(above, below)] -> CHP [a])
connAB n p q abs = liftM (uncurry (++)) $ connectList n $ \abs' -> p (zip (map fst abs) (map snd abs'))
  <||> q (zip (map fst abs') (map snd abs))

connectColumnsCycleDiag :: (Connectable a b, Connectable bl ar, Connectable al br) =>
  Int -> [[((a, b), FourWayDiag al br ar bl)] -> CHP [z]] -> CHP [[z]]
connectColumnsCycleDiag _ [] = return []
connectColumnsCycleDiag n ps = connectList n $ \abs ->
  connectList n $ \leadingDiag -> connectList n $ \otherDiag ->
    foldl1 (connABDiag n) (map (liftM (:[]) .) ps)
      $ zip abs [FourWayDiag al br ar bl
                | (_, ar) <- otherDiag
                | (bl, _) <- shiftRight otherDiag
                | (al, _) <- leadingDiag
                | (_, br) <- shiftLeft leadingDiag]

-- Let's imagine we have a square:
--
-- A B C
-- D E F
-- G H I
--
-- We pass in the outer-most channels as the processes need them to be wired.
--
-- So for example, A will recieve:
-- aboveLeft: AI
-- aboveRight AH
-- belowLeft: AF
-- belowRight: AE
--
-- So for example when we create the leadingDiag channels:
--
-- \1 \2 \3 
-- A  B  C
--
-- The ends are passed to the above channels as-is, but to the below channels shifted lleft:
--
-- G  H  I
-- \2 \3 \1
--
-- For the otherDiag, shifted right when below:
--
-- /1 /2 /3
-- A  B  C
--
-- G  H  I
-- /3 /1 /2

shiftLeft, shiftRight :: [a] -> [a]
shiftLeft [] = []
shiftLeft xs = tail xs ++ [head xs]
shiftRight [] = []
shiftRight xs = last xs : init xs

connABDiag :: (Connectable above below, Connectable al br, Connectable bl ar) =>
  Int -> ([((above, below), FourWayDiag al br ar bl)] -> CHP [a])
  -> ([((above, below), FourWayDiag al br ar bl)] -> CHP [a])
  -> ([((above, below), FourWayDiag al br ar bl)] -> CHP [a])
connABDiag n p q abs = liftM (uncurry (++)) $ connectList n $ \abs' ->
  connectList n $ \leadingDiag -> connectList n $ \otherDiag ->
    p [((a, b), FourWayDiag al br ar bl)
      | ((a, _), _) <- abs
      | (_, b) <- abs'
      | (_, FourWayDiag al _ ar _) <- abs
      | (bl, _) <- shiftRight otherDiag
      | (_, br) <- shiftLeft leadingDiag
      ]
  <||>
    q [((a, b), FourWayDiag al br ar bl)
      | ((_, b), _) <- abs
      | (a, _) <- abs'
      | (al, _) <- leadingDiag
      | (_, ar) <- otherDiag
      | (_, FourWayDiag _ br _ bl) <- abs
      ]
-- We are given our own above and below as we need them to be arranged already.


connectRowCycleDiag :: Connectable l r =>
  [EightWay a b l r al br ar bl -> CHP z]
  -> ([((a, b), FourWayDiag al br ar bl)] -> CHP [z])
connectRowCycleDiag [] _ = return []
connectRowCycleDiag allps abs = connect $
  foldr connLRDiag
        -- The last process is special because it must take both channels for itself:
        (\lr -> liftM (:[]) $ last allps $ first (($ lr) . uncurry . uncurry FourWay) (last abs))
        (zip (init allps) (init abs))


connLRDiag :: Connectable l r =>
          (EightWay a b l r al br ar bl -> CHP z, ((a, b), FourWayDiag al br ar bl))
       -> ((l, r) -> CHP [z])
       -> ((l, r) -> CHP [z])
connLRDiag (p, ((a, b), diag)) q (l, r)
  = liftM (uncurry (:)) . connect $ \(l', r') -> p (FourWay a b l r', diag) <||> q (l', r)