implementation module StdReceiver


import	StdInt, StdBool, StdList, StdTuple, StdOverloaded, StdFunc
import	commondef, id, receiveraccess, receiverdefaccess, receiverdevice, receiverid, scheduler
from	StdPSt	import	accPIO, appPIO, :: St


stdReceiverFatalError :: String String -> .x
stdReceiverFatalError rule error
	= fatalError rule "StdReceiver" error


//	Open one-way receiver:

class Receivers rdef where
	openReceiver	:: .ls !*(rdef .ls (PSt .l)) !(PSt .l) -> (!ErrorReport,!PSt .l)
	getReceiverType	::      *(rdef .ls .pst)               -> ReceiverType

instance Receivers (Receiver m) where
	openReceiver ls rDef pState
		# pState					= receiverFunctions.dOpen pState
		# (idtable,ioState)			= ioStGetIdTable pState.io
		# (member,idtable)			= memberIdTable id idtable
		| member
			= (ErrorIdsInUse,{pState & io=ioStSetIdTable idtable ioState})
		# (rt,ioState)				= ioStGetReceiverTable ioState
		# (maybe_parent,rt)			= getReceiverTableEntry id rt
		# ioState					= ioStSetReceiverTable rt ioState
		| isJust maybe_parent		// This condition should not occur: IdTable didn't contain Id while ReceiverTable does.
			= stdReceiverFatalError "openReceiver (Receiver)" "inconsistency detected between IdTable and ReceiverTable"
		# (found,rDevice,ioState)	= ioStGetDevice ReceiverDevice ioState
		| not found					// This condition should not occur: ReceiverDevice has just been 'installed'
			= stdReceiverFatalError "openReceiver (Receiver)" "could not retrieve ReceiverSystemState from IOSt"
		| otherwise
			= (NoError,pState2)
		with
			rsHs				= (receiverSystemStateGetReceiverHandles rDevice).rReceivers
// MW11 was			rsH					= newReceiverStateHandle id ls1 select f
			rsH					= newReceiverStateHandle id ls1 select connectedIds f
			ioState2			= ioStSetDevice (ReceiverSystemState {rReceivers=[rsH:rsHs]}) ioState
			ioState3			= bindRId id select id ReceiverDevice ioState2
			(ioId,ioState4)		= ioStGetIOId ioState3
			ioState5			= ioStSetIdTable (snd (addIdToIdTable id {idpIOId=ioId,idpDevice=ReceiverDevice,idpId=id} idtable)) ioState4
			pState1				= {pState & io=ioState5}
			(ls1,pState2)		= receiverInit (ls,pState1)
	where
		rid						= receiverDefRId         rDef
		select					= receiverDefSelectState rDef
		f						= receiverDefFunction    rDef
// MW11 was		receiverInit			= getReceiverInitFun (snd (cselect isReceiverInit (ReceiverInit (\x->x)) (receiverDefAttributes rDef)))
		receiverInit			= getReceiverInitFun (snd (cselect isReceiverInit (ReceiverInit (\x->x)) rDefAttributes))
		connectedIds			= getReceiverConnectedReceivers (snd (cselect isReceiverConnectedReceivers (ReceiverConnectedReceivers []) rDefAttributes)) // MW11++
		id						= rIdtoId rid
		rDefAttributes			= receiverDefAttributes rDef // MW11++

	getReceiverType _			= "Receiver"

instance Receivers (Receiver2 m r) where
	openReceiver ls rDef pState
		# pState					= receiverFunctions.dOpen pState
		# (idtable,ioState)			= ioStGetIdTable pState.io
		# (member,idtable)			= memberIdTable id idtable
		| member
			= (ErrorIdsInUse,{pState & io=ioStSetIdTable idtable ioState})
		# (rt,ioState)				= ioStGetReceiverTable ioState
		# (maybe_parent,rt)			= getReceiverTableEntry id rt
		# ioState					= ioStSetReceiverTable rt ioState
		| isJust maybe_parent		// This condition should not occur: IdTable didn't contain Id while ReceiverTable does.
			= stdReceiverFatalError "openReceiver (Receiver2)" "inconsistency detected between IdTable and ReceiverTable"
		# (found,rDevice,ioState)	= ioStGetDevice ReceiverDevice ioState
		| not found
			= stdReceiverFatalError "openReceiver (Receiver2)" "could not retrieve ReceiverSystemState from IOSt"
		| otherwise
			= (NoError,pState2)
		with
			rsHs				= (receiverSystemStateGetReceiverHandles rDevice).rReceivers
