// CSMAProtocol.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 CSMA/CD MAC layer.

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

public class CSMAProtocol implements ProtocolEntity {

  ////////////////////////// User Interface Messages ///////////////////////////

  /** Protocol deliver offer */
  private final static String DELIVER = "Deliver data to ";

  /** Protocol finish transmission offer */
  private final static String FINISH = "Finish sending data to ";

  /** Protocol jam offer */
  private final static String JAM = "Jam transmission to ";

  /** Protocol retry limit report */
  private final static String RETRIES =
    "Report failure due to excessive retries";

  /** Protocol start transmission offer */
  private final static String START = "Start sending data to ";

  ////////////////////////////// Protocol States ///////////////////////////////

  /** Protocol state - collision */
  protected final static int NORMAL = 0;

  /** Protocol state - collision detected */
  protected final static int COLLISION = 1;

  /** Protocol state - jam received following collision */
  protected final static int JAM_IN = 2;

  /** Protocol state - jam sent following collision */
  protected final static int JAM_OUT = 3;

  /** Protocol state - backing off following jam */
  protected final static int BACKOFF = 4;

  ///////////////////////////// Protocol Variables /////////////////////////////

  /** Protocol backoff count */
  protected int protocolBackoff;

  /** Protocol deferring to an incoming transmission */
  private boolean protocolDeferring;

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

  /** Protocol PDU received */
  protected CSMAPdu protocolIn;

  /** Protocol channel */
  protected CSMAMedium protocolMedium;

  /** Protocol name */
  protected String protocolName;

  /** Protocol PDU sent */
  protected CSMAPdu protocolOut;

  /** Protocol peer */
  protected CSMAProtocol protocolPeer;

  /** Protocol in the process of receiving a message */
  protected boolean protocolReceiving;

  /** Protocol retry count */
  protected int protocolRetries;

  /** Protocol in the process of sending a message */
  protected boolean protocolSending;

  /** Protocol collision state */
  protected int protocolState;

  /** Protocol user */
  protected CSMAService protocolUser;

  ////////////////////////////// Protocol Methods //////////////////////////////

  /**
    Constructor for a (de)multiplexer.

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

  /**
    Return the backoff count as a random integer in the range (0, 2^retries].
    This is 0..1 if there have been no retries, 0..2 if there has been one
    retry, etc. Technically this is the number of slot times, but actual time is
    irrelevant in the simulation approach.

    @return		backoff interval
  */
  public int backoffCount() {
    float backoff =				// get random in (0, 2^retries]
      (float) (CSMA.getRandom() * Math.pow(2.0, protocolRetries));
    return(Math.round(backoff));		// return nearest integer
  }

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

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

