/*
 *     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 GBN 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 GBN 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 = 1;
   // 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
   // nextseq - 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, nextseq, 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
   GoBackNPacket 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
      nextseq = 0; // Defining next sequence number for next packet sent.
      fps = DEFAULT_FPS; // 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 GoBackNPacket[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("-- GoBackN 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 for runnable interface. Determines which
    * thread is calling and begins animation processing accordingly. gbnThread
    * produces the animation for the applet. The timerThread sleeps until
    * timeout processing is needed to retransmit the sending window.
    * 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() {
      // test if our simulation has completed. Prevents animation from looping
      // over
      // no inputs. Terminates threads and exits method.
      if (sender[total_packet - 1] != null)
	 if (sender[total_packet - 1].acknowledged) {
	    sender[total_packet - 1].packet_pos += ADVANCE_PACKET;
	    gbnThread = null;
	    output.append("Data Transferred - Simulation completed\n");
	    return;
	 }
      /*
       * determine which thread called the run method since both the GBN
       * simulation thread and the timer thread call the same run method.
       */
      System.gc();
     Thread currenthread = Thread.currentThread();
      
      // while the gbnThread (Go Back N simulation thread) is calling
      // animation advance the animation accordingly.
      while (currenthread == gbnThread) {
	 /*
	  * Explanation of the following if structure:
	  * if any of the packets in the sender array are in
	  * transit between nodes then the location variable is incremented by
	  * ADVANCE_PACKET(5 pixels by default) for that packet. Once the packet 
	  * has reached the receiving node the variable reached_dest is set to true. 
	  * Processing and sending of ACKs will then takes place.
	  */
	 if (onTheWay(sender)) {
	    for (int i = 0; i < total_packet; i++)
	       if (sender[i] != null) // if a packet has been created at this index in array
		  if (sender[i].on_way) // If this packet is currently being transmitted
			    // Test if the packet has reached the destination node
			    // if the packet has not reached the destination then
			    // advance the packet's location by ADVANCE_PACKET
		     if (sender[i].packet_pos < (v_clearance - pack_height))
			sender[i].packet_pos += ADVANCE_PACKET; // and

	    
	    // If a packet is moving towards the destination
		     else if (sender[i].packet_ack) {
			deliverPackets(i);
		     } else if (!sender[i].packet_ack) {
			// check if the packet this acknowledges is our
			// next expected
			// sequence number
			if (sender[i].ackFor != i) {
			   // extract what packet this ack is for
			   int location = sender[i].ackFor;
			   // check to see if we have processed the ack
			   // as the original ack for this packet may
			   // have been lost
			   if (!sender[location].acknowledged) {
			      // print out confirmation message,
			      // process packet, and perform GBN
			      // processing
			      output.append("(S) - Cumulative Ack received for Packet(s) up to and including Packet " + location + ". Timer for Packet(s) up to and including " + location + " stopped\n");
			      sender[location].received = true;
			      sender[location].on_way = false;
			      sender[location].acknowledged = true;
			      sender[i].packet_ack = false;
			      sender[i].on_way = false;
			      
			      // do go back n processing for every
			      // packet up to the location of this
			      // packet
			      simGoBackN(location);
			      
			   } else {
			      /*
			       * else this is a duplicate ack. Go Back
			       * N performs no action on duplicate
			       * acks received. process packet and
			       * ignore
			       */
			      output.append("(S) - Cumulative Ack for Packet(s) up to and including " + sender[i].ackFor + " received again (DUPACK)\n");
			      sender[i].packet_pos = pack_height + ADVANCE_PACKET;
			      sender[i].packet_ack = false;
			      sender[i].on_way = false;
			   }
			} else {
			   // else this is an ack for our next expected
			   // sequence number
			   output.append("(S) - Cumulative Ack received for Packet(s) up to and including  " + i  + "\n");
			   output.append("(S) - Timer(s) for Packet(s) up to and including " + i + " stopped\n");
			   sender[i].received = true;
			   sender[i].on_way = false;
			   
			   // perform go back n processing for all
			   // packets up to index i in sender array
			   simGoBackN(i);
			}
		     }
	    // update our display
	    repaint();
	    // sleep to display a reasonable fps for the animaition.
	    // without this the animation would process faster than the user
	    // can see.
	    
	    try {
	       Thread.sleep(TIMEOUT_MULTIPLIER / fps);
	       timeout = (TIMEOUT_MULTIPLIER / fps);
	       // if we get interupted something outside of our control has
	       // gone wrong.
	       // this should only be interrupted by a browser error
	    } catch (InterruptedException e) {
	       output.append("-Help\n");
	    }
	 }// end if
	 else
	    gbnThread = null;
      }
      
      while (currenthread == timerThread) {
	 // if timerSleep is true we have told the timerthread to wait a
	 // specified period of time(time_out_sec*TIMEOUT_MULTIPLIER)
	 // before checking to see if any packets are still outstanding.
	 if (timerSleep) {
	    timerSleep = false;
	    try {
	       Thread.sleep(time_out_sec * TIMEOUT_MULTIPLIER);
	       timeout = (time_out_sec * TIMEOUT_MULTIPLIER);
	    } catch (InterruptedException e) {
	       output.append("-Timer interrupted.\n");
	       return;
	    }
	 } else
	    retransmitOutstandingPackets();
      }// end while
   }// End run() method
   
   /*********************************************************************
    *                  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 retransmissio
    *   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() {
      // after the timerThread wakes up process the packets in sender
      // array from the base of our window (the leftmost edge)
      
      // up to the last packet send
      for (int n = (base == 0) ? 0 : base - 1; n < base + window_len; n++) {
	 if (sender[n] != null)
	    if (!sender[n].acknowledged) {
	       sender[n].on_way = true;
	       sender[n].packet_ack = true;
	       sender[n].packet_pos = pack_height + ADVANCE_PACKET;
	       sender[n].ackFor = n;
	    }
	 timerSleep = true;
	 
	 if (gbnThread == null) {
         gbnThread = new Thread(this);
         gbnThread.start();
	 }
	 
      }// end for
      //test for border case -- needs cleanup
      
      for(int i = base; i < total_packet; i++)
    	  if(sender[i].acknowledged == false)
    	  {
      		  output.append("(S) - Timeout occurred for Packet " + (i) + ". \n");
    		  break;
    	  }
      output.append("(S) - All outstanding Packet(s) from " + base + " to " + (nextseq - 1) + " are restransmitted. Timer(s) restarted for Packet(s)\n");
   }
   
   /***************************************************************************
    *                        Method simGoBackN                                *
    * *************************************************************************
    * Purpose: Simulates the Go Back N protocol. The "heart" of the Go Back N
    * processing. Method handles the acknowledging up to the ack we just
    * received.
    * Procedures/Functions Calling: run
    * Local variables: i - the index in the array to check up to - comes from the
    * packet we just received 
    * 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 simGoBackN(int i) {
      // set all packets in the sender array up to index i (our ack that just
      // arrived) to acknowledged per go back n specs.
      for (int n = 0; n <= i; n++) {
	 sender[n].acknowledged = true;
      }
      // if our packet was selected clear the selection bit.
      if (i == selected) {
	 selected = DESELECTED;
	 kill.setEnabled(false);
      }
      
      timerThread = null; // resetting timer thread
      // increment our base value to reflect the new ack we just received
      if (i + window_len < total_packet)
	 base = i + 1;
      
      // if we have room in our window allow the user to send a new packet
      if (nextseq < base + window_len)
	 send.setEnabled(true);
      
      if (base != nextseq) {
	 // need to test to make sure our lastKnownSucPacket + 1 does not
	 // throw an index out of bounds exception
	 if (lastKnownSucPacket < (total_packet - 1))
	    if (sender[lastKnownSucPacket + 1] != null && i != lastKnownSucPacket)
	       output.append("(S) -Timer still running for Packet " + (lastKnownSucPacket) + ".\n");
	    else if (sender[lastKnownSucPacket + 1] != null)
	       output.append("(S) -Timer still running for Packet " + (lastKnownSucPacket + 1) + ".\n");
	 timerThread = new Thread(this);
	 timerSleep = true;
	 timerThread.start();
      } else
	 // set out of order to false in order to control the last known
	 // packet received
	 sender[i].out_of_order = false;
   }
   
   /********************************************************************
    *                       Method deliverPackets                      *
    * ******************************************************************
    * Purpose: process incomming acknowledgments. handle incrementing the
    *   window and informing user of ack arrival
    * Procedures Calling: run
    * Procedures Called: none
    * Local Variables: none
    * Global Variables: packetNumber -index into sender of arriving packet
    *   sender[] - array of packets updated to reflect a new ack has arrived
    *   output - display information to user about ack that just arrived
    *   base - number of left most packet in sending window
    **************************************************************************/
   private void deliverPackets(int packetNumber) {
      sender[packetNumber].reached_dest = true;
      
      // if this packet is an acknowledgment for any
      // other packet than itself, it is a duplicate
      // ack. print out a message telling the user the
      // packet is a duplicate
      if (sender[packetNumber].ackFor != packetNumber) {
	 output.append("(S) Cumulative Ack for Packets up to and including " + sender[packetNumber].ackFor + " received again (DUPACK)\n");
	 sender[packetNumber].packet_pos = pack_height + ADVANCE_PACKET;
	 sender[packetNumber].packet_ack = false;
	 
	 // if all packets have been received in
	 // order, print confirmation message of
	 // packet arrival and process ack normally
	 
	 // check for in-order delivery
      } else if (check_upto_n(packetNumber) && packetNumber >= receiver_base) {
	 sender[packetNumber].packet_pos = pack_height + ADVANCE_PACKET;
	 sender[packetNumber].packet_ack = false;
	 lastKnownSucPacket = packetNumber;
	 output.append("(R) - Packet " + packetNumber + " received. Cumulative Ack for Packets up to and including " + packetNumber  + " sent. Packet " + packetNumber + " delivered to application\n");
	 // advance the receivers next expected
	 // packet window while
	 // the base less than our max packets
	 if (receiver_base + 1 < total_packet && receiver_base <= lastKnownSucPacket)
	    receiver_base = receiver_base + 1;
      }
      
      // Handle the out of order packets by
      // creating a duplicate ack for the last
      // in order packet received
      else {
	 // handles the special case where the first packet is lost - otherwise any packet
	 // that arrives will acknowledge packet 0 even though packet 0 has been lost
	 if (base == 0 && sender[0].packet_ack && receiver_base ==0) {
	    output.append("(R) - Packet " + packetNumber + " received out of order - no Packets acknowledged. Special case -  No Ack sent\n");
	    sender[packetNumber].packet_pos = pack_height + ADVANCE_PACKET;
	    sender[packetNumber].on_way = false;
	    sender[packetNumber].reached_dest = false;

	 }
	 //for duplicate acks
	 else if(packetNumber < receiver_base){
	    sender[packetNumber].packet_pos = pack_height + ADVANCE_PACKET;
	    sender[packetNumber].packet_ack = false;
	    
	    // base is stored as the next expected packet number so our last in packet
	    // received is 1 less than the current value of base when base is 0 we have not
	    // received any packets but we can't signify this without having an array out 
	    // of bounds error
	    output.append("(R) - Packet " + packetNumber + " received out of order Dropping Packet " + packetNumber + ". Cumulative Ack for Packets up to and including " + (lastKnownSucPacket) + " sent\n");
	 }
	 // all other packet arrivals handled below
	 else {
	    sender[packetNumber].packet_pos = pack_height + ADVANCE_PACKET;
	    sender[packetNumber].packet_ack = false;
	    
	    // base is stored as the next expected packet number so our last in packet
	    // received is 1 less than the current value of base when base is 0 we have not
	    // received any packets but we can't signify this without having an array out 
	    // of bounds error
	    sender[packetNumber].ackFor = lastKnownSucPacket;
	    sender[packetNumber].reached_dest = false;
	    sender[packetNumber].out_of_order = true;
	    
	    output.append("(R) - Packet " + packetNumber + " received out of order. Dropping Packet " + packetNumber + ". Cumulative Ack for Packets up to and including " + (lastKnownSucPacket) + " sent\n");
	    if (packetNumber == selected) {
	       selected = DESELECTED;
	       kill.setEnabled(false);
	    }
	 }
      }
   }
   
   /*********************************************************************
    *                        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" && nextseq < base + window_len) {
	 // create our new packet in the sender array
	 sender[nextseq] = new GoBackNPacket(true, pack_height + ADVANCE_PACKET, nextseq);
	 // tell user the packet was successfully created and sent
	 output.append("(S) - Packet " + nextseq + " sent\n");
	 // simulate our per packet timers
	 output.append("(S) - Timer started for Packet " + nextseq + "\n");
	 if (base == nextseq) // 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();
	 nextseq++;
	 if (nextseq == 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("-Cumulative 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("-Cumulative 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);
	       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("base = " + base, h_offset + (pack_width + 7)* total_packet + 10, v_offset + 33);
      offGraphics.drawString("nextseqnum = " + nextseq, 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, 400, 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);
      
      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(GoBackNPacket 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;
      nextseq = 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("Pause");
      stop.setActionCommand("stopanim");
      output.append("---------------------------------------------------\n\n");
      output.append("-Simulation restarted. Press 'Send New' to start.\n");
      repaint();
   }
   
} // end class GBN

class GoBackNPacket {
   
   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;
   
   GoBackNPacket() {
      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;
   }
   
   GoBackNPacket(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;
      
   }
}


