// SWP3Receiver.java	(C) I. A. Robin, K. J. Turner	08/03/06

package protocol;

import java.util.Enumeration;
import java.util.Vector;
import support.*;

public class SWP3Receiver implements ProtocolEntity {

  private int maxSeq;
  private int winSize;
  private int lowerWindowEdge;
  private int upperWindowEdge;
  private PDU pduReceived;
  private boolean[] pduArrived;
  private Medium medium;
  private ProtocolEntity peer;
  private Vector<ProtocolEvent> entityEvents;		// events from entity

  public SWP3Receiver(int maxSeq, int winSize, Medium m) {
    this.maxSeq = maxSeq;
    this.winSize = winSize;
    pduArrived = new boolean[maxSeq + 1];
    medium = m;
    initialise();
  }

  private ProtocolEvent comment(String c) {
    return(new ProtocolEvent(ProtocolEvent.COMMENT, this, c));
  }

  public String getName() {
    return("Receiver");
  }

  public Vector<String> getServices() {
    Vector<String> list = new Vector<String>();
    PDU pdu = pduReceived;
    if (pdu != null && pdu.type.equals("DT")) {
      int seq = pdu.seq;
      if (isWithinWindow(seq)) {
	int s = lowerWindowEdge;
	for (int i = 0; i < winSize && pduArrived[s]; i++) {
	  s = inc(s);
	}
	list.addElement("Send AK(" + s + ")");
      }
      else						// duplicate seq. no.
	list.addElement("Send AK(" + lowerWindowEdge +")");
    }
    return(list);
  }

  private int inc(int seq) {
    return(mod(seq + 1));
  }

  public void initialise() {
    for (int i = 0; i <= maxSeq; i++) pduArrived[i] = false;
    lowerWindowEdge = 0;
    upperWindowEdge = winSize;
    pduReceived = null;
    entityEvents = new Vector<ProtocolEvent>();		// empty entity events
  }

  private boolean isWithinWindow(int seq) {
    return
      (lowerWindowEdge <= seq && seq < upperWindowEdge) ||
	(upperWindowEdge < lowerWindowEdge &&
	  (seq >= lowerWindowEdge || seq < upperWindowEdge));
  }

  private int mod(int n) {
    int base = maxSeq + 1;
    n %= base;
    return(n >= 0 ? n : n + base);
  }

  public Vector<ProtocolEvent> performService(String s) {
    Vector<ProtocolEvent> events = new Vector<ProtocolEvent>();
    if (s.startsWith("Send AK")) {
      int startIndex = s.indexOf( '(' ) + 1;
      int endIndex = s.indexOf( ')' );
      int seq = Integer.parseInt(s.substring(startIndex, endIndex));
      PDU pdu = new PDU("AK", seq);
      transmitPDU(pdu, peer);
      events.addElement(new ProtocolEvent(ProtocolEvent.TRANSMIT, pdu));
      pduReceived = null;
    }
    for (Enumeration e = entityEvents.elements(); e.hasMoreElements(); )
      events.addElement((ProtocolEvent) e.nextElement());
    return(events);
  }

  public Vector<ProtocolEvent> receivePDU(PDU pdu) {
    Vector<ProtocolEvent> events = new Vector<ProtocolEvent>();
    if (pdu != null && pdu.type.equals("DT")) {
      int seq = pdu.seq;
      if (isWithinWindow(seq)) {
	boolean alreadyBuffered = pduArrived[seq];
	boolean delivered = false;
	pduArrived[seq] = true;
	int s = lowerWindowEdge;
	// deliver sequence of data messages to user
	for (int i = 0; i < winSize && pduArrived[s]; i++) {
	  events.addElement(comment("DT(" + s + ") delivered"));
	  s = inc(s);
	}
	// clear pduArrived flags for messages passed to user
	for (int i = lowerWindowEdge; i != s; i = inc(i)) {
	  pduArrived[i] = false;
	  if (i == seq) {
	    alreadyBuffered = true;
	    delivered = true;
	  }
	}
	if (alreadyBuffered && !delivered)
	  events.addElement(comment("Ignored"));
	if (!alreadyBuffered) {
	  events.addElement(comment("Buffered"));
	  alreadyBuffered = true;
	}
	// update window edges
	lowerWindowEdge = s;
	upperWindowEdge = mod(lowerWindowEdge + winSize);
      }
      else { // duplicate sequence number received
	events.addElement(comment("Ignored"));
      }
    }
    pduReceived = pdu;
    return(events);
  }

  public void setMaxSeq(int maxSeq) {
    this.maxSeq = maxSeq;
  }

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

  public void setWindowSize(int winSize) {
    this.winSize = winSize;
  }

  public void transmitPDU(PDU pdu, ProtocolEntity dest) {
    pdu.setSource(this);
    pdu.setDestination(dest);
    entityEvents = medium.receivePDU(pdu);
  }

}
