/**
 * Java Applet Demonstration of Selective Repeat Protocol.
 * @author Joshua McKinzie (jmckinzi), December 2005
 *
 * Project created for class CS365 at Park University
 * Project assigned by Dr. Wen-Jung Hsin
 *
*/

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 {
  final int window_len_def = 5; // Default values of parameters

  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;

  final int total_packet_def = 20;

  final int time_out_sec_def = 15;

  final Color unack_color = Color.red; // Default colors of different
                      // packets

  final Color ack_color = Color.yellow;

  final Color sel_color = Color.green;

  final Color roam_pack_color = Color.red;

  final Color roam_ack_color = Color.yellow;

  final Color dest_color = Color.blue;

  final Color buff_color = Color.gray;

  int base, nextseq, fps, selected = -1;

  boolean timerFlag, timerSleep;

  Button send, stop, fast, slow, kill, reset;

  Thread gbnThread, timerThread;

  Dimension offDimension; // flashing eliminator: double buffering

  Image offImage;

  Graphics offGraphics;

  String statusMsg, strCurrentValues;

  Packet sender[];

  // Declaring properties

  int window_len, pack_width, pack_height, h_offset, v_offset, v_clearance,
      total_packet, time_out_sec;

  public void init() {
    String strWinLen, strPackWd, strPackHt, strHrOff, strVtOff, strVtClr, strTotPack, strTimeout;

    strWinLen = getParameter("window_length"); // Start collecting
                          // parameters
    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");

    // Get the values of the parameters into properties.

    try {
      if (strWinLen != null)
        window_len = Integer.parseInt(strWinLen);
      if (strPackWd != null)
        pack_width = Integer.parseInt(strPackWd);
      if (strPackHt != null)
        pack_height = Integer.parseInt(strPackHt);
      if (strHrOff != null)
        h_offset = Integer.parseInt(strHrOff);
      if (strVtOff != null)
        v_offset = Integer.parseInt(strVtOff);
      if (strVtClr != null)
        v_clearance = Integer.parseInt(strVtClr);
      if (strTotPack != null)
        total_packet = Integer.parseInt(strTotPack);
      if (strTimeout != null)
        time_out_sec = Integer.parseInt(strTimeout);
    } catch (Exception e) {
    }

    // If parameter is not found, use default values.

    window_len = (window_len > 0) ? window_len : 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;

    base = 0; // Defining base
    nextseq = 0; // Defining Next sequence number.
    fps = 5; // Defining default Frame per Second.

    sender = new Packet[total_packet];
    statusMsg = "Ready to run. Press 'Send New' button to start.";
    strCurrentValues = "Window Base = " + base + ".   Next Sequence No. = "
        + nextseq;

    // Defining the buttons

    send = new Button("Send New");
    send.setActionCommand("rdt");
    send.addActionListener(this);

    stop = new Button("Stop Animation");
    stop.setActionCommand("stopanim");
    stop.addActionListener(this);

    fast = new Button("Faster");
    fast.setActionCommand("fast");
    fast.addActionListener(this);

    slow = new Button("Slower");
    slow.setActionCommand("slow");
    slow.addActionListener(this);

    kill = new Button("Kill Packet");
    kill.setActionCommand("kl");
    kill.addActionListener(this);
    kill.setEnabled(false);

    reset = new Button("Reset");
    reset.setActionCommand("rst");
    reset.addActionListener(this);

    // Adding the buttons

    add(send);
    add(stop);
    add(fast);
    add(slow);
    add(kill);
    add(reset);
  }

  public void start() {
    if (gbnThread == null) // Creating main thread and start it
      gbnThread = new Thread(this);
    gbnThread.start();
  }

  public void run() {
    Thread currenthread = Thread.currentThread();

    while (currenthread == gbnThread)
    {
      // While the animation is running
      if (onTheWay(sender)) // Checks if any of the packets are
                  // travelling
      {
        for (int i = 0; i < total_packet; i++)
        {
          if (sender[i] != null)
          {
            if (sender[i].on_way) // If packet is roaming
            {
              if (sender[i].packet_pos < (v_clearance - pack_height))
              {
                sender[i].packet_pos += 5; // Move packet
              }
              else if (sender[i].packet_ack) // If it is moving
                              // to destination
              {
                sender[i].reached_dest = true;
                sender[i].packet_pos = pack_height + 5;
                sender[i].packet_ack = false;
                // checks if this packet needs to be buffered
                if (buffered(i))
                {
                  statusMsg = "Packet " + i
                    + " received and buffered. Acknowledge sent.";
                }
                else
                {
                  statusMsg = "Packet " + i
                    + " received. Acknowledge sent.";
                }
                clearBuffer();  // clears the buffer
              }
              else if (!sender[i].packet_ack) // acknowledgement
              {
                statusMsg = "Packet " + i
                    + " acknowledge received.";
                sender[i].on_way = false;
                //for (int n = 0; n <= i; n++)
                sender[i].acknowledged = true;

                if (i == selected) {
                  selected = -1;
                  kill.setEnabled(false);
                }

                timerThread = null; // resetting timer thread
                if (previousComplete(i) && base <= (total_packet - window_len)-1)
                {
                  base = i + 1;
                  repaint();
                  while(previousComplete(base))
                  {
                    if (base <= (total_packet - window_len)-1)
                    {
                      base = base + 1;
                      repaint();
                    }
                    else
                    {
                      break;
                    }
                  }
                }
                //repaint();

                //if (i + window_len < total_packet)
                //  base = i + 1;
                if (nextseq < base + window_len)
                  send.setEnabled(true);

                if (base != nextseq) {
                  statusMsg += " Timer restarted.";
                  timerThread = new Thread(this);
                  timerSleep = true;
                  timerThread.start();
                } else
                  statusMsg += " Timer stopped.";
              }
            }
          }
        }
        strCurrentValues = "Window Base = " + base
            + ".   Next Sequence No. = " + nextseq;
        repaint();

        try {
          Thread.sleep(1000 / fps);
        } catch (InterruptedException e) {
          System.out.println("Help");
        }
      } else
        gbnThread = null;
    }
    while (currenthread == timerThread)
    {
      if (timerSleep)
      {
        timerSleep = false;
        try {
          Thread.sleep(time_out_sec * 1000);
        } catch (InterruptedException e) {
          System.out.println("Timer interrupted.");
        }
      } else {
        for (int n = base; 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 + 5;
            }
        timerSleep = true;
        if (gbnThread == null) {
          gbnThread = new Thread(this);
          gbnThread.start();
        }

        statusMsg = "Packet(s) resent by timer. Timer restarted.";
      }
    }
  }

  public void actionPerformed(ActionEvent e) {
    String cmd = e.getActionCommand();

    if (cmd == "rdt" && nextseq < base + window_len) // Send button is
                              // pressed
    {
      sender[nextseq] = new Packet(true, pack_height + 5);

      statusMsg = "Packet " + nextseq + " sent.";

      if (base == nextseq) { // i.e. the window is empty and a new data
                  // is getting in
        statusMsg += " Timer set for packet " + base + ".";
        if (timerThread == null)
          timerThread = new Thread(this);
        timerSleep = true;
        timerThread.start();
      }
      repaint();
      // I don't know if this will work
      gbnThread = null;
      if (timerThread != null) {
        timerFlag = true;
        timerThread = null; // added later
      }
      repaint();
      if (timerFlag) {
        statusMsg += " Timer running.";
        timerThread = new Thread(this);
        timerSleep = true;
        timerThread.start();
      }
      // end trying
      repaint();
      nextseq++;
      if (nextseq == base + window_len)
        send.setEnabled(false);
      start();
    }

    else if (cmd == "fast") // Faster button pressed
    {
      fps += 2;
      statusMsg = "Simulation speed increased by 2 fps.";
    }

    else if (cmd == "slow" && fps > 2) {
      fps -= 2;
      statusMsg = "Simulation speed decreased by 2 fps.";
    }

    else if (cmd == "stopanim") {
      gbnThread = null;
      if (timerThread != null) {
        timerFlag = true;
        timerThread = null; // added later
      }
      stop.setLabel("Resume");
      stop.setActionCommand("startanim");

      // disableing all the buttons
      send.setEnabled(false);
      slow.setEnabled(false);
      fast.setEnabled(false);
      kill.setEnabled(false);

      statusMsg = "Simulation paused.";
      repaint();
    }

    else if (cmd == "startanim") {
      statusMsg = "Simulation resumed.";
      stop.setLabel("Stop Animation");
      stop.setActionCommand("stopanim");
      if (timerFlag) {
        statusMsg += " Timer running.";
        timerThread = new Thread(this);
        timerSleep = true;
        timerThread.start();
      }

      // enabling the buttons
      send.setEnabled(true);
      slow.setEnabled(true);
      fast.setEnabled(true);
      kill.setEnabled(true);
      repaint(); // repainted to show new messages

      start();
    }

    else if (cmd == "kl") {
      if (sender[selected].packet_ack)
        statusMsg = "Packet " + selected
            + " destroyed. Timer running for packet " + base + ".";
      else
        statusMsg = "Acknowledgement of packet " + selected
            + " destroyed. Timer running for packet " + base + ".";

      sender[selected].on_way = false;
      kill.setEnabled(false);
      selected = -1;
      repaint();
    }

    else if (cmd == "rst")
      reset_app();
  }

  public boolean mouseDown(Event e, int x, int y) {
    int i, xpos, ypos;
    i = (x - h_offset) / (pack_width + 7);
    if (sender[i] != null) {
      xpos = h_offset + (pack_width + 7) * i;
      ypos = sender[i].packet_pos;

      if (x >= xpos && x <= xpos + pack_width && sender[i].on_way) {
        if ((sender[i].packet_ack && y >= v_offset + ypos && y <= v_offset
            + ypos + pack_height)
            || ((!sender[i].packet_ack)
                && y >= v_offset + v_clearance - ypos && y <= v_offset
                + v_clearance - ypos + pack_height)) {
          statusMsg = "Packet " + i + " selected.";
          sender[i].selected = true;
          selected = i;
          kill.setEnabled(true);
        } else
          statusMsg = "Click on a moving packet to select.";
      } else
        statusMsg = "Click on a moving packet to select.";
    }
    return true;
  }

  public void paint(Graphics g) // To eliminate flushing, update is
                  // overriden
  {
    update(g);
  }

  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);
    offGraphics.draw3DRect(h_offset + base * (pack_width + 7) - 4,
        v_offset - 3, (window_len) * (pack_width + 7) + 1,
        pack_height + 6, true);

    for (int i = 0; i < total_packet; i++) {
      // drawing the sending row

      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 {
        if (sender[i].acknowledged)
          offGraphics.setColor(ack_color);
        else
          offGraphics.setColor(unack_color);
        offGraphics.fill3DRect(h_offset + (pack_width + 7) * i,
            v_offset, pack_width, pack_height, true);

        // drawing the destination packets

        offGraphics.setColor(dest_color);
        if (sender[i].buffered)
        {
          offGraphics.setColor(buff_color);
          offGraphics.fill3DRect(h_offset + (pack_width + 7) * i,
              v_offset + v_clearance, pack_width, pack_height,
              true);
        }
        else if (sender[i].reached_dest)
        {
          offGraphics.fill3DRect(h_offset + (pack_width + 7) * i,
              v_offset + v_clearance, pack_width, pack_height,
              true);
        }
        else
        {
          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
            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);
          else
            offGraphics.fill3DRect(h_offset + (pack_width + 7) * i,
                v_offset + v_clearance - sender[i].packet_pos,
                pack_width, pack_height, true);
        }
      }
    } // for loop ends

    // drawing message boxes

    offGraphics.setColor(Color.black);
    int newvOffset = v_offset + v_clearance + pack_height;
    int newHOffset = h_offset;

    offGraphics.drawString(statusMsg, newHOffset, newvOffset + 25);
    // offGraphics.drawString(strCurrentValues,newHOffset,newvOffset+40);

    offGraphics.drawString("Packet", newHOffset + 15, newvOffset + 60);
    offGraphics.drawString("Acknowledge", newHOffset + 85, newvOffset + 60);
    offGraphics.drawString("Received Pack", newHOffset + 185,
        newvOffset + 60);
    offGraphics.drawString("Buffered Pack", newHOffset + 295, newvOffset + 60);
    offGraphics.drawString("Selected", newHOffset + 415, newvOffset + 60);

    offGraphics.drawString("Base = " + base, h_offset + (pack_width + 7)
        * total_packet + 10, v_offset + v_clearance / 2);
    offGraphics.drawString("NextSeq = " + nextseq, h_offset
        + (pack_width + 7) * total_packet + 10, v_offset + v_clearance
        / 2 + 20);

    offGraphics.setColor(Color.blue);
    offGraphics.drawString("Sender", h_offset + (pack_width + 7)
        * total_packet + 10, v_offset + 12);
    offGraphics.drawString("Receiver", h_offset + (pack_width + 7)
        * total_packet + 10, v_offset + v_clearance + 12);

    offGraphics.setColor(Color.gray);
    offGraphics.draw3DRect(newHOffset - 10, newvOffset + 42, 490, 25, true);

    offGraphics.setColor(Color.red);
    offGraphics.draw3DRect(h_offset + (pack_width + 7) * total_packet + 5,
        v_offset + v_clearance / 2 - 15, 80, 40, true);

    offGraphics.setColor(roam_pack_color);
    offGraphics.fill3DRect(newHOffset, newvOffset + 50, 10, 10, true);

    offGraphics.setColor(roam_ack_color);
    offGraphics.fill3DRect(newHOffset + 70, newvOffset + 50, 10, 10, true);

    offGraphics.setColor(dest_color);
    offGraphics.fill3DRect(newHOffset + 170, newvOffset + 50, 10, 10, true);

    offGraphics.setColor(buff_color);
    offGraphics.fill3DRect(newHOffset + 280, newvOffset + 50, 10, 10, true);

    offGraphics.setColor(sel_color);
    offGraphics.fill3DRect(newHOffset + 390, newvOffset + 50, 10, 10, true);

    g.drawImage(offImage, 0, 0, this);
  } // method paint ends

  // checks out if an array is on the way to source or destination

  public boolean onTheWay(Packet 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;
  }

  // checkes all the packets before packno. Returns false if any packet has
  // not reached destination and true if all the packets have reached
  // destination.

  public boolean previousComplete(int packno)
  {
    for (int i = 0; i <= packno; i++)
    {
      if (sender[i] == null || !sender[i].acknowledged)
      {
        return false;
      }
    }
    return true;
  }

  private int getBase(int packno)
  {
    int i = packno;
    for (; i< base + window_len; i++)
    {
      if (!sender[i].acknowledged)
      {
        return i;
      }
    }
    return i;
  }

  private boolean buffered(int packno)
  {
    for (int i=0; i<packno; i++)
    {
      if (!sender[i].reached_dest)
      {
        sender[packno].buffered = true;
        return true;
      }
    }
    return false;
  }

  private void clearBuffer()
  {
    for (int i=0; i<nextseq; i++)
    {
      if (sender[i].reached_dest)
      {
        sender[i].buffered = false;
      }
      else
      {
        break;
      }
    }
  }

  public void reset_app() {
    for (int i = 0; i < total_packet; i++)
      if (sender[i] != null)
        sender[i] = null;
    base = 0;
    nextseq = 0;
    selected = -1;
    fps = 5;
    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");
    statusMsg = "Simulation restarted. Press 'Send New' to start.";
    repaint();
  }
}
