// SWP3Sender.java

package protocol;

import java.util.Enumeration;			// enumeration
import java.util.Vector;			// vector (list)
import support.*;				// protocol entity support

/**
  This is the class for a three-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 SWP3Sender implements ProtocolEntity, Timeouts {

  // SWP3 Sliding Window sender protocol entity

  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 Medium medium;
  private ProtocolEntity peer;
  private Vector<String> services;
  private boolean windowClosed;
  private Vector<ProtocolEvent> entityEvents;		// events from entity

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

  private void addService(String s) {
    if (services.indexOf(s) < 0)
      services.addElement(s);
  }

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

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

  public Vector<String> getServices() {
    int seq;
    int first = -1;
    int last = -1;
    // remove any previous resend actions
    for (Enumeration e = services.elements(); e.hasMoreElements(); ) {
      String s = (String)e.nextElement();
      if(s.startsWith(RESEND)) services.removeElement(s);
    }
    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
      // ensure there is no option to send new data until timeout dealt with
      services.removeAllElements();
      if (last == first)				// 1 PDU may be resent
	addService(RESEND + " DT(" + first + ")");
      else						// >1 PDU may be resent
	addService(RESEND + " DT(" + first + ") - (" + last + ")");
    }
    if (!windowClosed) {
      addService(SEND + " DT(" + seq + ")");
    }
    return(services);
  }

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

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

  public void initialise() {
    timerEnabled = new boolean[maxSeq + 1];
    for (int i = 0; i <= maxSeq; i++) timerEnabled[i] = false;
    lowerWindowEdge = 0;
    upperWindowEdge = 0;
    services = new Vector<String>();
    windowClosed = false;
    entityEvents = new Vector<ProtocolEvent>();		// empty medium 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) {
    int startIndex, endIndex;
    String seqString;
    Vector<ProtocolEvent> events = new Vector<ProtocolEvent>();
    if (s.startsWith(SEND)) {
      PDU pdu = new PDU("DT", upperWindowEdge);
      transmitPDU(pdu, peer);
      events.addElement(new ProtocolEvent(ProtocolEvent.TRANSMIT, pdu));
      removeService(s);
      upperWindowEdge = inc(upperWindowEdge);
      windowClosed = mod(upperWindowEdge - lowerWindowEdge) >= winSize;
      if (windowClosed)
	events.addElement(comment(BLOCKED));
    }
    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);
	transmitPDU(pdu, peer);
	events.addElement(new ProtocolEvent(ProtocolEvent.TIMEOUT, pdu));
      }
      removeService(s);
    }
    for (Enumeration e = entityEvents.elements();	// get medium events
	 e.hasMoreElements(); )
      events.addElement(				// add medium event
	(ProtocolEvent) e.nextElement());
    return(events);
  }

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

  private void removeService(String s) {
    if (services.isEmpty())
      return;
    for (Enumeration e = services.elements(); e.hasMoreElements(); ) {
      String s1 = (String)e.nextElement();
      if (s1.equals(s)) {
	services.removeElement(s1);
	break;
      }
    }
  }

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

  /**
    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 Vector<ProtocolEvent> receivePDU(PDU pdu) {
    Vector<ProtocolEvent> events = new Vector<ProtocolEvent>();
    if (pdu != null && pdu.type.equals("AK")) {		// respond to AK
      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;
      }
    }
    return(events);
  }

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

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

}
