-- Communicating Haskell Processes.
-- Copyright (c) 2010, University of Kent, Neil Brown.
-- 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.

module Control.Concurrent.CHP.EventType (
  Event, EventMap, EventSet, getEventTVar, getEventType, getEventTypeVal, getEventUnique, getEventPriority, newEvent, newEventPri,
  Offer(signalValue, offerAction, eventsSet),
  OfferSet(signalVar, offersSet, processId), makeOfferSet,
  RecordedEventType(..),
  SignalVar, SignalValue(..), addPoison, nullSignalValue, isNullSignal
  ) where

import Control.Arrow
import Data.Function (on)
import qualified Data.Map as Map
import Data.Unique
import Control.Concurrent
import Control.Concurrent.STM
import Control.Concurrent.CHP.Poison
import Control.Concurrent.CHP.ProcessId

type EventMap v = [(Event, v)]
type EventSet = [Event]
type OfferSetSet = [OfferSet]

-- | The type of an event in the CSP and VCR traces.
--
-- ClockSync was added in version 1.2.0.
--
-- The extra parameter on ChannelComm and BarrierSync (which are the result of
-- showing the value sent and phase ended respectively) was added in version 1.5.0.
data RecordedEventType
  = ChannelComm String
  | BarrierSync String
  | ClockSync String deriving (Eq, Ord, Show)

getEventTypeVal :: RecordedEventType -> String
getEventTypeVal (ChannelComm s) = s
getEventTypeVal (BarrierSync s) = s
getEventTypeVal (ClockSync s) = s

-- Not really a CSP event, more like an enrollable poisonable alting barrier!
data Event = Event {
  getEventUnique :: Unique, -- Event identifier
  getEventPriority :: Int, -- Priority
  getEventType :: STM RecordedEventType, -- Event type for trace recording
  getEventTVar :: TVar (WithPoison
    (Int, -- Enrolled count
     Integer, -- Event sequence count
     OfferSetSet) -- A list of offer sets
 )}

instance Eq Event where
  (==) = (==) `on` getEventUnique

instance Ord Event where
  compare = compare `on` getEventUnique

-- For testing:
instance Show Event where
  show e = "Event " ++ show (hashUnique $ getEventUnique e)

newEvent :: STM RecordedEventType -> Int -> IO Event
newEvent t n
  = do u <- newUnique
       atomically $ do tv <- newTVar (NoPoison (n, 0, []))
                       return $ Event u 0 t tv

newEventPri :: STM RecordedEventType -> Int -> Int -> IO Event
newEventPri t n pri
  = do u <- newUnique
       atomically $ do tv <- newTVar (NoPoison (n, 0, []))
                       return $ Event u pri t tv


-- The value used to pass information to a waiting process once one of their events
-- has fired (and they have been committed to it).  The Int is an index into their
-- list of guards
newtype SignalValue = Signal (WithPoison Int)
  deriving (Eq, Show)

type SignalVar = TVar (Maybe (SignalValue, Map.Map Unique (Integer, RecordedEventType)))

addPoison :: SignalValue -> SignalValue
addPoison = const $ Signal PoisonItem

nullSignalValue :: SignalValue
nullSignalValue = Signal $ NoPoison (-1)

isNullSignal :: SignalValue -> Bool
isNullSignal (Signal n) = n == NoPoison (-1)

data Offer = Offer {signalValue :: SignalValue, offerAction :: STM (), eventsSet :: EventSet}

data OfferSet = OfferSet { signalVar :: SignalVar -- Variable to use to signal when committed
                         , threadId :: ThreadId
                         , processId :: ProcessId -- Id of the process making the offer
                         , offersSet :: [Offer]} -- Value to send when committed
                                                 -- A list of all sets of events currently offered

instance Eq OfferSet where
  (==) = (==) `on` threadId

instance Ord OfferSet where
  compare = compare `on` threadId

instance Show OfferSet where
  show os = "OfferSet " ++ show (processId os, map (signalValue &&& eventsSet) $ offersSet os)

makeOfferSet :: SignalVar -> ProcessId -> ThreadId -> [((SignalValue, STM ()), EventSet)] -> OfferSet
makeOfferSet v pid tid = OfferSet v tid pid . map (uncurry (uncurry Offer))