/*
 *     Updated by Matthew Shatley and Chris Hoffman
 *     for Professor Paul Amer (amer@udel.edu)
 *     University of Delaware (2008)
 
 *    Java Applet Demonstration of Selective Repeat Protocol.
 *    Coded by Shamiul Azom as  project assigned by 
 *    Prof. Martin Reisslein, Arizona State University
 *    Course No. EEE-459/591. Spring 2001
 
 
 *     This Applet was designed to be used in conjunction with
 *     "Computer Networking: A Top Down Approach"
 *     by James Kurose & Keith Ross.
 *     Terminology and specifications are based upon their description of the
 *     Selective Repeat protocol in chapter 3, section 4.
 
 
 *     A note on magic numbers: Magic numbers are horrible to have in your code in general.
 *     However, the graphics components of this applet provided no good way to remove the
 *     magic numbers from the code as locations for objects are specified in pixel coordinates. 
 *     We apologize in advance for any confusion this may cause in reading the code. 
 *     
 */

import java.applet.Applet;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

public class SelectiveRepeat extends Applet implements ActionListener, Runnable {
    
    private static final int ADVANCE_PACKET = 5;
    // Default values of parameters for animation
    // sender_window_len_def the sender can have a maximum of 5 outstanding
    // un-acknowledged packets
    final int sender_window_len_def = 5;
    // how many packets the receiver can hold in memory without delivering data
    // in the case of SelectiveRepeat we can hold 1(or the current packet) in
    // memory
    // if another packet arrives the one in memory is discarded
    final int receiver_window_len = 5;
    // GUI components to describe how the Simulation should be drawn
    final int pack_width_def = 10;
    final int pack_height_def = 30;
    final int h_offset_def = 100;
    final int v_offset_def = 50;
    final int v_clearance_def = 300;
    
    // used for timeout values, thread.sleep() is specified in milliseconds
    // so we convert to seconds for timeout processing.(1000 milliseconds = 1
    // second)
    final int TIMEOUT_MULTIPLIER = 1000;
    
    final int MIN_FPS = 3;
    final int FPS_STEP = 2;
    final int DESELECTED = -1;
    final int DEFAULT_FPS = 5;
    // default to 20 Packets if no value is supplied
    final int total_Packet_def = 20;
    // 25 sec default timeout for retransmissions
    final int time_out_sec_def = 25;
    
    // Default colors of different Packets
    // these have been matched as closely to the the text as possible
    // Order of color values Red, Green, Blue
    final Color unack_color = new Color(204, 230, 247);
    final Color ack_color = Color.yellow;
    final Color sel_color = Color.green;
    final Color roam_pack_color = new Color(204, 230, 247);
    final Color roam_ack_color = Color.yellow;
    final Color dest_color = Color.red;
    final Color received_ack = new Color(37, 135, 234);
    
    // base - our sending base - the next expected Packet to be received
    // nextseqnum - the next sequence number that will be given to a newly
    // created Packet
    // selected - the index of the currently selected Packet in transmission
    // lastKnownSucPacket - LAST KNOWN SUCcessful PACKET received by receiving
    // node
    int base, receiver_base, nextseqsum, fps, selected = DESELECTED, timeout,
	timeoutPacket, lastKnownSucPacket;
    boolean timerFlag, timerSleep;
    
    // define our buttons for actions available to be taken by the user
    Button send, stop, fast, slow, kill, reset;
    /*
     * 2 threads run for the applet gbnTread - runs to create our animation and
     * process Packets timerThread - created and sleeps for a specified period
     * of time. On wake up performs timeout processing A timeout causes all of
     * the outstanding Packets to be re-transmitted. NOTE: The text(Computer
     * Networking: A Top Down Approach) specified a per Packet timer, however
     * this is rarely implemented as there is a significant overhead in using
     * that many timers. Logically, the only Packet that would ever timeout is
     * the left most edge of the sending window as this has been in transmission
     * the longest. Since a per Packet timer system is not implemented in
     * practice we have simulated per Packet timers per the books description
     * while using only a single timer.
     */
    Thread gbnThread, timerThread;
    
    TextArea output; // output variable used to write information in the text
    // box
    Dimension offDimension;
    Image offImage; // implements double buffering to proved a smoother
    // animation
    Graphics offGraphics; // graphics component used for drawing
    SelectiveRepeatPacket sender[]; // sender array - holds the Packets being sent
    
    // Declaring properties of our window
    int window_len, pack_width, pack_height, h_offset, v_offset, v_clearance,
	total_Packet, time_out_sec;
    
