// Medium.java

package support;				// protocol support package

import java.util.Enumeration;			// import Java enumerations
import java.util.HashSet;			// import Java hash sets
import java.util.StringTokenizer;		// import Java string tokenisers
import java.util.Vector;			// import Java vectors

/**
  This is the class for a protocol medium.

  @author	Iain A. Robin, Kenneth J. Turner
  @version	1.0 (1st September 1999, IAR): initial version <br/>
  <br/>		1.4 (9th March 2006, KJT): updated for JDK 1.5, use of Swing
		  graphics, minor tidying
  <br/>		1.5 (27th July 2010, KJT): minor tidying; "isEmpty" check added
*/
public class Medium implements ProtocolEntity {

  /** Control delivery and loss */
  public final static int CONTROL_DELIVERY_LOSS = 0;

  /** Control delivery (but not loss) */
  public final static int CONTROL_DELIVERY = 1;

  /** Control neither delivery nor loss - immediately deliver on reception */
  public final static int CONTROL_NOTHING = 2;

  /** PDUs in the medium */
  protected Vector<PDU> pdus;

  /** Destination events */
  protected Vector<ProtocolEvent> destinationEvents;

  /** Medium control type */
  private int mediumType = CONTROL_DELIVERY_LOSS;

  /**
    Constructor for a medium.
  */
  public Medium() {
    initialise();
  }

  /**
    Get the name attribute of a medium.

    @return   The name value
  */
  public String getName() {
    return("Medium");
  }

  /**
    Identify and return PDU currently on the medium with type and parameters
    matching those in the given description.

    @param description		service description
    @return			corresponding PDU (or null if not found)
  */

  protected PDU getMatchingPDU(String description) {
    PDU pdu;
    int seq = -1;
    // extract name of source entity for this PDU:
    int sourceStart = description.indexOf('[') + 1;
    int sourceEnd = description.indexOf(']');
    String sourceName = description.substring(sourceStart, sourceEnd);
    // get type and parameters of PDU from action string
    // PDU type starts after first space following "Deliver" or "Lose"
    int typeStart = description.indexOf(' ') + 1;
    int typeEnd = description.indexOf('(');
    if (typeEnd < 0)
      typeEnd = sourceStart - 2;
    String type = description.substring(typeStart, typeEnd);
    String[] params = getParams(description);
    boolean noParams = params == null;
    int paramCount = noParams ? 0 : params.length;
    if (paramCount == 1)
      seq = Integer.parseInt(params[0]);	// sequence number
    // try to match PDU type and seq with a PDU currently on channel
    for (Enumeration enumeration = pdus.elements();
	 enumeration.hasMoreElements(); ) {
      pdu = (PDU) enumeration.nextElement();
      boolean matches = false;
      if (pdu != null && pdu.type.equals(type)
	&& pdu.getSource().getName().equals(sourceName)) {
	if (noParams)
	  matches = true;
	else if (seq >= 0)
	  matches = pdu.seq == seq;
      }
      if (matches)
	return (pdu);
    }
    return (null);				// no match found
  }

  /**
    Extract comma-separated parameter strings within round brackets in string
    argument.

    @param description		description
    @return			comma-separated parameters
   */
  protected String[] getParams(String description) {
    int openBracket = description.indexOf("(");
    if (openBracket >= 0) {			// check for opening bracket
      int closeBracket = description.indexOf(")");
      String s1 = description.substring(openBracket + 1, closeBracket);
      StringTokenizer st = new StringTokenizer(s1, ",");
      String[] result = new String[st.countTokens()];
      for (int i = 0; st.hasMoreTokens(); i++)
	result[i] = st.nextToken();
      return (result);
    }
    return (null);
  }

