// IPMedium.java

package protocol;

import java.util.*;
import support.*;

/**
  This is the class for an IP 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
  <br/>		1.5 (24th July 2010, KJT): minor tidying
*/
public class IPMedium extends Medium {

  private int maxMessageSize = 100;		// maximum message size
  private boolean misordering = true;		// misordering allowed?
  private float lossRate = 0.2f;		// message loss rate
  private Vector<Float> randomNumbers;		// random numbers
  private int randomIndex;			// random number index

  public IPMedium() {
    super();					// construct superclass
    randomNumbers = new Vector<Float>();	// initialise random numbers
  }

  public void initialise() {
    super.initialise();
    randomIndex = 0;				// initialise random index
  }

  public void setMaxMessageSize(int size) {
    maxMessageSize = size;
  }

  public void setMisordering(boolean b) {
    misordering = b;
  }

  public void setLossRate(float rate) {
    lossRate = rate;
  }

  // randomise the order of the elements of vector v

  private void misorder(Vector<ProtocolEvent> v) {
    Vector<ProtocolEvent> t = new Vector<ProtocolEvent>(v);
    int size = v.size();
    v.removeAllElements();
    while (size > 0) {
      // choose an element of t at random and add it to v
      int index = Math.round(--size * random());
      v.addElement(t.elementAt(index));
      t.removeElementAt(index);
    }
  }

  // Identify and return PDU currently on channel with
  // type and parameters matching those described in given string
  // Parameters are:
  // ( messageID [,fragment offset] [,D] [,M] )
  // where D and M are booleans
  // "D" indicates Don't Fragment
  // "M" indicates More Fragments Follow

  protected PDU getMatchingPDU(String s) {
    IPMessage ipd;
    int messageID = -1;
    int offset = -1;
    // extract name of source entity for this datagram:
    int sourceStart = s.indexOf('[') + 1;
    int sourceEnd = s.indexOf( ']' );
    String sourceName = s.substring(sourceStart, sourceEnd);
    // get type and parameters of PDU from action string
    // PDU type starts after first space following "Deliver" or "Lose":
    int typeStart = s.indexOf(' ') + 1;
    int typeEnd = s.indexOf( '(' );
    if (typeEnd < 0) typeEnd = sourceStart - 2;
    String type = s.substring(typeStart, typeEnd);
    String[] params = getParams(s);
    messageID = Integer.parseInt(params[0]);
    if (params.length > 1) { // there is a fragment offset parameter
      try {
	offset = Integer.parseInt(params[1]);	// fragment offset
      }
      catch (NumberFormatException e) {		// not offset parameter - ignore
      }
    }
    // try to match IP datagram type and messageID, and fragment offset where
    // applicable, with a datagram currently on channel:
    for (Enumeration e = pdus.elements(); e.hasMoreElements(); ) {
      ipd = (IPMessage)e.nextElement();
      if (ipd != null && ipd.type.equals(type)
	  && ipd.getSource().getName().equals(sourceName)
	  && messageID == ipd.messageID
	  && (offset < 0 || (offset >= 0 && offset == ipd.fragOffset)))
	  return(ipd);
    }
    return(null); // no match found
  }

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

  public Vector<String> getServices() {
    Vector<String> list = new Vector<String>();
    for (Enumeration e = pdus.elements(); e.hasMoreElements(); ) {
      PDU pdu = (PDU)e.nextElement();
      if (pdu != null) {
	String s = pdu.getID();
	s += " [" + pdu.getSource().getName() + "]";
	if (misordering)
	  list.addElement("Deliver " + s + " - fragmentation/misordering");
	else
	  list.addElement("Deliver " + s + " - fragmentation");
	list.addElement("Lose " + s + " - congestion/error");
      }
    }
    return(list);
  }

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

  public Vector<ProtocolEvent> performService(String s) {
    Vector<ProtocolEvent> events = new Vector<ProtocolEvent>();
    IPMessage frag;
    IPMessage ipd = (IPMessage)getMatchingPDU(s);
    if (ipd == null) return(events);
    if (s.startsWith("Deliver")) {
      ProtocolEntity destination = ipd.getDestination();
      // Split message into fragments (up to size maxMessageSize)
      int messageSize = ipd.size;
      for (int relOffset = 0; relOffset < messageSize;
			      relOffset += maxMessageSize) {
	int size = Math.min(messageSize - relOffset, maxMessageSize);
	int offset = ipd.fragOffset + relOffset;
	frag = new IPMessage("DT", ipd.messageID, offset, size);
	frag.setSDU(ipd.getSDU());
	frag.setMoreFrags(ipd.hasMoreFrags() ||
			       relOffset + maxMessageSize < messageSize);
	frag.setSource(ipd.getSource());
	frag.setDestination(destination);
	// decide randomly whether to lose fragment
	if (random() >= lossRate)
	  events.addElement(new ProtocolEvent(ProtocolEvent.FRAGMENT, frag));
	else
	  events.addElement(new ProtocolEvent(ProtocolEvent.LOSE, frag));
      }
      if (misordering)
	misorder(events);		// transmit fragments in random order
      for (Enumeration e = events.elements(); e.hasMoreElements(); ) {
	ProtocolEvent event = (ProtocolEvent) e.nextElement();
	if (event.getType() == ProtocolEvent.FRAGMENT) {
	  IPMessage ipdSent = (IPMessage)event.getPDU();
	  transmitPDU(ipdSent, destination);
	}
      }
      pdus.removeElement(ipd);
    }
    if (s.startsWith("Lose")) {
      destinationEvents.removeAllElements();
      pdus.removeElement(ipd);
      events.addElement(new ProtocolEvent(ProtocolEvent.LOSE, ipd));
    }
    // tack on any events triggered by delivery of a message
    // to its destination protocol entity
    for (Enumeration e = destinationEvents.elements(); e.hasMoreElements(); )
      events.addElement((ProtocolEvent) e.nextElement());
    return(events);
  }

  /**
    Return a random float. If the list of randoms contains an unused value then
    use this, otherwise generate a fresh value and add to the list. This allows
    random numbers to be generated consistently when simulation steps are
    undone/redone.

    @return		random number in the range (0..1]
  */
  private float random() {
    if (randomIndex < randomNumbers.size())	// index within list?
      return(					// return this random
	((Float)randomNumbers.elementAt(randomIndex++)).floatValue());
    else {					// index not within list
      Float random = new Float(Math.random());	// get random number
      randomNumbers.addElement(random);		// add random to list
      randomIndex++;				// increment list index
      return(random.floatValue());		// return rand number
    }
  }

}