    /***************************************************************************
     * Method init *
     * ************************************************************************
     * Purpose: init method to set up applet for running - first method called
     * on loading the code. Attempts to load parameters passed from HTML code
     * contained in the website. If there is an error or no parameters are
     * provided then the default values(declared above) are used. Global
     * variables used: sender - array holding the Packets and the corresponding
     * acks for the Packets sent in the applet output - console window for
     * applet activities & messages
     **************************************************************************/
    public void init() {
	
	// prevents layout manager from adjusting components in the applet
	// The buttons made it easier to deal with pixel coordinates
	// than recode for layout manager
	setLayout(null);
	output = new TextArea(150, 150); // setup output box
	// create text area for console output box
	output.setBounds(0, 400, 650, 250); // set bounds for output box
	output.setEditable(false); // prevent user from editing output written
	// to console
	add(output); // tell applet to draw our output box
	
	setupSimulationParams();
	
	base = 0; // Defining our base to be 0 the first Packet number
	// expected
	receiver_base = 0; // Set the receiver base number to 0, which is the
	// first index in the receiver array
	nextseqsum = 0; // Defining next sequence number for next Packet sent.
	fps = 5; // Defining default Frame per Second for our animation
	
	// create a shared array, used for both the sender and the receiver
	// nodes.
	// all Packets will be created and processed from this array
	sender = new SelectiveRepeatPacket[total_Packet];
	
	// Defining the buttons - creates the button and text to go on the
	// button
	send = new Button("Send New");
	// set the command to be performed when button is pressed this command
	// is used
	// to determine which button was pressed in the actionPerformed method
	send.setActionCommand("rdt");
	// on button pushed the actionPerformed method of this class is called
	// and appropriate action is taken depending on the button pressed
	send.addActionListener(this);
	// set the size and location of this button (of form (x, y, width,
	// length) - this is specified in pixel coordinates
	send.setBounds(0, 0, 90, 20);
	
	// same structure as above
	stop = new Button("Pause");
	stop.setActionCommand("stopanim");
	stop.addActionListener(this);
	stop.setBounds(90, 0, 90, 20);
	
	fast = new Button("Faster");
	fast.setActionCommand("fast");
	fast.addActionListener(this);
	fast.setBounds(180, 0, 90, 20);
	
	slow = new Button("Slower");
	slow.setActionCommand("slow");
	slow.addActionListener(this);
	slow.setBounds(270, 0, 90, 20);
	
	kill = new Button("Kill Packet/Ack");
	kill.setActionCommand("kl");
	kill.addActionListener(this);
	kill.setEnabled(false);
	kill.setBounds(360, 0, 90, 20);
	
	reset = new Button("Reset");
	reset.setActionCommand("rst");
	reset.addActionListener(this);
	reset.setBounds(450, 0, 90, 20);
	
	// Adding the buttons to our applet window so they can be rendered and
	// used
	add(send);
	add(stop);
	add(fast);
	add(slow);
	add(kill);
	add(reset);
	
	// print out message about the new authors of the code
	output.append("-- SelectiveRepeat Applet\n");
	output.append("-- Written By Matt Shatley & Chris Hoffman under the advisement of Professor Paul Amer\n");
	output.append("-- University of Delaware, 2008\n\n");
	
	// tell user we are ready to begin demonstrating Go Back N
	output.append("-Ready to run. Press 'Send New' button to start.\n");
	
    }// End init() method
    
    /***************************************************************************
     *                           Method Start                                  *
     * *************************************************************************
     * Purpose: Start method required for implementing multi-threading. Start is
     * the first method called by a thread after creation. Procedures Calling:
     * run Procedures Called: run Global Variables Used: gbnThread - creates new
     * thread for first execution and starts thread(calling run method of
     * thread)
     **************************************************************************/
    public void start() {
	// Creating GBNThread and starting execution. After start method is run
	// the run method of this class is called
	if (gbnThread == null)
	    gbnThread = new Thread(this);
	gbnThread.start();
    }// End start() method
    
