// MUXProtocol.java

package protocol;				// protocol package

import java.util.*;				// import Java utility classes

import support.*;				// import Jasper support classes

/**
  This is the class that supports the (de)multiplexing protocol. It handles
  both synchronous and asynchronous modes.

  @author	Kenneth J. Turner
  @version	1.0 (20th July 2010, KJT): initial version
*/

public class MUXProtocol implements ProtocolEntity {

  /** Service send offer */
  private final static String RECEIVE = "Send data to ";

  /** Protocol send offer */
  private final static String SEND =
    "Multiplex and send ";

  /** Protocol send all available offer */
  private final static String SEND_ALL = SEND + "all available data";

  /** Protocol send one offer */
  private final static String SEND_ONE = SEND + "the individual data";

  /** Service source offer */
  private final static String SINK = "Sink ";

  /** Protocol events */
  private Vector<ProtocolEvent> protocolEvents;

  /** Protocol PDU received */
  private MUXPdu protocolIn;

  /** Protocol channel */
  private Medium protocolMedium;

  /** Protocol protocol mode (true = asynchronous, false = synchronous) */
  public static boolean protocolMode;

  /** Protocol name */
  private String protocolName;

  /** Protocol PDU sent */
  private MUXPdu protocolOut;

  /** Protocol peer */
  private MUXProtocol protocolPeer;

  /** Protocol user */
  private MUXService protocolUser;

  /**
    Constructor for a (de)multiplexer.

    @param protocolMedium	channel
    @param protocolName		protocol name
  */
  public MUXProtocol(Medium protocolMedium, String protocolName) {
    this.protocolName = protocolName;		// set protocol name
    this.protocolMedium = protocolMedium;	// set protocol medium
    initialise();				// initialise protocol
  }

  /**
    Return a protocol event with the given comment.

    @param comment	protocol comment
    @return		protocol event
  */
  private ProtocolEvent comment(String comment) {
    return(new ProtocolEvent(ProtocolEvent.COMMENT, this, comment));
  }

  /**
    Get the protocol mode.

    @return	protocol mode (true = asynchronous, false = synchronous)
  */
  public boolean getMode() {
    return(protocolMode);			// return protocol mode
  }

  /**
    Get the protocol name.

    @return	protocol name
  */
  public String getName() {
    return(protocolName);			// return protocol name
  }

  /**
    Get the protocol peer.

    @return	protocol peer
  */
  public MUXProtocol getPeer() {
    return(protocolPeer);			// return protocol peer
  }

  /**
    Get protocol input.

    @return	protocol input
  */
  public MUXPdu getProtocolIn() {
    return(protocolIn);				// return protocol input
  }

  /**
    Get protocol output.

    @return	protocol output
  */
  public MUXPdu getProtocolOut() {
    return(protocolOut);			// return protocol output
  }

 /**
    Return services currently offered.

    @return		list of protocol services
  */
  public Vector<String> getServices() {
    Vector<String> list = new Vector<String>();	// initialise protocol services
    int firstSDU = protocolOut.firstSDU();	// get first SDU index
    if (firstSDU >= 0) {			// SDU available?
      if (protocolMode)				// asynchronous?
	list.add(SEND_ONE);			// append send one offer
      else					// synchronous
	list.add(SEND_ALL);			// append send all offer
    }
    String[] sduList =				// get SDU list from PDU
      ((MUXPdu) protocolIn).getSDUs();
    for (int i = 0; i < sduList.length; i++) {	// go through SDUs
      String data = sduList[i];			// get SDU data
      if (data != null)				// SDU available?
	list.add(RECEIVE + SINK + i);		// append protocol receive offer
    }
    return (list);				// return protocol service list
  }

  /**
    Initialise the protocol entity.
  */
  public void initialise() {
    protocolEvents = new Vector<ProtocolEvent>();// initialise protocol events
    protocolIn = new MUXPdu();			// initialise protocol input
    protocolOut = new MUXPdu();			// initialise protocol output
  }