  /**
    Return a list of strings describing actions (services) that can be carried
    out in the current state.

    @return			service descriptions
  */
  public Vector<String> getServices() {
    Vector<String> list = new Vector<String>();	// initialise service list
    HashSet<String> pdusSent =			// initialise list of sources
      new HashSet<String>();
    for (Enumeration enumeration = pdus.elements(); // go through PDUs
	 enumeration.hasMoreElements(); ) {
      PDU pdu = (PDU) enumeration.nextElement();// get next PDU
      if (pdu != null) {			// non-null PDU?
	String name = pdu.getSource().getName();// get PDU source name
	String description =			// get PDU id and name
	  pdu.getID() + " [" + name + "]";
	if (!pdusSent.contains(name)) {		// no PDU with this source?
	  pdusSent.add(name);			// add source to list
	  list.addElement(			// add delivery service
	    "Deliver " + description + " - no loss");
	}
	if (mediumType == CONTROL_DELIVERY_LOSS)// delivery and loss controlled?
	  list.addElement(			// add loss service
	    "Lose " + description + " - congestion/error");
      }
    }
    return(list);				// return service list
  }

  /**
    Initialise the medium.
  */
  public void initialise() {
    pdus = new Vector<PDU>();
    destinationEvents = new Vector<ProtocolEvent>();
  }

  /**
    Check if the medium is empty (i.e. has no PDUs in either direction).

    @return		true/false if the medium has no/has PDUs
  */
  public boolean isEmpty() {
    return(pdus == null || pdus.size() == 0);	// return empty check
  }

  /**
    Check if the medium is empty (i.e. has no PDUs with the given source name).

    @param entityName	entity name
    @return		true/false if the medium has no/has PDUs
  */
  public boolean isEmpty(String entityName) {
    for (Enumeration enumeration = pdus.elements(); // go through PDUs
	 enumeration.hasMoreElements(); ) {
      PDU pdu = (PDU) enumeration.nextElement();// get next PDU
      String name = pdu.getSource().getName();	// get PDU source name
      if (name.equals(entityName))		// PDU with given source?
	return(false);				// immediately return false
    }
    return(true);				// return true as no PDUs
  }

  /**
    Carry out the action specified in the given string, and return a list of
    protocol events resulting from this action

    @param service	service to perform
    @return		resulting protocol events
   */

  public Vector<ProtocolEvent> performService(String service) {
    Vector<ProtocolEvent> events = new Vector<ProtocolEvent>();
    destinationEvents.removeAllElements();
    PDU pdu = getMatchingPDU(service);
    if (pdu != null) {
      if (service.startsWith("Deliver")) {
	transmitPDU(pdu, pdu.getDestination());
	pdus.removeElement(pdu);
	events.addElement(new ProtocolEvent(ProtocolEvent.RECEIVE, pdu));
      }
      else if (service.startsWith("Lose")) {
	destinationEvents.removeAllElements();
	pdus.removeElement(pdu);
	events.addElement(new ProtocolEvent(ProtocolEvent.LOSE, pdu));
      }
    }
    // tack on any events triggered by delivery of a message
    // to its destination protocol entity
    for (Enumeration enumeration = destinationEvents.elements();
	 enumeration.hasMoreElements(); )
      events.addElement((ProtocolEvent) enumeration.nextElement());
    return(events);
  }

  /**
    Passively accept an incoming PDU and add to the list of those currently on
    channel.

    @param pdu		PDU
    @return		resulting protocol events
   */
  public Vector<ProtocolEvent> receivePDU(PDU pdu) {
    Vector<ProtocolEvent> events = new Vector<ProtocolEvent>();
    pdus.addElement(pdu);
    if (mediumType == CONTROL_NOTHING) {
      String s = "Deliver " + pdu.getID() +
	" [" + pdu.getSource().getName() + "] - no loss";
      events.addAll(performService(s));
      transmitPDU(pdu, pdu.getDestination());
    }
    return(events);				// return empty list of events
  }

  /**
    Set user control over delivery and loss.

    @param mediumType	medium control type
   */
  public void setMediumType(int mediumType) {
    this.mediumType = mediumType;
  }

  /**
    Deliver specified PDU to destination protocol entity, and get any protocol
    events triggered by this delivery.

    @param pdu		PDU
    @param destination	PDU destination
   */

  public void transmitPDU(PDU pdu, ProtocolEntity destination) {
    destinationEvents = destination.receivePDU(pdu);
  }

}