    /***************************************************************************
     * Method run *
     * **************************************************************** Purpose:
     * Run method required by runnable interface. Determines which thread is
     * calling and process accordingly. gbnThread produces the animation for the
     * applet. The timerThread sleeps until timeout processing is needed to
     * retransmit the sending window. Original code by Shamiul Azom
     * Procedures/Functions Called: check_upto_n, paint/update(indirectly)
     * Procedures/Functions Calling: main, start Local variables: currentThread -
     * holds the identifier for the currently executing thread i - temporary
     * variable used for loop control Global variables used: sender - array
     * holding the Packets and the corresponding acks for the Packets sent in
     * the applet output - console window to display information about the
     * applet activities.
     * 
     * lastKnownSucPacket - holds the number of the last successful Packet to
     * arrive gbnThread - thread to advance animation
     **************************************************************************/
    public void run() {
	//force garbage collection - depending on garbage collection threads may be left
	//executing even though they have been killed leading to unexpected behavior
	System.gc();
	/*
	 * Figure out which thread called this run method since both the
	 * SelectiveRepeat simulation thread and the timer thread call the same
	 * run method. We must do this because there cannot be 2 run methods in
	 * the same class.
	 */
	boolean stopCheck = false;
	if (sender[total_Packet - 1] != null) {
	    for (int i = total_Packet - window_len; i < total_Packet; i++)
		if (!sender[i].acknowledged) {
		    stopCheck = false;
		    break;
		} else {
		    stopCheck = true;
		}
	    if (stopCheck) {
		output.append("Data Transferred - Simulation completed.\n");
		gbnThread = null;
		return;
	    }
	}
	Thread currenthread = Thread.currentThread();
	while (currenthread == gbnThread)
	    // While the animation is running
	    if (onTheWay(sender)) // Checks if any of the Packets are
		// traveling
		{
		    // Iterates through all of the Packet numbers (in this case from
		    // 0 to 20)
		    for (int i = 0; i < total_Packet; i++) {
			// If the sender array for index[Packet number] is not null,
			// do the following, else do nothing
			if (sender[i] != null) {
			    // If the sender array for index[Packet number] is
			    // marked as roaming, do the following, else do nothing
			    if (sender[i].on_way) {
				// If the sender array for index[Packet number]'s
				// Packet position is not yet at its destination
				// increase the Packets position by 5 and call a
				// repaint to essentially move the Packet.
				if (sender[i].Packet_pos < (v_clearance - pack_height))
				    sender[i].Packet_pos += 5;
				// Otherwise the PACKET reached its destination, in
				// which case do the following:
				else if (sender[i].Packet_ack) {
				    // Set the Packets reached destination attribute
				    // to true
				    sender[i].reached_dest = true;
				    // Check to see if the Packet arrived in order
				    if (check_upto_n(i)) {
					sender[i].Packet_pos = pack_height + 5;
					sender[i].Packet_ack = false;
					
					if (sender[i].buffered
					    || sender[i].acknowledged) {
					    output.append("(R) - Packet " + i + " received. Selective acknowledge for only Packet " + i + " sent.\n");
					    sender[i].received = true;
					} else if (!sender[i].received) {
					    output.append("(R) - Packet " + i + " received. Selective acknowledge for only Packet " + i + " sent. Packet " + i + " delivered to application.\n");
					    sender[i].received = true;
					} else
					    output.append("(R) - Packet " + i + " received out of order. Selective acknowledge for only Packet " + i + " sent again(DUPACK)\n");
					sender[i].received = true;
					deliverBuffer(i);
				    }
				    
				    // if Packet is already acknowledged then we
				    // know its duplicate
				    // in response to a lost ack
				    else if (sender[i].acknowledged) {
					sender[i].Packet_pos = pack_height + 5;
					sender[i].Packet_ack = false;
					output.append("(R) - Packet " + i + " received. Selective acknowledge for only Packet " + i + " sent.\n");
					sender[i].received = true;
					deliverBuffer(i);
					
				    } else {
					sender[i].buffered = true;
					sender[i].Packet_pos = pack_height + 5;
					sender[i].Packet_ack = false;
					
					output.append("(R) - Packet " + i + " received out of order.  Packet buffered. Selective acknowledge for only Packet " + i + " sent.\n");
					sender[i].received = true;
					
					deliverBuffer(i);
					if (i == selected) {
					    selected = -1;
					    kill.setEnabled(false);
					}
				    }
				} else if (!sender[i].Packet_ack) {
				    // End sender[i].Packet_ack
				    // Otherwise the ACK reached its destination, in
				    // which case do the following:
				    output.append("(S) - Selective ACK for only Packet " + i + " received. Timer for Packet " + i + " stopped.\n");
				    sender[i].on_way = false;
				    // In order check
				    if (check_upto_n(i)) {
					sender[i].acknowledged = true;
					sender[i].buffered = false;
				    } else {
					sender[i].acknowledged = true;
					sender[i].buffered = true;
				    }
				    
				    if (i == selected) {
					selected = -1;
					kill.setEnabled(false);
				    }
				    
				    timerThread = null; // resetting timer thread
				    
				    // deliverBuffer();
				    
				    // Iterate from the base value to the end (from
				    // base to 20 in this case)
				    // Checking for buffered Packets, in which case
				    // deliver all of the ones found.
				    for (int k = base; k < total_Packet; k++) {
					if (sender[k] != null) {
					    if (sender[base].acknowledged) {
						sender[base].buffered = false;
						if (k + window_len < total_Packet)
						    base = base + 1;
						
					    }
					} else
					    break;
				    }
				    if (nextseqsum < base + window_len)
					send.setEnabled(true);
				    
				    if (base != nextseqsum) {
					timerThread = new Thread(this);
					timerSleep = true;
					timerThread.start();
				    }
				}// End !sender[i].Packet_ack
			    }// End sender[i] .onway
			}// End sender[i] != null
		    }// End for loop
		    repaint();
		    
		    try {
			Thread.sleep(1000 / fps);
		    } catch (InterruptedException e) {
			System.out.println("Help");
		    }
		    
		} else
		    gbnThread = null;

	// Timer thread restransmission of Packets
	while (currenthread == timerThread)
	    if (timerSleep) {
		timerSleep = false;
		try {
		    Thread.sleep(time_out_sec * 1000);
		} catch (InterruptedException e) {
		    System.out.println("Timer interrupted.");
		}
	    } else
		retransmitOutstandingPackets();
    }
    