// MW11 was			rsH					= newReceiverStateHandle2 id ls1 select f
			rsH					= newReceiverStateHandle2 id ls1 select connectedIds f
			ioState2			= ioStSetDevice (ReceiverSystemState {rReceivers=[rsH:rsHs]}) ioState
			ioState3			= bindRId id select id ReceiverDevice ioState2
			(ioId,ioState4)		= ioStGetIOId ioState3
			ioState5			= ioStSetIdTable (snd (addIdToIdTable id {idpIOId=ioId,idpDevice=ReceiverDevice,idpId=id} idtable)) ioState4
			pState1				= {pState & io=ioState5}
			(ls1,pState2)		= receiverInit (ls,pState1)
	where
		r2id					= receiver2DefR2Id        rDef
		select					= receiver2DefSelectState rDef
		f						= receiver2DefFunction    rDef
// MW11 was		receiverInit			= getReceiverInitFun (snd (cselect isReceiverInit (ReceiverInit (\x->x)) (receiver2DefAttributes rDef)))
		receiverInit			= getReceiverInitFun (snd (cselect isReceiverInit (ReceiverInit (\x->x)) rDefAttributes))
		connectedIds			= getReceiverConnectedReceivers (snd (cselect isReceiverConnectedReceivers (ReceiverConnectedReceivers []) rDefAttributes)) // MW11++
		id						= r2IdtoId r2id
		rDefAttributes			= receiver2DefAttributes rDef // MW11++

	getReceiverType _			= "Receiver2"


//	Closing receivers.

closeReceiver :: !Id !(IOSt .l) -> IOSt .l
closeReceiver id ioState
// MW11..
	#! (closed,ioState)			= ioStClosed ioState
	| closed
		= ioState
// ...MW11
// MW11 was	| not (isCustomRId id || isCustomR2Id id)
	| not (isCustomRId id || isCustomR2Id id || isCustomId id)
		= ioState
	# (found,rDevice,ioState)	= ioStGetDevice ReceiverDevice ioState
	| not found
		= ioState
	# rsHs						= (receiverSystemStateGetReceiverHandles rDevice).rReceivers
	  (found,rsH,rsHs)			= uremove (receiverStateIdentified id) (dummy "closeReceiver") rsHs
	# ioState					= ioStSetDevice (ReceiverSystemState {rReceivers=rsHs}) ioState
	| not found
		= ioState
//	| otherwise
	# (idtable,ioState)			= ioStGetIdTable ioState
	  ioState					= ioStSetIdTable (snd (removeIdFromIdTable id idtable)) ioState
	  ioState					= unbindRId id ioState
// MW11..
	  ioState					= ioStSetRcvDisabled True ioState
	  connectedIds				= rsH.rHandle.rConnected
	  ioState					= seq (map closeReceiver connectedIds) ioState
	  inetInfo					= rsH.rHandle.rInetInfo
	| isNothing inetInfo
		= ioState
	| otherwise
		# (_,_,_,closeFun)		= fromJust inetInfo
		  ioState				= appIOToolbox closeFun ioState
// ..MW11
		= ioState
where
	receiverStateIdentified :: !Id !(ReceiverStateHandle .pst) -> *(!Bool,!ReceiverStateHandle .pst)
	receiverStateIdentified id rsH=:{rHandle}
		= (receiverIdentified id rHandle,rsH)

//	Get the Ids and ReceiverTypes of all current receivers:

getReceivers :: !(IOSt .l) -> (![(Id,ReceiverType)],!IOSt .l)
getReceivers ioState
	# (found,rDevice,ioState)	= ioStGetDevice ReceiverDevice ioState
	| not found
		= ([],ioState)
	| otherwise
		# (idstypes,rsHs)		= getreceivers (receiverSystemStateGetReceiverHandles rDevice).rReceivers
		# ioState				= ioStSetDevice (ReceiverSystemState {rReceivers=rsHs}) ioState
		= (idstypes,ioState)
