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

package protocol;

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

public class SWP5Receiver implements ProtocolEntity {

  private int maxSeq;
  private int winSize;
  private int lowerWindowEdge;
  private int upperWindowEdge;
  private boolean[] pduArrived;
  private PDU[] recvBuffer;
  private SWP5Service user;
  private ProtocolEntity peer;
  private Medium medium;
  private Vector userEvents;
  private int ackSeq;					// next AK seq. no.

  public SWP5Receiver(int maxSeq, int winSize, Medium m) {
    setMaxSeq(maxSeq);
    setWindowSize(winSize);
    medium = m;
    initialise();
  }

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

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

  public void initialise() {
    recvBuffer = new PDU[maxSeq + 1];
    pduArrived = new boolean[maxSeq + 1];
    for (int i = 0; i <= maxSeq; i++) pduArrived[i] = false;
    lowerWindowEdge = 0;
    upperWindowEdge = winSize;
    ackSeq = -1;
  }

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

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

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

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

  public String getName() {
    return("Protocol B");
  }

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

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

  public Vector<ProtocolEvent> receivePDU(PDU pdu) {
    Vector<ProtocolEvent> events = new Vector<ProtocolEvent>();
    if (pdu != null && pdu.type.equals("DT")) {
      ackSeq = -1;
      int seq = pdu.seq;
      if (isWithinWindow(seq)) {
	pduArrived[seq] = true;
	recvBuffer[seq] = pdu;
	seq = lowerWindowEdge;
	// deliver sequence of data messages to user
	for (int i = 0; i < winSize && pduArrived[seq]; i++) {
	  PDU pduSent = new PDU("DatInd", recvBuffer[seq].getSDU());
	  transmitPDU(pduSent, user);
	  events.addElement(
	    new ProtocolEvent(ProtocolEvent.DELIVER, pduSent));
	  seq = inc(seq);
	}
	ackSeq = seq;
	// clear pduArrived flags for messages passed to user
	for (int i = lowerWindowEdge; i != seq; i = inc(i))
	  pduArrived[i] = false;
	// update window edges
	lowerWindowEdge = seq;
	upperWindowEdge = mod(lowerWindowEdge + winSize);
      }
      else {						// duplicate seq. no.
	ackSeq = lowerWindowEdge;
	events.addElement(comment("Ignored"));
      }
    }
    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>();
    if (ackSeq >= 0)
      services.addElement("Send AK(" + ackSeq + ")");
    return(services);
  }

  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));
      // send AK for next sequence number required
      PDU ackPDU = new PDU("AK", seq);
      transmitPDU(ackPDU, peer);
      events.addElement(new ProtocolEvent(ProtocolEvent.TRANSMIT, ackPDU));
      ackSeq = -1;
    }
    for (Enumeration e = userEvents.elements(); e.hasMoreElements(); )
      events.addElement((ProtocolEvent) e.nextElement());
    return(events);
  }

}