    /***************************************************************************
     *                         Method deliverBuffer                            *
     * *************************************************************************
     * Purpose: Handles the delivery of buffered packets at the receiver.
     * calling: run Procedures called: none Global variables used: sender[] -
     * access packet information Local variables used: j, k, l - loop control
     * variables PacketNumber - process up to this index in sender
     **************************************************************************/
    void deliverBuffer(int PacketNumber) {
	int j = 0;
	
	// Find our first buffered Packet in our array
	while (j < PacketNumber) {
	    // error - all Packets up to PacketNumber should be created
	    // if not something has gone horribly wrong
	    if (sender[j] == null)
		return;
	    // if Packet is ackd everythings fine keep looping
	    else if (sender[j].acknowledged) {
		sender[j].buffered = false;
		j++;
		// else it must be buffered or in transmission stop here
		// this is our first possible buffered Packet
	    } else
		break;
	}
	// above loop stops on last acked packet + 1
	// adjust count to make sure we start check at appropriate count
	// test > 0 to prevent index out of bounds
	if (j > 0)
	    j--;
	for (int k = j; k < total_Packet; k++) {
	    // prevent indexing out of bounds
	    if (sender[k] == null)
		break;
	    // if packet is buffered deliver to application and advance window
	    else if (sender[k].buffered) {
		sender[k].buffered = false;
		// sender[k].acknowledged = true;
		output.append("(R) - Buffered Packet " + k + " delivered to application.\n");
		
		// if this packet is ack'd already advance
	    } else if (sender[k].acknowledged) {
		sender[k].acknowledged = true;
		sender[k].buffered = false;
	    } else if (!sender[k].Packet_ack) {
		sender[k].buffered = false;
		// if Packet is buffered deliver to application
		// and increment receiver window
	    } else
		break;
	}
	int count = 0;
	for (int i = 0; i < total_Packet; i++)
	    if (sender[i] != null) {
		if (sender[i].received){
		    if (i + 1 <= (total_Packet - receiver_window_len))
			count = i + 1;
		}else 
		    break;
	    } else
		break;
	receiver_base = count;
    }
    
    /***************************************************************************
     *                  Method retransmitOutstandingPacket                     *
     * *************************************************************************
     * Purpose: handles transmission of Packets when a timeout occurs Procedure
     * calling: run(called by timerThread) Procedures called: none Global
     * variables used: sender[] - to set up params for retransmission timerSleep -
     * to reset timer value GBNThread - set animation thread for retransmission
     * output - output messages to user about retransmission base - number of
     * left-most Packet in the sending window Local variables: n - used as loop
     * control variable
     **************************************************************************/
    private void retransmitOutstandingPackets() {
	int retransmitPacket = 0;
	// after the timerThread wakes up process the Packets in sender
	// array from the base of our window (the leftmost edge)
	for (int n = base; n < base + window_len; n++)
	    if (sender[n] != null)
		if (!sender[n].acknowledged && !sender[n].buffered) {
		    sender[n].on_way = true;
		    sender[n].Packet_ack = true;
		    sender[n].Packet_pos = pack_height + 5;
		    retransmitPacket++;
		} else if (!sender[n].acknowledged && sender[n].buffered) {
		    sender[n].on_way = true;
		    sender[n].Packet_ack = true;
		    sender[n].Packet_pos = pack_height + 5;
		    retransmitPacket++;
		}
	timerSleep = true;
	if (gbnThread == null) {
	    gbnThread = new Thread(this);
	    gbnThread.start();
	}
	if (retransmitPacket == 0) {
	    timerThread = null;
	} else
	    output.append("(S) - Timeout occurred for Packet(s). Timer(s) restarted for Packet(s). \n");
	
    }
    
