// IPProtocol.java	(C) I. A. Robin, K. J. Turner	04/03/06

package protocol;

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

public class IPProtocol implements ProtocolEntity {

  private static final String TTL_EXPIRY = "Time-To-Live expiry: Message ID ";

  private ProtocolEntity peer;
  private IPService user;
  private Vector userEvents;
  private Medium medium;
  private String name;
  private int maxMessageSize = 200;
  private Vector<Integer> expiredMIDs;			// expired message IDs
  private Vector<IPMessage> recvFragments;		// message fragments
  private int messageID;				// current message ID

  public IPProtocol(Medium m, String name) {
    this.name = name;
    medium = m;
    initialise();
  }

  public void initialise() {
    messageID = 0;
    expiredMIDs = new Vector<Integer>();
    userEvents = new Vector();
    recvFragments = new Vector<IPMessage>();
  }

  public String getName() {
    return(name);
  }

  public void setPeer(ProtocolEntity peer) {
    this.peer = peer;
  }

  public void setUser(ProtocolEntity user) {
    this.user = (IPService)user;
  }

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

  // if all fragments of message with message identifier mID
  // have arrived, return total message size, else return 0

  private int completeMessageSize(int mID) {
    int expectedSize = 0;
    int sizeSoFar = 0;
    for (Enumeration e = recvFragments.elements(); e.hasMoreElements(); ) {
      IPMessage ipd = (IPMessage)e.nextElement();
      if (ipd.messageID == mID) {
	sizeSoFar += ipd.size;
	if (!ipd.hasMoreFrags()) {
	  // last fragment has "More Fragments" flag unset; get
	  // message size from offset and size of last fragment
	  expectedSize = ipd.fragOffset + ipd.size;
	}
      }
    }
    if (expectedSize > 0 && sizeSoFar == expectedSize) return(expectedSize);
    return(0);						// message incomplete
  }

  private void removeFragments(int mID) {
    // remove all message fragments with message identifier mID
    Enumeration e = recvFragments.elements();
    while (e.hasMoreElements()) {
      IPMessage ipd = (IPMessage)e.nextElement();
      if (ipd.messageID == mID) {
	recvFragments.removeElement(ipd);
	e = recvFragments.elements();
      }
    }
  }

  // add new message ID to expired list
  // but not if this message ID already appears in list

  private void addExpired(int mID) {
    Integer iMID = new Integer(mID);
    if (expiredMIDs.indexOf(iMID) < 0)
      expiredMIDs.addElement(iMID);
  }

  // return true if TTL for message ID has expired

  private boolean expired(int mID) {
    Integer iMID = new Integer(mID);
    return(expiredMIDs.indexOf(iMID) >= 0);
  }

  // find oldest (ie smallest) message identifier of
  // any message fragment received so far
  // return -1 if no messages have been received yet

  private int oldestMessageID() {
    if (recvFragments.isEmpty()) return(-1);
    IPMessage ipd = (IPMessage)recvFragments.firstElement();
    int oldestSoFar = ipd.messageID;
    for (Enumeration e = recvFragments.elements(); e.hasMoreElements(); ) {
      ipd = (IPMessage)e.nextElement();
      oldestSoFar = Math.min(oldestSoFar, ipd.messageID);
    }
   return(oldestSoFar);
  }

  public Vector<ProtocolEvent> receivePDU(PDU pdu) {
    Vector<ProtocolEvent> events = new Vector<ProtocolEvent>();
    String pduType = "";
    int size;
    if (pdu != null) {
      pduType = pdu.type;
      if (pduType.equals("DatReq")) {			// DatReq from user
	int messageSize = pdu.size;
	// fragment message
	for (int offset = 0; offset < messageSize; offset += maxMessageSize) {
	  size = Math.min(messageSize - offset, maxMessageSize);
	  IPMessage fragment = new IPMessage("DT", messageID, offset, size);
	  fragment.setSDU(pdu.getSDU());
	  fragment.setMoreFrags(offset + maxMessageSize < messageSize);
	  transmitPDU(fragment, peer);
	  events.addElement(
	    new ProtocolEvent(ProtocolEvent.TRANSMIT, fragment));
	}
	messageID++;
      }
      if (pduType.equals("DT")) {   // data PDU received
	// simulate reconstruction of message fragments
	IPMessage ipd = (IPMessage)pdu;
	int mID = ipd.messageID;
	if (!expired(mID)) recvFragments.addElement(ipd);
	size = completeMessageSize(mID);
	if (size > 0) {					// all fragments rcvd.
	  PDU pduSent = new PDU("DatInd", pdu.getSDU());
	  transmitPDU(pduSent, user);
	  events.addElement(new ProtocolEvent(ProtocolEvent.DELIVER, pduSent));
	  // dispose of fragments of delivered message:
	  removeFragments(mID);
	}
      }
    }
    return(events);
  }

  public void transmitPDU(PDU pdu, ProtocolEntity dest) {
    pdu.setSource(this);
    pdu.setDestination(dest);
    if (dest == peer)
      userEvents = medium.receivePDU(pdu);
    else
      userEvents = user.receivePDU(pdu);
  }

  public Vector<String> getServices() {
    Vector<String> services = new Vector<String>();
    int oldestMID = oldestMessageID();
    if (oldestMID >= 0)
      services.addElement(TTL_EXPIRY + oldestMID);
    return(services);
  }

  public Vector<ProtocolEvent> performService(String s) {
    Vector<ProtocolEvent> events = new Vector<ProtocolEvent>();
    if (s.startsWith(TTL_EXPIRY)) {
      int mIDIndex = s.indexOf("ID") + 3;
      int mID = Integer.parseInt(s.substring(mIDIndex));
      removeFragments(mID);
      addExpired(mID);
      events.addElement(
	new ProtocolEvent(ProtocolEvent.COMMENT, this,
	  "TTL expiry " + mID));
    }
    for (Enumeration e = userEvents.elements(); e.hasMoreElements(); )
      events.addElement((ProtocolEvent) e.nextElement());
    return(events);
  }

}