  /**
    Get the protocol name.

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

  /**
    Get the protocol peer.

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

 /**
    Return services currently offered.

    @return		list of protocol services
  */
  public Vector<String> getServices() {
    // System.err.println("CSMAProtocol.getServices - " + protocolName +
    //   ": protocolState <" + protocolState + "> protocolOut <" + protocolOut +
    //   "> protocolSending <" + protocolSending + ">");
    String peerName = protocolPeer.getName();	// get peer name
    Vector<String> list = new Vector<String>();	// initialise protocol services
    if (protocolState != NORMAL) {		// handling collision?
      if (protocolState == COLLISION ||		// collision detected or
	  protocolState == JAM_IN) {		// jam received?
	list.add(JAM + peerName);		// append jam sending offer
      }
      else if (protocolState == BACKOFF) {	// backing off?
	// System.err.println("CSMAProtocol.getServices - " + protocolName +
	//   ": state local <" + protocolState + "> remote <" +
	//   protocolPeer.protocolState + ">, backoff local <" +
	//   protocolBackoff + "> remote <" + protocolPeer.protocolBackoff +
	//   ">, protocolRetries local <" + protocolRetries + "> remote <" +
	//   protocolPeer.protocolRetries + ">\n");
	int peerState = protocolPeer.protocolState; // get peer state
	if (peerState == BACKOFF ||		// peer also backing off or
	    peerState == NORMAL) {		// peer recovered from backoff?
	  if (protocolBackoff <=		// local protocol can retry?
	      protocolPeer.protocolBackoff) {
	    protocolRetries++;			// increment retry count
	    if (protocolRetries > CSMA.retryLimit) { // retry limit reached?
	      list.add(RETRIES);		// add retry limit report
	    }
	    else {				// retry limit not reached
	      protocolState = NORMAL;		// set collision state to normal
	      protocolOut.type = CSMAPdu.START;	// set PDU back to start
	      initialise_part();		// partly re-initialise protocol
	      list.add(START+ peerName +	// append retry sending offer
		" (retry " + protocolRetries + ")");
	    }
	  }
	  else {				// local entity must wait
	    initialise_part();			// partly re-initialise protocol
	    protocolDeferring = true;		// note deferring
	  }
	}
      }
    }						// not handling collision hereon
    else if (protocolOut != null) {		// outgoing PDU available?
      if (protocolSending) {			// already sending?
	if (protocolMedium.isEmpty(protocolName)) { // no local PDUs sent?
	  list.add(FINISH + peerName);		// append finish sending offer
	}
      }
      else if ((protocolMedium.isEmpty() ||	// nothing in medium or
		CSMA.retryLimit > 0) &&		// retries allowed, and
	       protocolPeer.protocolState ==	// peer not in collision and
		 NORMAL &&
	       !protocolDeferring) {		// not deferring?
	list.add(START + peerName);		// append start sending offer
      }
    }
    if (protocolIn != null &&			// incoming PDU available and
	protocolState == NORMAL) {		// not in collision?
      String serviceName = protocolUser.getName(); // get service name
      list.add(DELIVER + serviceName);		// append deliver offer
    }
    return (list);				// return protocol service list
  }

  /**
    Initialise the protocol entity.
  */
  public void initialise() {
    initialise_part();				// partly initialise protocol
    protocolIn = null;				// initialise incoming PDU
    protocolOut = null;				// initialise outgoing PDu
    protocolRetries = 0;			// initialise no retries
    CSMA.randomIndex = 0;			// initialise random index
  }

  /**
    Partly initialise the protocol entity in the event of backoff and retry.
  */
  public void initialise_part() {
    protocolDeferring = false;			// initialise not deferring
    protocolEvents = new Vector<ProtocolEvent>();// initialise protocol events
    protocolReceiving = false;			// initialise not receiving
    protocolSending = false;			// initialise not sending
    protocolState = NORMAL;			// initialise no collision
  }