    /***************************************************************************
     *                     Method setupSimulationParams                        *
     * *************************************************************************
     * Purpose: Extract simulation parameters from the HTML page the applet is
     * being executed from. If the parameter is supplied convert to value to
     * integer and check for greater than 0(less than 0 will throw exceptions)
     * if the value supplied is in range, assign that value to the simulation
     * parameter Global variables used: window_len,pack_widt, pack_height,
     * h_offset, v_offset, v_clearance, total_Packet, time_out_sec
     **************************************************************************/
    private void setupSimulationParams() {
	
	String strWinLen, strPackWd, strPackHt, strHrOff, strVtOff, strVtClr, strTotPack, strTimeout;
	
	// Start collecting parameters from HTML the applet is called from
	strWinLen = getParameter("window_length");
	strPackWd = getParameter("Packet_width");
	strPackHt = getParameter("Packet_height");
	strHrOff = getParameter("horizontal_offset");
	strVtOff = getParameter("vertical_offset");
	strVtClr = getParameter("vertical_clearance");
	strTotPack = getParameter("total_Packets");
	strTimeout = getParameter("timer_time_out");
	
	// try to retrieve the expected parameters we read in from above
	try {
	    // check if current param was supplied in HTML page
	    if (strWinLen != null) {
		// if param was supplied convert value to integer value
		window_len = Integer.parseInt(strWinLen);
		// check if value supplied is greater than 0 (negative or 0 will
		// cause simulation errors)
		// conditional assignment - if window_leng is greater than 0,
		// window_len keeps its current value otherwise the default
		// value(sender_window_len_def) is uesd
		window_len = (window_len > 0) ? window_len: sender_window_len_def;
	    } else
		// if param was not supplied use default value
		window_len = sender_window_len_def;
	    
	    // same structure as above
	    if (strPackWd != null) {
		pack_width = Integer.parseInt(strPackWd);
		pack_width = (pack_width > 0) ? pack_width : pack_width_def;
	    } else
		pack_width = pack_width_def;
	    
	    if (strPackHt != null) {
		pack_height = Integer.parseInt(strPackHt);
		pack_height = (pack_height > 0) ? pack_height : pack_height_def;
	    } else
		pack_height = pack_height_def;
	    
	    if (strHrOff != null) {
		h_offset = Integer.parseInt(strHrOff);
		h_offset = (h_offset > 0) ? h_offset : h_offset_def;
	    } else
		h_offset = h_offset_def;
	    
	    if (strVtOff != null) {
		v_offset = Integer.parseInt(strVtOff);
		v_offset = (v_offset > 0) ? v_offset : v_offset_def;
	    } else
		v_offset = v_offset_def;
	    
	    if (strVtClr != null) {
		v_clearance = Integer.parseInt(strVtClr);
		v_clearance = (v_clearance > 0) ? v_clearance : v_clearance_def;
	    } else
		v_clearance = v_clearance_def;
	    
	    if (strTotPack != null) {
		total_Packet = Integer.parseInt(strTotPack);
		total_Packet = (total_Packet > 0) ? total_Packet : total_Packet_def;
	    } else
		total_Packet = total_Packet_def;
	    
	    if (strTimeout != null) {
		time_out_sec = Integer.parseInt(strTimeout);
		time_out_sec = (time_out_sec > 0) ? time_out_sec : time_out_sec_def;
	    } else
		time_out_sec = (time_out_sec > 0) ? time_out_sec : time_out_sec_def;
	    
	    // exception converting to integer - if a non integer value is
	    // supplied conversion to an integer value will throw an exception
	    // if an exception is thrown, keep supplied values(already checked)
	    // and use default values for rest of params.
	} catch (Exception e) {
	    // if above fails use what values we have and defaults for the rest
	    // should recover more gracefully than previous code
	    window_len = (window_len > 0) ? window_len : sender_window_len_def;
	    pack_width = (pack_width > 0) ? pack_width : pack_width_def;
	    pack_height = (pack_height > 0) ? pack_height : pack_height_def;
	    h_offset = (h_offset > 0) ? h_offset : h_offset_def;
	    v_offset = (v_offset > 0) ? v_offset : v_offset_def;
	    v_clearance = (v_clearance > 0) ? v_clearance : v_clearance_def;
	    total_Packet = (total_Packet > 0) ? total_Packet : total_Packet_def;
	    time_out_sec = (time_out_sec > 0) ? time_out_sec : time_out_sec_def;
	}
	
    }
    
    
    /***************************************************************************
     *                     Method actionPerformed                              * 
     * *************************************************************************
     * Purpose: actionPerformed method required to be an action listener class.
     * Determines which button in the animation is pressed (ie send new, stop
     * animation, kill Packet/ack, ...) Procedures/Functions Called:
     * paint/update i - temporary variable used for loop control Global
     * variables used: sender - array holding the Packets and the corresponding
     * acks for the Packets sent in the applet nextSeq - the next unused
     * sequence number for a Packet
     **************************************************************************/
    
    public void actionPerformed(ActionEvent e) {
	
	// get what button called the method and perform appropriate action
	String cmd = e.getActionCommand();
	
	// user pressed the send new button check if we can send a new Packet
	if (cmd == "rdt" && nextseqsum < base + window_len) {
	    // create our new Packet in the sender array
	    sender[nextseqsum] = new SelectiveRepeatPacket(true, pack_height + ADVANCE_PACKET,nextseqsum);
	    // tell user the Packet was successfully created and sent
	    output.append("(S) - Packet " + nextseqsum + " sent\n");
	    // simulate our per Packet timers
	    output.append("(S) - Timer started for Packet " + nextseqsum + "\n");
	    if (base == nextseqsum) // i.e. the window is empty and new data is
		// comming in
		{
		    // start the timer thread for timeout processing
		    if (timerThread == null)
			timerThread = new Thread(this);
		    timerSleep = true;
		    timerThread.start();
		}
	    
	    repaint();
	    nextseqsum++;
	    if (nextseqsum == base + window_len)
		send.setEnabled(false);
	    start();
	}
	
	// user wants to increase speed of animation
	else if (cmd == "fast") // Faster button pressed
	    {
		fps += FPS_STEP;
		output.append("-Simulation speed increased\n");
	    }
	
	// user wants to decrease speed of animation
	else if (cmd == "slow" && fps > MIN_FPS) {
	    fps -= FPS_STEP;
	    output.append("-Simulation speed decreased\n");
	}
	// pause animation
	
	// stop the animation from running to allow user to read status messages
	// and examine Packets in transmission
	else if (cmd == "stopanim") {
	    output.append("- Simulation paused\n");
	    gbnThread = null;
	    
	    if (timerThread != null) {
		timerFlag = true;
		timerThread = null; // added later
	    }
	    // change our stop button to allow the user to resume the simulation
	    stop.setLabel("Resume");
	    stop.setActionCommand("startanim");
	    
	    // disableing all the buttons we dont allow user to perform actions
	    // during paused sim
	    send.setEnabled(false);
	    slow.setEnabled(false);
	    fast.setEnabled(false);
	    kill.setEnabled(false);
	    
	    repaint();
	}
	
	// resumes animation after it was paused.
	else if (cmd == "startanim") {
	    output.append("-Simulation resumed.\n");
	    stop.setLabel("Pause");
	    stop.setActionCommand("stopanim");
	    
	    if (timerFlag) {
		timerThread = new Thread(this);
		timerSleep = true;
		timerThread.start();
	    }
	    
	    // enabling the buttons
	    send.setEnabled(true);
	    slow.setEnabled(true);
	    fast.setEnabled(true);
	    kill.setEnabled(true);
	    
	    // repaint to show updated simulation
	    repaint();
	    start();
	    
	}
	
	// lose selected Packet in transmisson
	else if (cmd == "kl") {
	    if (sender[selected].Packet_ack) {
		output.append("- Packet " + selected + " lost\n");
	    } else
		output.append("- Selective Ack of Packet " + selected
			      + " lost.\n");
	    
	    sender[selected].on_way = false;
	    kill.setEnabled(false);
	    selected = DESELECTED;
	    repaint();
	}
	
	// reset animation to initial view
	else if (cmd == "rst")
	    reset_app();
    }
    