  /**
    Perform service.

    @param service	service request
    @return		resulting protocol events
  */
  public Vector<ProtocolEvent> performService(String service) {
    protocolEvents = new Vector<ProtocolEvent>(); // initialise protocol events
    if (service.startsWith(SEND)) {		// send all request?
      protocolEvents.addElement(		// add send event
	new ProtocolEvent(ProtocolEvent.TRANSMIT, protocolOut));
      protocolEvents.addElement(		// add receive event
	new ProtocolEvent(ProtocolEvent.RECEIVE, protocolOut));
      transmitPDU(protocolOut, protocolPeer);	// send protocol PDU
      protocolOut = new MUXPdu();		// re-initialise sent PDU
    }
    else if (service.startsWith(RECEIVE)) {	// receive request?
      int sinkStart =				// get start of sink index
	service.indexOf(SINK) + SINK.length();
      int sink =				// get source index
	Integer.parseInt(service.substring(sinkStart));
      String[] sduList = protocolIn.getSDUs();	// get SDU list from PDU
      String data = sduList[sink];		// get SDU data
      MUXSdu sdu = new MUXSdu(sink, data);	// create SDU
      transmitPDU(sdu, protocolUser);		// send to service user
      protocolIn.setSDU(sink, null);		// remove sink SDU data
    }
    return(protocolEvents);			// return protocol events
  }

  /**
    Receive PDU.

    @param pdu		PDU
    @return		resulting protocol events
  */
  public Vector<ProtocolEvent> receivePDU(PDU pdu) {
    protocolEvents = new Vector<ProtocolEvent>(); // initialise event list
    if (pdu != null) {				// non-empty PDU?
      String pduType = pdu.type;		// get PDU type
      if (pduType.equals(MUXSdu.TYPE)) {	// service user message?
	int source = ((MUXSdu) pdu).entity;	// get source index
	String data = ((MUXSdu) pdu).sdu;	// get service user data
	String comment;				// declare protocol comment
	if (protocolMode) { 			// asynchronous?
	  comment =				// set comment from presence
	    protocolOut.firstSDU() >= 0 ? "Overwritten" : "Stored";
	  protocolOut = new MUXPdu();		// initialise protocol output
	}
	else					// synchronous
	  comment =				// set comment from presence
	    protocolOut.getSDU(source) != null ? "Overwritten" : "Stored";
	protocolOut.setSDU(source, data);	// store source SDU data
	protocolEvents.addElement(		// add receive comment event
	  comment(comment));
      }
      else if (pduType.equals(MUXPdu.TYPE)) {	// protocol peer message?
	String comment =			// set comment from presence
	  protocolIn.firstSDU() >= 0 ? "Overwritten" : "Stored";
	protocolIn = (MUXPdu) pdu;		// store received PDU
	protocolEvents.addElement(		// add receive comment event
	  comment(comment));
      }
    }
    return(protocolEvents);			// return protocol events
  }

  /**
    Set the protocol mode.

    @param protocolMode		protocol mode (true = asynchronous,
				false = synchronous)
  */
  public void setMode(boolean protocolMode) {
    this.protocolMode = protocolMode;	 	// set protocol mode

  }

  /**
    Set the protocol peer.

    @param protocolPeer		protocol peer
  */
  public void setPeer(ProtocolEntity protocolPeer) {
    this.protocolPeer =				// set protocol peer
      (MUXProtocol) protocolPeer;
  }

  /**
    Set the protocol service.

    @param protocolUser		protocol service user
  */
  public void setUser(ProtocolEntity protocolUser) {
    this.protocolUser =				// set service user
      (MUXService) protocolUser;
  }

  /**
    Send PDU.

    @param pdu		PDU
    @param destination	protocol destination
  */
  public void transmitPDU(PDU pdu, ProtocolEntity destination) {
    pdu.setSource(this);			// set protocol entity as source
    pdu.setDestination(destination);		// set message destination
    Vector<ProtocolEvent> receiveEvents;	// declare receive events
    if (destination == protocolPeer) {		// for protocol peer?
      receiveEvents = 				// receive PDU at medium
	protocolPeer.receivePDU(pdu);
    }
    else					// for service user?
      receiveEvents =				// receive PDU at user
	protocolUser.receivePDU(pdu);
    for (int i = 0; i < receiveEvents.size(); i++) // go through receive events
      protocolEvents.addElement(		// append receive event
	receiveEvents.get(i));
  }

}