where
	getreceivers :: ![ReceiverStateHandle .pst] -> ([(Id,ReceiverType)],![ReceiverStateHandle .pst])
	getreceivers [rsH:rsHs]
		# (idtype, rsH)		= getreceiver  rsH
		  (idtypes,rsHs)	= getreceivers rsHs
		= ([idtype:idtypes],[rsH:rsHs])
	where
		getreceiver :: !(ReceiverStateHandle .pst) -> ((Id,ReceiverType),!ReceiverStateHandle .pst)
		getreceiver rsH=:{rHandle={rId,rOneWay}}
			= ((rId,rType),rsH)
		where
			rType	= if rOneWay "Receiver" "Receiver2"
	getreceivers _
		= ([],[])


//	Changing attributes:

enableReceivers :: ![Id] !(IOSt .l) -> IOSt .l
enableReceivers ids ioState
	= changeReceivers (receiverSetSelectState Able) (receiverEntrySetSelectState Able) ids ioState

disableReceivers :: ![Id] !(IOSt .l) -> IOSt .l
disableReceivers ids ioState
	# ioState = ioStSetRcvDisabled True ioState
	= changeReceivers (receiverSetSelectState Unable) (receiverEntrySetSelectState Unable) ids ioState

receiverEntrySetSelectState :: !SelectState !ReceiverTableEntry -> ReceiverTableEntry
receiverEntrySetSelectState select rte
	= {rte & rteSelectState=select}

changeReceivers :: (IdFun (ReceiverStateHandle (PSt .l))) (IdFun ReceiverTableEntry) ![Id] !(IOSt .l) -> IOSt .l
changeReceivers changeReceiverState changeReceiverEntry ids ioState
	| isEmpty okids			// There aren't any receiver ids in the list
		= ioState
	# (found,rDevice,ioState)	= ioStGetDevice ReceiverDevice ioState
	| not found
		= ioState
	# rsHs						= (receiverSystemStateGetReceiverHandles rDevice).rReceivers
// MW11..
//	  allIds					= getConnectedIds okids [] rsHs rsHs
// PA...
	  (allIds,rsHs)				= getConnectedIds okids rsHs
// ...PA
// ..MW11
// MW11 was	  (myids,rsHs)			= changereceiverstates changeReceiverState okids rsHs
	  (myids,rsHs)				= changereceiverstates changeReceiverState allIds rsHs
	# ioState					= ioStSetDevice (ReceiverSystemState {rReceivers=rsHs}) ioState
	| isEmpty myids			// No receivers were changed
		= ioState
	| otherwise
		# (rt,ioState)			= ioStGetReceiverTable ioState
		  rt					= changereceiverentries changeReceiverEntry myids rt
		# ioState				= ioStSetReceiverTable rt ioState
		= ioState
where
// MW11 was	okids					= filter (\id->isCustomRId id || isCustomR2Id id) ids
	okids						= filter (\id->isCustomRId id || isCustomR2Id id || isCustomId id) ids
	