    /***************************************************************************
     *                             Method mouseDown                            *
     * *************************************************************************
     * Purpose: Determines when the mouse is pressed down and what
     * object(Packet) is currently under the mouse. mouseDown is used to select
     * a Packet in transmission to be killed(possibly) Global variables used:
     * sender - array holding the Packets and the corresponding acks for the
     * Packets sent in the applet output - console window to display information
     * about the applet activities
     **************************************************************************/
    public boolean mouseDown(Event e, int x, int y) {
	int location, xpos, ypos;
	location = (x - h_offset) / (pack_width + 7);
	// for clicking off of currently selected Packet - also prevents index
	// out of bounds exceptions
	if (location >= total_Packet || location < 0) {
	    selected = DESELECTED;
	    return false;
	}
	if (sender[location] != null) {
	    xpos = h_offset + (pack_width + 7) * location;
	    ypos = sender[location].Packet_pos;
	    
	    if (x >= xpos && x <= xpos + pack_width && sender[location].on_way) {
		if ((sender[location].Packet_ack && y >= v_offset + ypos && y <= v_offset + ypos + pack_height) || ((!sender[location].Packet_ack) && y >= v_offset + v_clearance - ypos && y <= v_offset + v_clearance - ypos + pack_height)) {
		    if (sender[location].Packet_ack)
			output.append("- Packet " + location + " selected.\n");
		    else
			output.append("- Selective Ack " + location
				      + " selected.\n");
		    
		    sender[location].selected = true;
		    selected = location;
		    kill.setEnabled(true);
		    repaint();
		    
		} else {
		    output.append("-Click on a moving Packet to select.\n");
		    selected = DESELECTED;
		}
	    } else {
		output.append("-Click on a moving Packet to select.\n");
		selected = DESELECTED;
	    }
	}
	
	return true;
    }
    
    /***************************************************************************
     *                            Method paint                                 *
     * *************************************************************************
     * Purpose: Allows a graphics context to be established for drawing
     * Procedures/Functions Called: update Procedures/Functions Calling: main,
     * start, run Local variables: g - Graphics object for drawing functionality
     **************************************************************************/
    public void paint(Graphics g) // To eliminate flushing, update is
    // overriden
    {
	update(g);
    }
    
