// SWP5Sender.java

package protocol;

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

/**
  This is the class for a five-column Sliding Window Protocol sender.

  @author	Iain A. Robin, Kenneth J. Turner
  @version	1.0 (1st September 1999, IAR): initial version
  <br/>		1.4 (9th March 2006, KJT): updated for JDK 1.5
  <br/>		1.5 (27th July 2010, KJT): minor tidying
*/
public class SWP5Sender implements ProtocolEntity, Timeouts {

  private static final String SEND    = "Send";
  private static final String RESEND  = "Timeout - resend";
  private static final String BLOCKED = "Blocked";
  private static final String CLEAR   = "Clear";

  private int maxSeq;					// maximum seq. no.
  private int winSize;					// transmit window
  private int lowerWindowEdge;				// ack seq. no.
  private int upperWindowEdge;				// next seq. no.
  private boolean[] timerEnabled;
  private PDU[] recvBuffer;
  private Medium medium;
  private SWP5Service user;
  private ProtocolEntity peer;
  private Vector userEvents;
  private boolean windowClosed;

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

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

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

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

  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));
  }

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

  /**
    Set the timer for a specified PDU.

    @param pdu		PDU
    @param enabled	whether the timer is enabled
  */
  public void setTimer(PDU pdu, boolean enabled) {
    timerEnabled[pdu.seq] = enabled;
  }

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

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

  private void releaseTimers(int lower, int upper) {
    for (int seq = lower; seq != upper; seq = inc(seq)) {
      timerEnabled[seq] = false;
    }
  }

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

  public boolean hasTimer(String type) {
    return(true);
  }

  public Vector<ProtocolEvent> receivePDU(PDU pdu) {
    Vector<ProtocolEvent> events = new Vector<ProtocolEvent>();
    String pduType = "";
    if (pdu != null) pduType = pdu.type;
    if (pduType.equals("DatReq")) {			// DatReq from user
      PDU pduSent = new PDU("DT", upperWindowEdge, pdu.getSDU());
      recvBuffer[upperWindowEdge] = pdu;
      transmitPDU(pduSent, peer);
      events.addElement(new ProtocolEvent(ProtocolEvent.TRANSMIT, pduSent));
      upperWindowEdge = inc(upperWindowEdge);
      windowClosed = mod(upperWindowEdge - lowerWindowEdge) >= winSize;
      if (windowClosed) {
	events.addElement(comment(BLOCKED));
      }
    }
    if (pduType.equals("AK")) {				// AK PDU from peer
      int seq = pdu.seq;
      if (isWithinWindow(mod(seq - 1))) {
	releaseTimers(lowerWindowEdge, seq);
	lowerWindowEdge = seq; // update sender window
	if (windowClosed) {
	  // Send window was full, indicate it is now clear:
	  events.addElement(comment(CLEAR));
	}
	windowClosed = false; // and set flag appropriately
      }
    }
    user.setOKToSend(!windowClosed);
    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 seq;
    int first = -1;
    int last = -1;
    for (seq = lowerWindowEdge; isWithinWindow(seq); seq = inc(seq)) {
      if (timerEnabled[seq]) {
	if (first < 0)
	  first = seq;
	last = seq;
      }
    }
    if (first >= 0) {					// >=1 PDU may time out
      if (last == first) // only one PDU may be resent
	services.addElement(RESEND + " DT(" + first + ")");
      else // more than one PDU may be resent
	services.addElement(RESEND + " DT(" + first + ") - (" + last + ")");
    }
    return(services);
  }

  public Vector<ProtocolEvent> performService(String s) {
    int startIndex, endIndex;
    String seqString;
    Vector<ProtocolEvent> events = new Vector<ProtocolEvent>();
    if (s.startsWith(RESEND)) {
      // get sequence number of first data PDU to be resent
      startIndex = s.indexOf( '(' ) + 1;
      endIndex = s.indexOf( ')' );
      seqString = s.substring(startIndex, endIndex);
      int seq1 = Integer.parseInt(seqString);
      // get sequence number of last data PDU to be resent
      startIndex = s.lastIndexOf( '(' ) + 1;
      endIndex = s.lastIndexOf( ')' );
      seqString = s.substring(startIndex, endIndex);
      int seq2 = Integer.parseInt(seqString);
      releaseTimers(seq1, inc(seq2));
      // retransmit all unacknowledged data PDUs already sent and timed out
      for (int p = seq1; p != inc(seq2); p = inc(p)) {
	PDU pdu = new PDU("DT", p, recvBuffer[p].getSDU());
	transmitPDU(pdu, peer);
	events.addElement(new ProtocolEvent(ProtocolEvent.TIMEOUT, pdu));
      }
    }
    for (Enumeration e = userEvents.elements(); e.hasMoreElements(); )
      events.addElement((ProtocolEvent) e.nextElement());
    return(events);
  }

}