  /**
    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(DELIVER)) {		// deliver request?
      CSMASdu sdu =				// create SDU from PDU
	new CSMASdu(CSMASdu.DATA, protocolIn.sdu);
      transmitPDU(sdu, protocolUser);		// deliver SDU to service user
      protocolIn = null;			// re-initialise incoming PDU
    }
    else if (service.startsWith(FINISH)) {	// finish sending request?
      String message = protocolOut.sdu;		// get current SDU
      protocolOut =				// create finish PDU
	new CSMAPdu(CSMAPdu.FINISH, message);
      protocolSending = false;			// note not sending
      protocolEvents.addElement(		// add send event
	new ProtocolEvent(ProtocolEvent.TRANSMIT, protocolOut));
      transmitPDU(protocolOut, protocolPeer);	// send protocol PDU
      if (protocolPeer.protocolState == NORMAL)	// peer is in normal state?
	protocolOut = null;			// re-initialise outgoing PDU
    }
    else if (service.startsWith(JAM)) {		// jamming request?
      if (protocolState == JAM_IN) {		// jam received?
	protocolState = BACKOFF;		// note backing off
	protocolBackoff = backoffCount();	// compute backoff "time"
	protocolEvents.addElement(		// add backoff event
	  comment("backoff " + protocolBackoff));
      }
      else					// jam not received
	protocolState = JAM_OUT;		// note jam sent
      CSMAPdu pdu = new CSMAPdu(CSMAPdu.JAM, ""); // set jam PDU
      protocolEvents.addElement(		// add send event
	new ProtocolEvent(ProtocolEvent.TRANSMIT, pdu));
      transmitPDU(pdu, protocolPeer);	// send protocol PDU
    }
    else if (service.startsWith(RETRIES)) {	// retries response?
      initialise();				// re-initialise whole protocol
      CSMASdu sdu =				// construct failure SDU
	new CSMASdu(CSMASdu.FAIL, "Retry Limit");
      transmitPDU(sdu, protocolUser);		// deliver SDU to service user
    }
    else if (service.startsWith(START)) {	// start sending request?
      String message = protocolOut.sdu;		// get current SDU
      protocolOut =				// create start PDU
	new CSMAPdu(CSMAPdu.START, message);
      protocolSending = true;			// note sending
      if (protocolReceiving) {			// already receiving?
	protocolState = COLLISION;		// note collision
	protocolEvents.addElement(		// add collision event
	  comment("collision"));
      }
      protocolEvents.addElement(		// add send event
	new ProtocolEvent(ProtocolEvent.TRANSMIT, protocolOut));
      transmitPDU(protocolOut, protocolPeer);	// send protocol PDU
    }
    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 type = pdu.type;			// get PDU type
      String data = pdu.sdu;			// get SDU
      if (type.equals(CSMASdu.DATA)) {		// service user message?
	protocolOut =				// store outgoing PDU
	  new CSMAPdu(CSMAPdu.START, data);
	if (protocolReceiving) {		// already receiving
	  protocolDeferring = true;		// note deferring
	  protocolEvents.addElement(		// add defer event
	    comment("defer"));
	}
      }
      else if (type.equals(CSMAPdu.FINISH)) {	// finish receiving message?
	protocolReceiving = false;		// note not receiving
	if (protocolState == NORMAL)		// normal state?
	  protocolIn = (CSMAPdu) pdu;		// store incoming PDU
	if (protocolDeferring) {		// deferring?
	  protocolDeferring = false;		// not deferring
	  protocolEvents.addElement(		// add resume event
	    comment("resume"));
	}
      }
      else if (type.equals(CSMAPdu.JAM)) {	// jam message?
	if (protocolState == JAM_OUT) {		// jam sent?
	  protocolState = BACKOFF;		// note backing off
	  protocolBackoff = backoffCount();	// compute backoff "time"
	  protocolEvents.addElement(		// add backoff event
	    comment("backoff " + protocolBackoff));
	}
	else {					// jam not sent
	  if (protocolState == NORMAL) {	// normal state?
	    protocolEvents.addElement(		// add collision event
	      comment("collision"));
	  }
	  protocolState = JAM_IN;		// note jam received
	}
      }
      else if (type.equals(CSMAPdu.START)) {	// start receiving message?
	protocolReceiving = true;		// note receiving
	if (protocolSending) {			// already sending?
	  protocolState = COLLISION;		// note collision
	  protocolEvents.addElement(		// add collision event
	    comment("collision"));
	}
	else if (protocolOut != null) {		// PDU to be sent?
	  protocolDeferring = true;		// note deferring
	  if (protocolPeer.protocolState == NORMAL) // peer in normal state?
	    protocolEvents.addElement(		// add defer event
	      comment("defer"));
	}
	if (protocolState == NORMAL &&		// normal state and
	    protocolDeferring) {		// deferring to peer?
	  protocolPeer.protocolRetries = 0;	// reset its retry count
	}
      }
    }
    return(protocolEvents);			// return protocol events
  }

  /**
    Set the protocol peer.

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

  /**
    Set the protocol service.

    @param protocolUser		protocol service user
  */
  public void setUser(ProtocolEntity protocolUser) {
    this.protocolUser =				// set service user
      (CSMAService) 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
	protocolMedium.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));
  }

}