    /***************************************************************************
     *                          Method Update                                  *
     * *************************************************************************
     * Purpose: Handles the actual drawing for the applet. Draws the Packets,
     * message boxes, ... Procedures/Functions Called:check_upto_n,
     * paint/update(indirectly) Procedures/Functions Calling: paint Local
     * variables: i - temporary variable used for loop control Global variables
     * used: sender - array holding the Packets and the corresponding acks for
     * the Packets sent in the applet offGraphics - used to create a secondary
     * buffer to draw the necessary components before putting the completed
     * drawing to screen. This prevents "flashing" when viewing the applet on
     * higher frame rates
     **************************************************************************/
    public void update(Graphics g) {
	Dimension d = size();

	// Create the offscreen graphics context, if no good one exists.
	if ((offGraphics == null) || (d.width != offDimension.width)
	    || (d.height != offDimension.height)) {
	    offDimension = d;
	    offImage = createImage(d.width, d.height);
	    offGraphics = offImage.getGraphics();
	}
	
	// Erase the previous image.
	offGraphics.setColor(Color.white);
	offGraphics.fillRect(0, 0, d.width, d.height);
	
	// drawing window
	offGraphics.setColor(Color.black);
	// Sender window defining the top left, and bottom right coordinates of
	// the rectangle.

	offGraphics.draw3DRect(h_offset + base * (pack_width + 7) - 4,v_offset - 3, (window_len) * (pack_width + 7) + 1,pack_height + 6, true);
	// Receiver window. Note: the 222 is used to relocate the box based on
	// the v_offset variable, which is located in the senders box
	offGraphics.draw3DRect(h_offset + receiver_base * (pack_width + 7) - 4,v_offset + 222, ((receiver_window_len) * (pack_width + 7) + 1),pack_height + 6, true);
	
	// walk through our sender array and gather information about how to
	// draw Packets
	for (int i = 0; i < total_Packet; i++) {
	    // print out numbers over our Packets for easy reference
	    offGraphics.setColor(Color.black);
	    offGraphics.drawString("" + i, h_offset + (pack_width + 7) * i, v_offset - 4);
	    offGraphics.drawString("" + i, h_offset + (pack_width + 7) * i, v_offset + v_clearance + 30);
	    
	    // if no Packet has been created at our current index draw the
	    // Packet as a black rectangle
	    if (sender[i] == null) {
		offGraphics.setColor(Color.black);
		offGraphics.draw3DRect(h_offset + (pack_width + 7) * i,v_offset, pack_width, pack_height, true);
		offGraphics.draw3DRect(h_offset + (pack_width + 7) * i,v_offset + v_clearance, pack_width, pack_height, true);
	    } else {
		// Packet exists at our current index - determine what color to
		// draw the Packet in the animation
		if (sender[i].acknowledged)
		    offGraphics.setColor(received_ack);
		else
		    offGraphics.setColor(unack_color);
		
		offGraphics.fill3DRect(h_offset + (pack_width + 7) * i,v_offset, pack_width, pack_height, true);
		if (sender[i].buffered)
		    offGraphics.setColor(Color.GRAY);
		else
		    // drawing the destination Packets
		    offGraphics.setColor(dest_color);
		// if the Packet has reached the destination than draw a filled
		// rectangle in destination row
		
		// else draw a "clear" rectangle in destination row
		if (sender[i].reached_dest)
		    offGraphics.fill3DRect(h_offset + (pack_width + 7) * i,v_offset + v_clearance, pack_width, pack_height,true);
		
		else {
		    offGraphics.setColor(Color.black);
		    offGraphics.draw3DRect(h_offset + (pack_width + 7) * i,v_offset + v_clearance, pack_width, pack_height,true);
		}
		// drawing the moving Packets
		if (sender[i].on_way) {
		    if (i == selected)
			offGraphics.setColor(sel_color);
		    
		    else if (sender[i].Packet_ack)
			offGraphics.setColor(roam_pack_color);
		    else if (sender[i].received)
			//offGraphics.setColor(received_ack);
			offGraphics.setColor(roam_ack_color);
		    else
			offGraphics.setColor(roam_ack_color);
		    
		    if (sender[i].Packet_ack) {
			offGraphics.fill3DRect(h_offset + (pack_width + 7) * i,v_offset + sender[i].Packet_pos, pack_width,pack_height, true);
			offGraphics.setColor(Color.black);
			offGraphics.drawString("" + i, h_offset
					       + (pack_width + 7) * i, v_offset
					       + sender[i].Packet_pos);
		    } else {
			offGraphics.fill3DRect(h_offset + (pack_width + 7) * i,v_offset + v_clearance - sender[i].Packet_pos,pack_width, pack_height, true);
			if (sender[i].out_of_order) {
			    offGraphics.setColor(Color.black);
			    offGraphics.drawString("" + sender[i].ackFor,h_offset + (pack_width + 7) * i, v_offset+ v_clearance- sender[i].Packet_pos);
			} else {
			    offGraphics.setColor(Color.black);
			    offGraphics.drawString("" + i, h_offset+(pack_width + 7) * i, v_offset+ v_clearance - sender[i].Packet_pos);
			}
		    }
		} // end if sender on way
	    } // end else
	} // for loop ends
	
	// drawing message boxes
	offGraphics.setColor(Color.black);
	int newvOffset = v_offset + v_clearance + pack_height;
	int newHOffset = h_offset;
	
	// draw values of variables on frame
	// offGraphics.drawString(newHOffset,newvOffset+25);
	offGraphics.drawString("(S) - Action at Sender                  (R) - Action at Receiver",newHOffset + 60, newvOffset + 90);
	
	// offGraphics.drawString(strCurrentValues,newHOffset,newvOffset+40);
	offGraphics.drawString("Packet", newHOffset + 15, newvOffset + 60);
	offGraphics.drawString("Ack Received", newHOffset + 225,newvOffset + 60);
	offGraphics.drawString("Ack", newHOffset + 170, newvOffset + 60);
	offGraphics.drawString("Received", newHOffset + 85, newvOffset + 60);
	offGraphics.drawString("Selected", newHOffset + 335, newvOffset + 60);
	offGraphics.drawString("Buffered", newHOffset + 415, newvOffset + 60);
	
	offGraphics.drawString("base = " + base, h_offset + (pack_width + 7)* total_Packet + 10, v_offset + 33);
	offGraphics.drawString("nextseqnum = " + nextseqsum, h_offset+(pack_width + 7) * total_Packet + 10, v_offset + 50);
	
	offGraphics.setColor(Color.blue);
	offGraphics.drawString("Sender (Send Window Size = " + window_len + ")", h_offset + (pack_width + 7) * total_Packet + 10, v_offset + 12);
	offGraphics.drawString("Receiver (Receiver Window Size = " + receiver_window_len + ")", h_offset + (pack_width + 7) * total_Packet + 10, v_offset + v_clearance + 12);
	offGraphics.setColor(Color.gray);
	offGraphics.draw3DRect(newHOffset - 10, newvOffset + 42, 475, 25, true);
	offGraphics.setColor(roam_pack_color);
	offGraphics.fill3DRect(newHOffset, newvOffset + 50, 10, 10, true);
	offGraphics.setColor(roam_ack_color);
	offGraphics.fill3DRect(newHOffset + 155, newvOffset + 50, 10, 10, true);
	offGraphics.setColor(received_ack);
	offGraphics.fill3DRect(newHOffset + 210, newvOffset + 50, 10, 10, true);
	offGraphics.setColor(dest_color);
	offGraphics.fill3DRect(newHOffset + 70, newvOffset + 50, 10, 10, true);
	offGraphics.setColor(sel_color);
	offGraphics.fill3DRect(newHOffset + 320, newvOffset + 50, 10, 10, true);
	offGraphics.setColor(Color.GRAY);
	offGraphics.fill3DRect(newHOffset + 400, newvOffset + 50, 10, 10, true);
	g.drawImage(offImage, 0, 0, this);
    } // method paint ends
    