/* MW11..
	getConnectedIds	:: ![Id] ![Id] ![ReceiverStateHandle .pst] ![ReceiverStateHandle .pst] -> [Id]
	getConnectedIds ids _ [] _
		= ids
	getConnectedIds ids alreadyHandled [rsH=:{rHandle={rId,rConnected}}:rsHs] allStateHandles
		| not (isMember rId ids) || isEmpty rConnected || isMember rId alreadyHandled
			= getConnectedIds ids alreadyHandled rsHs allStateHandles
		| otherwise
			= getConnectedIds (removeDup (rConnected++ids)) [rId:alreadyHandled] allStateHandles allStateHandles
		// search again in the whole set of receivers
..MW11 */
// PA...: replaced by the following because of uniqueness
	getConnectedIds :: ![Id] !*[ReceiverStateHandle .pst] -> (![Id],!*[ReceiverStateHandle .pst])
	getConnectedIds ids rsHs
		# (rshIds,rsHs)	= accessList getReceiverIds rsHs
		= (getConnectedIds` ids [] rshIds rshIds,rsHs)
	where
		getReceiverIds :: !*(ReceiverStateHandle .pst) -> ((Id,[Id]),!*ReceiverStateHandle .pst)
		getReceiverIds rsH=:{rHandle={rId,rConnected}} = ((rId,rConnected),rsH)
		
		getConnectedIds`	:: ![Id] ![Id] ![(Id,[Id])] ![(Id,[Id])] -> [Id]
		getConnectedIds` ids _ [] _
			= ids
		getConnectedIds` ids alreadyHandled [(rId,rConnected):rsHs] allStateHandles
			| not (isMember rId ids) || isEmpty rConnected || isMember rId alreadyHandled
				= getConnectedIds` ids alreadyHandled rsHs allStateHandles
			| otherwise	// search again in the whole set of receivers
				= getConnectedIds` (removeDup (rConnected++ids)) [rId:alreadyHandled] allStateHandles allStateHandles
// ...PA

	changereceiverstates :: !(IdFun (ReceiverStateHandle .pst)) ![Id] ![ReceiverStateHandle .pst]
															-> (![Id],![ReceiverStateHandle .pst])
	changereceiverstates _ [] rsHs
		= ([],rsHs)
	changereceiverstates _ ids []
		= ([],[])
	changereceiverstates f ids [rsH=:{rHandle={rId}}:rsHs]
		| hadId
			= ([rId:rIds],[f rsH:rsHs1])
		| otherwise
			= (     rIds, [  rsH:rsHs1])
	where
		(hadId,_,ids1)	= remove ((==) rId) (dummy "changereceiverstates") ids
		(rIds,rsHs1)	= changereceiverstates f ids1 rsHs
	
	changereceiverentries :: !(IdFun ReceiverTableEntry) ![Id] !*ReceiverTable -> *ReceiverTable
	changereceiverentries f [id:ids] rt
		# (maybe_rte,rt)			= getReceiverTableEntry id rt
		| isNothing maybe_rte		= changereceiverentries f ids rt
		| otherwise					= changereceiverentries f ids (setReceiverTableEntry (f (fromJust maybe_rte)) rt)
	changereceiverentries _ _ rt
		= rt


//	Get the SelectState of a receiver:

getReceiverSelectState :: !Id !(IOSt .l) -> (!Maybe SelectState,!IOSt .l)
getReceiverSelectState id ioState
// MW11 was	| not (isCustomRId id || isCustomR2Id id)
	| not (isCustomRId id || isCustomR2Id id || isCustomId id)
		= (Nothing,ioState)
	# (found,rDevice,ioState)	= ioStGetDevice ReceiverDevice ioState
	| not found
		= (Nothing,ioState)
	| otherwise
		# rsHs					= (receiverSystemStateGetReceiverHandles rDevice).rReceivers
		  (select,rsHs1)		= getselectstate id rsHs
		# ioState				= ioStSetDevice (ReceiverSystemState {rReceivers=rsHs1}) ioState
		= (select,ioState)
where
	getselectstate :: !Id ![ReceiverStateHandle .pst] -> (!Maybe SelectState,![ReceiverStateHandle .pst])
	getselectstate id [rsH=:{rHandle=rH=:{rSelect}}:rsHs]
		| receiverIdentified id rH	= (Just rSelect,[rsH:rsHs])
		| otherwise					= (select,[rsH:rsHs1])
		with
			(select,rsHs1)			= getselectstate id rsHs
	getselectstate _ _
		= (Nothing,[])


//	Message passing:

::	SendReport
	=	SendOk
	|	SendUnknownReceiver
	|	SendUnableReceiver
	|	SendDeadlock
	|	OtherSendReport !String

instance == SendReport where
	(==) :: !SendReport !SendReport -> Bool
	(==) SendOk					report	= case report of
											SendOk				-> True
											_					-> False
	(==) SendUnknownReceiver	report	= case report of
											SendUnknownReceiver	-> True
											_					-> False
	(==) SendUnableReceiver		report	= case report of
											SendUnableReceiver	-> True
											_					-> False
	(==) SendDeadlock			report	= case report of
											SendDeadlock		-> True
											_					-> False
	(==) (OtherSendReport s1)	report	= case report of
											OtherSendReport s2	-> s1==s2
											_					-> False

instance toString SendReport where
	toString :: !SendReport -> {#Char}
	toString SendOk					= "SendOk"
	toString SendUnknownReceiver	= "SendUnknownReceiver"
	toString SendUnableReceiver		= "SendUnableReceiver"
	toString SendDeadlock			= "SendDeadlock"
	toString (OtherSendReport s)	= "(OtherSendReport "+++s+++")"


/*	Asynchronous, uni-directional, message passing.
	If the receiver could not be found in the global ReceiverTable, 
		then return SendUnknownReceiver and do nothing.
	If the receiver could be found, then increase the length of the 
		asynchronous message queue of the receiver in the global 
		ReceiverTable.
		Add the message to the asynchronous message queue of the 
		receiver using the scheduler.
*/
asyncSend :: !(RId msg) msg !(PSt .l) -> (!SendReport,!PSt .l)
asyncSend rid msg pState
	# id					= rIdtoId rid
	# (rt,ioState)			= ioStGetReceiverTable pState.io
	# (maybe_rte,rt)		= getReceiverTableEntry id rt
	| isNothing maybe_rte
		# ioState			= ioStSetReceiverTable rt ioState
		= (SendUnknownReceiver,{pState & io=ioState})
	# rte					= fromJust maybe_rte
	  rteLoc				= rte.rteLoc
	  pid					= rteLoc.rlIOId
	  qEvent				= {qasmRecLoc=rteLoc,qasmMsg=openDynamic (rIdtoDId rid) msg}
	# (ioid,ioState)		= ioStGetIOId ioState
	  rte					= {rte & rteASMCount=rte.rteASMCount+1}
	  rt					= setReceiverTableEntry rte rt
	# ioState				= ioStSetReceiverTable rt ioState
	# pState				= {pState & io=ioState}
	| pid==ioid
		= (SendOk,pState1)
	with
		(_,_,pState1)		= handleOneEventForDevices (ScheduleMsgEvent (QASyncMessage qEvent)) pState
	# (maybe_error,_,pState)= cswitchProcess pid (ScheduleMsgEvent (QASyncMessage qEvent)) pState
	| (isJust maybe_error)
		= (toSendError (fromJust maybe_error),pState)
	| otherwise
		= (SendOk,pState)


/*	Synchronous, uni-directional, message passing.
	If the receiver could not be found in the global ReceiverTable,
		then return SendUnknownReceiver and do nothing.
	If the receiver could be found, then let the receiver handle the
		synchronous message using the scheduler.
*/
syncSend :: !(RId msg) msg !(PSt .l) -> (!SendReport, !PSt .l)
syncSend rid msg pState
	# id						= rIdtoId rid
	# (rt,ioState)				= ioStGetReceiverTable pState.io
	# (maybe_parent,rt)			= getReceiverTableEntry id rt
	# ioState					= ioStSetReceiverTable rt ioState
	| isNothing maybe_parent
		= (SendUnknownReceiver,{pState & io=ioState})
	# parent					= fromJust maybe_parent
	  rteLoc					= parent.rteLoc
	  pid						= rteLoc.rlIOId
	  sEvent					= {	smRecLoc	= rteLoc
		  						  ,	smMsg		= openDynamic (rIdtoDId rid) msg
		  						  ,	smResp		= []
		  						  ,	smError		= []
		  						  }
	# (ioid,ioState)			= ioStGetIOId ioState
	# pState					= {pState & io=ioState}
	| pid==ioid
		= PStHandleSyncMessage sEvent pState
	| otherwise
		# (opt_error,_,pState)	= cswitchProcess pid (ScheduleMsgEvent (SyncMessage sEvent)) pState
		  report				= if (isJust opt_error) (toSendError (fromJust opt_error)) SendOk
		= (report,pState)
where
	PStHandleSyncMessage :: !SyncMessage !(PSt .l) -> (!SendReport, !PSt .l)
	PStHandleSyncMessage sm pState
		# (_,schedulerEvent,pState)
								= handleOneEventForDevices (ScheduleMsgEvent (SyncMessage sm)) pState
		  sm					= case schedulerEvent of
						  			(ScheduleMsgEvent (SyncMessage sm))	-> sm
				  					_									-> stdReceiverFatalError "syncSend" "unexpected scheduler event"
		  errors				= sm.smError
		  report				= if (isEmpty errors)
				  					SendOk
				  					(case (hd errors) of
										ReceiverUnable	-> SendUnableReceiver
										ReceiverUnknown	-> SendUnknownReceiver
									)
		= (report,pState)


/*	Synchronous, bi-directional, message passing.
	If the receiver could not be found in the global ReceiverTable,
		then return SendUnknownReceiver and do nothing.
	If the receiver could be found, then let the receiver handle the
		synchronous message using the scheduler.
*/
syncSend2 :: !(R2Id msg resp) msg !(PSt .l) -> (!(!SendReport,!Maybe resp), !PSt .l)
syncSend2 r2id msg pState=:{io=ioState}
	# id						= r2IdtoId r2id
	# (rt,ioState)				= ioStGetReceiverTable ioState
	# (maybe_parent,rt)			= getReceiverTableEntry id rt
	# ioState					= ioStSetReceiverTable rt ioState
	| isNothing maybe_parent
		= ((SendUnknownReceiver,Nothing),{pState & io=ioState})
	# parent					= fromJust maybe_parent
	  rteLoc					= parent.rteLoc
	  pid						= rteLoc.rlIOId
	  sEvent					= {	smRecLoc		= rteLoc
	  							  ,	smMsg			= openDynamic (r2IdtoDId r2id) msg
	  							  ,	smResp			= []
	  							  ,	smError			= []
	  							  }
	# (ioid,ioState)			= ioStGetIOId ioState
	# pState					= {pState & io=ioState}
	| pid==ioid
		= pstHandleSync2Message (r2IdtoDId` r2id) sEvent pState
	# (opt_error,resp,pState)	= cswitchProcess pid (ScheduleMsgEvent (SyncMessage sEvent)) pState
	| isJust opt_error
		= ((toSendError (fromJust opt_error),Nothing),pState)
	| isEmpty resp
		= stdReceiverFatalError "syncSend2" "no response received from bi-directional receiver"
	# maybe_response			= readDynamic (r2IdtoDId` r2id) (hd resp)
	| isNothing maybe_response
		= ((OtherSendReport "incorrect response received from bi-directional receiver",Nothing),pState)
	| otherwise
		= ((SendOk,maybe_response),pState)
where
	pstHandleSync2Message :: !(DId resp) !SyncMessage !(PSt .l) -> (!(!SendReport,!Maybe resp), !PSt .l)
	pstHandleSync2Message did sm pState
		# (handled,schedulerEvent,pState)
								= handleOneEventForDevices (ScheduleMsgEvent (SyncMessage sm)) pState
		  sm					= case schedulerEvent of
		  							(ScheduleMsgEvent (SyncMessage sm))	-> sm
		  							_									-> stdReceiverFatalError "syncSend2" "unexpected scheduler event"
		  errors				= sm.smError
		  resps					= sm.smResp
		| not handled
			= stdReceiverFatalError "syncSend2" "receiver event not handled"
		| not (isEmpty errors)
			# sendReport		= case (hd errors) of
									ReceiverUnable	-> SendUnableReceiver
									ReceiverUnknown	-> SendUnknownReceiver
			= ((sendReport,Nothing),pState)
		| isEmpty resps
			= stdReceiverFatalError "syncSend2" "no response received from bi-directional receiver"
		# maybe_response		= readDynamic did (hd resps)
		| isNothing maybe_response
			= ((OtherSendReport "incorrect response received from bi-directional receiver",Nothing),pState)
		| otherwise
			= ((SendOk,maybe_response),pState)

toSendError :: !SwitchError -> SendReport
toSendError SwitchToYourself				= SendUnknownReceiver
toSendError SwitchToDoesNotExist			= SendUnknownReceiver
toSendError SwitchToReceiverDoesNotExist	= SendUnknownReceiver
toSendError SwitchReceiverUnable			= SendUnableReceiver
toSendError SwitchEndsUpInDeadlock			= SendDeadlock
