
(* internal function used by other PMC function blocks, end user should NEVER call this themselves *)
FUNCTION PMC_CmdProcessor
	(*provide an output since void function types are not allowed*)
	PMC_CmdProcessor := TRUE;

	TicketCompleted := FALSE;
	(*determine function block behaviour*)
	IF Execute = TRUE THEN
		CASE PMCFuncInfo.CmdSta OF
			0:		
				(*initialization*)
				Busy := TRUE;
				Ack := FALSE;
				Done := FALSE;
				Aborted := FALSE;
				Error := FALSE;
				ErrorID := 0;
			
				(*get event ID if the function wants it*)
				IF EventCommand = TRUE THEN
					EventIDFound := FALSE;
					(*if an event id is currently available *)
					IF PM_Controller.EventMgmt.AvailableEventID <> 0 THEN
						(*grab the currently available event id *)
						PMCFuncInfo.EventID := PM_Controller.EventMgmt.AvailableEventID;
						PM_Controller.EventMgmt.ExecutingEventID[PMCFuncInfo.EventID] := TRUE;
						PM_Controller.EventMgmt.EventCode[PMCFuncInfo.EventID] := 0;
						EventIDFound := TRUE;	
						(*if an event id wasn't prepared try to find one	*)
					ELSE
						(*loop through all possible event IDs*)
						FOR loopcounter := 1 TO 5000 DO
							(*if free event id found grab it*)
							IF(PM_Controller.EventMgmt.ExecutingEventID[loopcounter] = FALSE)THEN
								PMCFuncInfo.EventID := UDINT_TO_UINT(loopcounter);
								PM_Controller.EventMgmt.ExecutingEventID[PMCFuncInfo.EventID] := TRUE;
								PM_Controller.EventMgmt.EventCode[PMCFuncInfo.EventID] := 0;
								EventIDFound := TRUE;
								EXIT;
							END_IF;
						END_FOR;		
					END_IF;
				
					(*if an eventID was grabbed, prepare a new one 				*)
					IF(EventIDFound = TRUE)THEN
						PM_Controller.EventMgmt.AvailableEventID := 0;
						(*loop to end of all possible event IDs*)
						FOR loopcounter := PMCFuncInfo.EventID + 1 TO 5000 DO
							IF(PM_Controller.EventMgmt.ExecutingEventID[loopcounter] = FALSE)THEN
								PM_Controller.EventMgmt.AvailableEventID := UDINT_TO_UINT(loopcounter);
								EXIT;
							END_IF;
						END_FOR;
						(*loop to the grabbed eventID if one wasn't found *)
						IF(PM_Controller.EventMgmt.AvailableEventID = 0)THEN
							FOR loopcounter := 1 TO PMCFuncInfo.EventID - 1 DO
								IF(PM_Controller.EventMgmt.ExecutingEventID[loopcounter] = FALSE)THEN
									PM_Controller.EventMgmt.AvailableEventID := UDINT_TO_UINT(loopcounter);
									EXIT;
								END_IF;
							END_FOR;
						END_IF;
					END_IF;
				ELSE
					EventIDFound := TRUE;
					PMCFuncInfo.EventID := 0;
				END_IF;
			
				IF ExecutionLocation = 0 AND EventIDFound = TRUE THEN
					(*get a new ticket*)
					PMCFuncInfo.TicketNumber := PM_Controller.TicketMgmt.AvailableTicket;
					(*calculate the next ticket number*)
					IF PM_Controller.TicketMgmt.AvailableTicket = 254 THEN
						PM_Controller.TicketMgmt.AvailableTicket := 0;   (*not sure if it wraps around automatically*)
					ELSE
						PM_Controller.TicketMgmt.AvailableTicket := PM_Controller.TicketMgmt.AvailableTicket + 1;
					END_IF;
				
					(*set the ticket status bit to 0, which means the ticket has not finished*)
					PM_Controller.TicketMgmt.TicketStatus[PMCFuncInfo.TicketNumber] := 0;
				
					(*may start sending the command this cyle*)
					IF PMCFuncInfo.TicketNumber = PM_Controller.TicketMgmt.ExecutingTicket THEN
						(*PMCFuncInfo.DataToPMCAddress := ADR(%QB0) + PM_Controller.H2P_START;*)
						(*PMCFuncInfo.DataFromPMCAddress := ADR(%IB0) + PM_Controller.P2H_START;*)
						PMCFuncInfo.SendToPMC := TRUE;
						PMCFuncInfo.MsgPartNStatus := 1; (*start with sending the first part*)
						PMCFuncInfo.CmdSta := 11;
					ELSE
						(*go to the pending send section*)
						PMCFuncInfo.CmdSta := 10;
					END_IF;
				ELSE
					PMCFuncInfo.TicketNumber := 255; (*if we didn't get a new ticket in this call, then set ticket value to default of 255.*)
				END_IF;
			10:
				(*waiting to send to the PMC*)
				IF PMCFuncInfo.TicketNumber = PM_Controller.TicketMgmt.ExecutingTicket AND ExecutionLocation = 0 THEN
					(*PMCFuncInfo.DataToPMCAddress := ADR(%QB0) + PM_Controller.H2P_START;*)
					(*PMCFuncInfo.DataFromPMCAddress := ADR(%IB0) + PM_Controller.P2H_START;*)
					PMCFuncInfo.SendToPMC := TRUE;
					PMCFuncInfo.MsgPartNStatus := 1; (*start with sending the first part*)
					PMCFuncInfo.CmdSta := 11;
				END_IF;
			11:
				(*attempted to send at least 1 part to the PMC, check to see how it went*)
				IF PMCFuncInfo.TicketNumber = PM_Controller.TicketMgmt.ExecutingTicket THEN
					IF PMCFuncInfo.MsgPartNStatus = 0 THEN
						(*finished sending to PMC*)
						PMCFuncInfo.MsgPartNStatus := 1;
						PMCFuncInfo.CmdSta := 20;
						PMCFuncInfo.SendToPMC := FALSE;
					ELSIF PMCFuncInfo.MsgPartNStatus < 0 THEN
						(*error with sending data to PMC*)
						PMCFuncInfo.SendToPMC := FALSE;
						Error := TRUE;
						ErrorID := 8000; (*pick an error ID that will specify library error*)
						TicketCompleted := TRUE;
						(*reset event ID*)
						IF PMCFuncInfo.EventID <> 0 THEN
							PM_Controller.EventMgmt.ExecutingEventID[PMCFuncInfo.EventID] := FALSE;
						END_IF;    
					ELSE    (*>0, multi part message processing*)
						IF ExecutionLocation = 0 THEN
							(*command ID*)
							memcpy(pDest := ADR(CmdCounterToPMC),pSrc := PM_Controller.H2P_START + PM_Controller.PMC_Constants.H2P_CMDC_OFFSET,length := 1);
							memcpy(pDest := ADR(CmdCounterFromPMC),pSrc := PM_Controller.P2H_START + PM_Controller.PMC_Constants.P2H_CMDC_OFFSET,length := 1);
							IF CmdCounterToPMC = CmdCounterFromPMC THEN
								(*PMCFuncInfo.DataToPMCAddress := ADR(%QB0) + PM_Controller.H2P_START;*)
								(*PMCFuncInfo.DataFromPMCAddress := ADR(%IB0) + PM_Controller.P2H_START;*)
								PMCFuncInfo.MsgPartNStatus := PMCFuncInfo.MsgPartNStatus + 1; (*send the next part*)
								PMCFuncInfo.SendToPMC := TRUE;
							ELSE
								(*don't send a new part until the previous one has been processed*)
								PMCFuncInfo.SendToPMC := FALSE;
							END_IF;
						END_IF;
					END_IF;
				ELSE
					(*abandoned ticket*)
					Aborted := TRUE;
					TicketCompleted := TRUE;
					(*reset event ID*)
					IF PMCFuncInfo.EventID <> 0 THEN
						PM_Controller.EventMgmt.ExecutingEventID[PMCFuncInfo.EventID] := FALSE;
					END_IF;    
				END_IF;
			20: (*waiting for the PMC to provide a reply            *)
				IF PMCFuncInfo.TicketNumber = PM_Controller.TicketMgmt.ExecutingTicket THEN
					IF ExecutionLocation = 0 THEN
						memcpy(pDest := ADR(CmdCounterToPMC),pSrc := PM_Controller.H2P_START + PM_Controller.PMC_Constants.H2P_CMDC_OFFSET,length := 1);
						memcpy(pDest := ADR(CmdCounterFromPMC),pSrc := PM_Controller.P2H_START + PM_Controller.PMC_Constants.P2H_CMDC_OFFSET,length := 1);
						IF CmdCounterToPMC = CmdCounterFromPMC THEN
							memcpy(pDest := ADR(CmdIDRtn),pSrc := PM_Controller.P2H_START + PM_Controller.PMC_Constants.P2H_CMDID_OFFSET,length := 2);
							memcpy(pDest := ADR(CmdID),pSrc := PM_Controller.H2P_START + PM_Controller.PMC_Constants.H2P_CMDID_OFFSET,length := 2);
						
							IF CmdID = CmdIDRtn THEN
								(*start the reading process*)
								(*PMCFuncInfo.DataToPMCAddress := ADR(%QB0) + PM_Controller.H2P_START;*)
								(*PMCFuncInfo.DataFromPMCAddress := ADR(%IB0) + PM_Controller.P2H_START;*)
								PMCFuncInfo.ReadFromPMC := TRUE;
								PMCFuncInfo.MsgPartNStatus := 1;
								PMCFuncInfo.CmdSta := 21; (*process the reading response*)
							ELSE
								(*command aborted*)
								Aborted := TRUE;
								TicketCompleted := TRUE;
								(*reset event ID*)
								IF PMCFuncInfo.EventID <> 0 THEN
									PM_Controller.EventMgmt.ExecutingEventID[PMCFuncInfo.EventID] := FALSE;
								END_IF;    
							END_IF;
						END_IF;
					END_IF;
				ELSE
					(*abandoned ticket*)
					Aborted := TRUE;
					TicketCompleted := TRUE;
					(*reset event ID*)
					IF PMCFuncInfo.EventID <> 0 THEN
						PM_Controller.EventMgmt.ExecutingEventID[PMCFuncInfo.EventID] := FALSE;
					END_IF;    
				END_IF;
			21: (*reading the processing the reply*)
				IF PMCFuncInfo.TicketNumber = PM_Controller.TicketMgmt.ExecutingTicket THEN
					IF PMCFuncInfo.MsgPartNStatus = 0 THEN
						(*finished reading from PMC, process                            *)
						PMCFuncInfo.ReadFromPMC := FALSE;
						TicketCompleted := TRUE;
						(*now need to process the return code from the command*)
						memcpy(pDest := ADR(ErrorID),pSrc := PM_Controller.P2H_START + PM_Controller.PMC_Constants.P2H_RTN_OFFSET,length := 2);
						IF ErrorID <> 0 THEN
							(* Statement section IF*)
							Error := TRUE; (*error reported by PMC*)
							(*reset event ID*)
							IF PMCFuncInfo.EventID <> 0 THEN
								PM_Controller.EventMgmt.ExecutingEventID[PMCFuncInfo.EventID] := FALSE;
							END_IF;
						ELSE
							Ack := TRUE;
						END_IF;
					ELSIF PMCFuncInfo.MsgPartNStatus < 0 THEN
						(*error with sending data to PMC*)
						PMCFuncInfo.ReadFromPMC := FALSE;
						TicketCompleted := TRUE;
						Error := TRUE;
						ErrorID := 8000; (*pick an error ID that will specify library error                                    *)
					ELSE    (*>0, multi part message*)
						(*first need to send acknowledgement handshake, will implement once interface is defined*)
						(*handshake*)
						(*processing additional reading replies*)
						memcpy(pDest := ADR(CmdCounterToPMC),pSrc := PM_Controller.H2P_START + PM_Controller.PMC_Constants.H2P_CMDC_OFFSET,length := 1);
						memcpy(pDest := ADR(CmdCounterFromPMC),pSrc := PM_Controller.P2H_START + PM_Controller.PMC_Constants.P2H_CMDC_OFFSET,length := 1);
						IF CmdCounterToPMC = CmdCounterFromPMC THEN
							(*send next part or finished sending?*)
						
							memcpy(pDest := ADR(CmdIDRtn),pSrc := PM_Controller.P2H_START + PM_Controller.PMC_Constants.P2H_CMDID_OFFSET,length := 2);
							memcpy(pDest := ADR(CmdID),pSrc := PM_Controller.H2P_START + PM_Controller.PMC_Constants.H2P_CMDID_OFFSET,length := 2);
							IF CmdID = CmdIDRtn THEN
								(*start the reading process*)
								(*PMCFuncInfo.DataToPMCAddress := ADR(%QB0) + PM_Controller.H2P_START;*)
								(*PMCFuncInfo.DataFromPMCAddress := ADR(%IB0) + PM_Controller.P2H_START;*)
								PMCFuncInfo.ReadFromPMC := TRUE;
								PMCFuncInfo.MsgPartNStatus := PMCFuncInfo.MsgPartNStatus + 1;
								PMCFuncInfo.CmdSta := 21; (*process the reading response*)
							ELSE
								(*command aborted*)
								Aborted := TRUE;
								TicketCompleted := TRUE;
							END_IF;
						END_IF;
					END_IF;
				ELSE
					(*abandoned ticket*)
					Aborted := TRUE;
					TicketCompleted := TRUE;
				END_IF;
			30:
				(*function block finished*)
				(*function block will reach this state if ticketcompleted is set to true                *)
				IF PMCFuncInfo.TicketNumber <> 255 THEN   (*double check to make sure no ticket is still active, due to forced timeout*)
					TicketCompleted := TRUE;
				END_IF;
				PMCFuncInfo.ReadFromPMC := FALSE;
				PMCFuncInfo.SendToPMC := FALSE;
			
				(*check if the event ID has triggered if it supports events*)
				IF(PMCFuncInfo.EventID <> 0)THEN
					IF(PM_Controller.EventMgmt.EventCode[PMCFuncInfo.EventID] <> 0)THEN
						Done := TRUE;
						PM_Controller.EventMgmt.ExecutingEventID[PMCFuncInfo.EventID] := FALSE;
					END_IF;
				END_IF;
		END_CASE;
	ELSE;
		(*reset the module and module state.*)
		IF PMCFuncInfo.CmdSta = 0 THEN
			PMCFuncInfo.TicketNumber := 255; (*this is needed to initialize the ticket number to 255 for function blocks that have not executed / obtained a ticket*)
		END_IF;
		Busy := FALSE;
		Ack := FALSE;
		Done := FALSE;
		Error := FALSE;
		IF PMCFuncInfo.CmdSta <> 0 AND PMCFuncInfo.CmdSta <> 30 THEN
			(*aborted a command in the middle of execution by setting execute to false*)
			(*for a truly aborted command, we can chose to use the state < 11 instead*)
			Aborted := TRUE;
		ELSE
			Aborted := FALSE;
		END_IF;
		PMCFuncInfo.CmdSta := 0;
		IF PMCFuncInfo.TicketNumber <> 255 THEN
			TicketCompleted := TRUE;
		END_IF;
	
	END_IF;

	(*close off tickets*)
	IF TicketCompleted = TRUE THEN
		IF PMCFuncInfo.TicketNumber <> 255 THEN
			(*release ticketing system by finding the next available ticket*)
			PM_Controller.TicketMgmt.TicketStatus[PMCFuncInfo.TicketNumber] := 1;    (*mark ticket as finished*)
			IF PM_Controller.TicketMgmt.ExecutingTicket = PMCFuncInfo.TicketNumber THEN
				(*scan through the ticket status array to find the next unfinished ticket*)
				nextTicketIndex := PMCFuncInfo.TicketNumber + 1;
				FOR tempCounter := 0 TO 255 DO
					IF nextTicketIndex = 255 THEN
						nextTicketIndex := 0;   (*not sure if it wraps around automatically*)
					END_IF;
					IF PM_Controller.TicketMgmt.TicketStatus[nextTicketIndex] = 0
						OR nextTicketIndex = PM_Controller.TicketMgmt.AvailableTicket THEN
						PM_Controller.TicketMgmt.ExecutingTicket := nextTicketIndex;
						EXIT;   (*exits the for loop*)
					ELSE
						nextTicketIndex := nextTicketIndex + 1;
					END_IF;
					(*no need to check if the entire array is 1, because at least 1 number will match with TicketMgmt.AvailableTicket. aside from the case where the available ticket number is 255*)
				END_FOR;
			END_IF;
			PMCFuncInfo.TicketNumber := 255;
		END_IF;
	
		IF (PMCFuncInfo.CmdSta <> 0) THEN
			PMCFuncInfo.CmdSta := 30;
		END_IF;
	
		Busy := FALSE;
	END_IF;
END_FUNCTION