    /***************************************************************************
     *                         Method onTheWay                                 *
     * *************************************************************************
     * Purpose: checks to see if all of the Packets in an array(in our case the
     * sender array) have been created and are being processed
     * Procedures/Functions Calling: run Local variables: i - temporary variable
     * used for loop control
     **************************************************************************/
    public boolean onTheWay(SelectiveRepeatPacket pac[]) {
	
	for (int i = 0; i < pac.length; i++)
	    if (pac[i] == null)
		return false;
	    else if (pac[i].on_way)
		return true;
	
	return false;
    }
    
    /***************************************************************************
     *                         Method check_upto_n                             *
     * *************************************************************************
     * Purpose: checks the sender array to see if all of the pacekts up to index
     * packno have reached thier destination Procedures/Functions Calling: run
     * Local variables: i - temporary variable used for loop control Global
     * variables used: sender - array holding the Packets and the corresponding
     * acks for the Packets sent in the applet
     **************************************************************************/
    public boolean check_upto_n(int packno) {
	for (int i = 0; i < packno; i++)
	    if (!sender[i].reached_dest)
		return false;
	return true;
    }
    
    /***************************************************************************
     *                         Method reset_app                                *
     * *************************************************************************
     * Purpose: resets the applet to its initial state to allow for a second run
     * without reloading the webpage Local variables: i - temporary variable
     * used for loop control Global variables used: sender - array holding the
     * Packets and the corresponding acks for the Packets sent in the applet
     * base - what number our sending window is set to nextseq - the next
     * sequence number that can be used for a Packet selected - the Packet
     * currently selected fps - how fast shoud the animation run timerFlag -
     * gbnThread - used to process and display the animation timerThread - used
     * to handle timeouts and retransmit the sending window
     **************************************************************************/
    
    public void reset_app() {
	
	for (int i = 0; i < total_Packet; i++)
	    if (sender[i] != null)
		sender[i] = null;
	
	base = 0;
	receiver_base = 0;
	nextseqsum = 0;
	selected = DESELECTED;
	fps = DEFAULT_FPS;
	timerFlag = false;
	timerSleep = false;
	gbnThread = null;
	timerThread = null;
	
	if (stop.getActionCommand() == "startanim") // in case of pause mode,
	    // enable all buttons
	    {
		slow.setEnabled(true);
		fast.setEnabled(true);
	    }
	
	send.setEnabled(true);
	kill.setEnabled(false);
	stop.setLabel("Stop Animation");
	stop.setActionCommand("stopanim");
	output
	    .append("---------------------------------------------------\n\n");
	output.append("-Simulation restarted. Press 'Send New' to start.\n");
	repaint();
    }
    
} // end class SelectiveRepeat

class SelectiveRepeatPacket {
    
    boolean on_way; // is Packet in transit
    boolean reached_dest; // true if Packet reached the destination
    boolean acknowledged; // used by drawing function -false will use Packet
    // color -true will use ack color
    boolean Packet_ack; // is this Packet an ack? if false Packet is assumed to
    // be a message
    boolean selected; // true if Packet was selected by user false otherwise
    boolean received; // true if Packet was received
    boolean out_of_order; // Packet arrived out of order and an ack from the
    // base needs to be sent
    int Packet_pos; // location of Packet in diagram
    int ackFor; // carries the number of the Packet the ack is for
    boolean buffered;
    
    SelectiveRepeatPacket() {
	on_way = false;
	selected = false;
	reached_dest = false;
	acknowledged = false;
	Packet_ack = true;
	received = false;
	out_of_order = false;
	Packet_pos = 0;
	ackFor = 0;
	buffered = false;
    }
    
    SelectiveRepeatPacket(boolean onway, int Packetpos, int nextseq) {
	on_way = onway;
	selected = false;
	reached_dest = false;
	acknowledged = false;
	Packet_ack = true;
	received = false;
	out_of_order = false;
	Packet_pos = Packetpos;
	ackFor = nextseq;
	buffered = false;
	
    }
}