/**
   * Send a data packet and wait for ack in one operation. This method employs a timer that resends
   * the packet until an ack is received (or the timeout is reached). <br>
   * <br>
   * This method sets the {@link #lastDataPacketSent} variable. This method can only be used in the
   * Established state, see {@link State}.
   *
   * @param packet the packet to be sent.
   * @return The ack-package received for the send packet (NB: ack can be null)
   * @throws IOException thrown if unable to send packet.
   * @see no.ntnu.fp.net.cl.ClSocket#send(KtnDatagram)
   */
  protected synchronized KtnDatagram sendDataPacketWithRetransmit(KtnDatagram packet)
      throws IOException {
    if (state != State.ESTABLISHED)
      throw new IllegalStateException("Should only be used in ESTABLISHED state.");
    if (packet.getFlag() != Flag.NONE)
      throw new IllegalArgumentException("Packet must be a data packet.");
    /*
     * Algorithm: 1 Start a timer used to resend the packet with a specified
     * interval, and that immediately starts trying (sending the first
     * packet as well as the retransmits). 2 Wait for the ACK using
     * receiveAck(). 3 Cancel the timer. 4 Return the ACK-packet.
     */

    lastDataPacketSent = packet;

    // Create a timer that sends the packet and retransmits every
    // RETRANSMIT milliseconds until cancelled.
    Timer timer = new Timer();
    timer.scheduleAtFixedRate(new SendTimer(new ClSocket(), packet), 0, RETRANSMIT);

    KtnDatagram ack = receiveAck();
    timer.cancel();

    return ack;
  }
  /**
   * Send an ack or synack for the given packet. <br>
   * If the send fails, there is no retransmission of the ack-packet: Just wait for the other side
   * to retransmit the original packet.<br>
   * <br>
   * This method relies on {@link #constructInternalPacket(Flag)}, i.e. myAddress, myPort,
   * remoteAddress, remotePort and sequenceNo must be initialized properly.
   *
   * @param packetToAck The packet that should be acked
   * @param synAck true if a synack should be sent, false if a regular ack, see {@link
   *     KtnDatagram.Flag}.
   * @throws ConnectException Thrown if unable to send packet.
   * @see #constructInternalPacket(Flag)
   */
  protected synchronized void sendAck(KtnDatagram packetToAck, boolean synAck)
      throws IOException, ConnectException {

    /*
     * Algorithm: Generate a new ack packet based on the packet given as
     * input Try to send the ack Catch a ConnectException if the sending
     * failed - and write this to the Log.
     */

    int tries = 3;
    boolean sent = false;

    KtnDatagram ackToSend = constructInternalPacket(synAck ? Flag.SYN_ACK : Flag.ACK);
    ackToSend.setAck(packetToAck.getSeq_nr());

    // Send the ack, trying at most `tries' times.
    Log.writeToLog(ackToSend, "Sending Ack: " + ackToSend.getAck(), "AbstractConnection");

    do {
      try {
        new ClSocket().send(ackToSend);
        sent = true;
      } catch (ClException e) {
        Log.writeToLog(
            ackToSend,
            "CLException: Could not establish a " + "connection to the specified address/port!",
            "AbstractConnection");
      } catch (ConnectException e) {
        // Silently ignore: Maybe recipient was processing and didn't
        // manage to call receiveAck() before we were ready to send.
        try {
          Thread.sleep(100);
        } catch (InterruptedException ex) {
        }
      }
    } while (!sent && (tries-- > 0));

    if (!sent) {
      nextSequenceNo--;
      throw new ConnectException("Unable to send ACK.");
    }
  }
  /**
   * Construct a datagram with the given flag. <br>
   * <br>
   * Note: This method *depends* on the values of `remotePort', `remoteAddress', `myPort',
   * `myAddress' and `sequenceNo'. Failing to set these before calling this method causes undefined
   * behaviour. Also note that if you want to set values to something else than the default, you
   * must construct the packet manually or alter the returned object.<br>
   * <br>
   * This method also increments the sequenceNo.<br>
   * <br>
   * This method sets the following fields:
   *
   * <ol>
   *   <li>Remote address
   *   <li>Remote port
   *   <li>Local address
   *   <li>Local port
   *   <li>Flag
   *   <li>Sequence no.
   *   <li>Payload to null.
   * </ol>
   *
   * @param flag Flag for the packet, see {@link KtnDatagram.Flag}. Setting this to
   *     KtnDatagram.Flag.NONE constructs a packet with no flag or data, and makes no sense.
   * @return Initialised flagged datagram.
   */
  protected KtnDatagram constructInternalPacket(Flag flag) {

    KtnDatagram packet = new KtnDatagram();
    packet.setDest_port(remotePort);
    packet.setDest_addr(remoteAddress);
    packet.setSrc_addr(myAddress);
    packet.setSrc_port(myPort);
    packet.setFlag(flag);
    packet.setSeq_nr(nextSequenceNo++);
    packet.setPayload(null);

    return packet;
  }
  /**
   * Construct a datagram with the given payload. <br>
   * <br>
   * Note: This method *depends* on the values of `remotePort', `remoteAddress', `myPort',
   * `myAddress' and `sequenceNo'. Failing to set these before calling this method causes undefined
   * behaviour. Also note that if you want to set values to something else than the default, you
   * must construct the packet manually or alter the returned object.<br>
   * <br>
   * This method also increments the sequenceNo.<br>
   * <br>
   * This method sets the following fields:
   *
   * <ol>
   *   <li>Remote address
   *   <li>Remote port
   *   <li>Local address
   *   <li>Local port
   *   <li>Flag to NONE.
   *   <li>Sequence no.
   *   <li>Payload.
   * </ol>
   *
   * @param payload Payload for packet, can not be null.
   * @return Initialised datagram.
   */
  protected KtnDatagram constructDataPacket(String payload) {
    if (payload == null) throw new IllegalArgumentException("Payload can not be null.");

    KtnDatagram packet = new KtnDatagram();
    packet.setDest_port(remotePort);
    packet.setDest_addr(remoteAddress);
    packet.setSrc_addr(myAddress);
    packet.setSrc_port(myPort);
    packet.setFlag(Flag.NONE);
    packet.setSeq_nr(nextSequenceNo++);
    packet.setPayload(payload);

    return packet;
  }
  /**
   * Waits for an ACK or SYN_ACK. Blocks until the ack is recieved. Returns null if no ack recieved
   * after the specified time, see {@link AbstractConnection#TIMEOUT}. <br>
   * <br>
   * If a FIN-packet is received and the state is not ESTABLISHED, this will also be returned.<br>
   * <br>
   * If a FIN-packet is received and the connection is in ESTABLISHED state, an EOFException is
   * thrown. It calls {@link #isValid(KtnDatagram)} on FIN-packets in ESTABLISHED state, before an
   * EOFException is thrown.
   *
   * @return The ACK or SYN_ACK KtnDatagram recieved (can be null), may also be a FIN if not in
   *     established state.
   * @throws IOException If caused by the underlying connectionless layer.
   * @throws EOFException If a FIN-packet is received in ESTABLISHED state.
   */
  protected KtnDatagram receiveAck() throws IOException, EOFException {
    /*
     * Acquire monitor for this instance, and see if another thread runs
     * receive on our port. If so, see if that thread gets the packet that
     * was meant for us.
     */
    synchronized (this) {
      long before, after;

      before = System.currentTimeMillis();
      while (isReceiving) {
        try {
          wait(TIMEOUT); // wait with timeout
        } catch (InterruptedException e) {
          /* do nothing */
        }
        after = System.currentTimeMillis();

        // If an ack for us has arrived, return it.
        // check internalQueue
        if (!internalQueue.isEmpty()) {
          KtnDatagram packet = internalQueue.get(0);
          if (packet.getFlag() == Flag.ACK || packet.getFlag() == Flag.SYN_ACK) {
            return internalQueue.remove(0);
          }
        }
        // If no packet arrived, see if timeout has expired.
        else if ((after - before) > TIMEOUT) {
          return null;
        }
      }

      // When we get here, this thread has not got its packet, and it's
      // allowed to enter the listening part of doReceive().
      isReceiving = true;
    }

    Log.writeToLog("Waiting for incoming packet in receiveAck()", "AbstractConnection");

    KtnDatagram incomingPacket;

    // We are waiting for an ack or syn_ack packet
    long start = System.currentTimeMillis();
    while (System.currentTimeMillis() - start < TIMEOUT) {
      ClSocketReceiver receiver = new ClSocketReceiver(myPort);
      receiver.start();
      // Wait at most what is left of the TIMEOUT period for thread to
      // die, but never less than 1 millisecond
      try {
        receiver.join(Math.max(TIMEOUT - (System.currentTimeMillis() - start), 1));
      } catch (InterruptedException e) {
        /* do nothing */
      }

      receiver.stopReceive();
      incomingPacket = receiver.getPacket();
      if (incomingPacket == null) {
        // No packet was received
        synchronized (this) {
          isReceiving = false;
          notifyAll();
          return null;
        }
      } else {
        // We have a packet
        if (incomingPacket.getFlag() != Flag.NONE) {
          // Packet is internal
          Log.writeToLog("Received an internal packet in receiveAck", "AbstractConnection");

          if (incomingPacket.getFlag() == Flag.FIN && state == State.ESTABLISHED) {
            // A FIN-packet has arrived in established state,
            // stop receiving and throw and exception
            disconnectRequest = incomingPacket;
            synchronized (this) {
              isReceiving = false;
              notifyAll();
              throw new EOFException("FIN packet received.");
            }
          } else if (incomingPacket.getFlag() == Flag.ACK
              || incomingPacket.getFlag() == Flag.SYN_ACK
              || incomingPacket.getFlag() == Flag.FIN) {
            // Not a FIN packet in established state, return if it
            // is SYN, SYN_ACK or FIN
            synchronized (this) {
              isReceiving = false;
              notifyAll();
              return incomingPacket;
            }
          } else {
            // Not a packet we want to return, continue looping.
            synchronized (this) {
              internalQueue.add(incomingPacket);
              notifyAll();
            }
          }
        } else {
          // Packet was meant for the application, continue
          // listening until timeout.
          Log.writeToLog("Received an external packet in receiveAck", "AbstractConnection");

          synchronized (this) {
            synchronized (this) {
              externalQueue.add(incomingPacket);
              notifyAll();
            }
          }
        }
      }
    }
    // We have now waited at least TIMEOUT milliseconds, still no
    // packet.
    synchronized (this) {
      isReceiving = false;
      notifyAll();
      return null;
    }
  }
  /**
   * Receives a packet from the connectionless layer. This function handles concurrency issues
   * related to that only one thread may listen to a port at the same time.<br>
   * <br>
   * It calls {@link #isValid(KtnDatagram)} on FIN-packets in ESTABLISHED state, before an
   * EOFException is thrown.
   *
   * @param internal true if you want to receive non-external packet, i.e. not a packet with data
   *     intended for the application. False otherwise.
   * @return A received datagram
   * @throws IOException If the underlying connectionless layer throws an IOException.
   * @throws EOFException If a packet with a FIN-flag was received in ESTABLISHED state.
   */
  protected KtnDatagram receivePacket(boolean internal) throws IOException, EOFException {
    /*
     * Acquire monitor for this instance, and see if another thread runs
     * receive on our port. If so, see if that thread gets the packet that
     * was meant for us.
     */
    synchronized (this) {
      long before, after;

      before = System.currentTimeMillis();
      while (isReceiving) {
        try {
          if (internal) wait(TIMEOUT); // wait with timeout
          else wait(); // wait (potentially) forever
        } catch (InterruptedException e) {
          /* do nothing */
        }
        after = System.currentTimeMillis();

        // If a packet for us has arrived, return it.
        if (internal) {
          // Case 1: Internal (protocol) caller, check internalQueue
          if (!internalQueue.isEmpty()) {
            return internalQueue.remove(0);
          }
          // If no packet arrived, see if timeout has expired.
          else if ((after - before) > TIMEOUT) {
            return null;
          }
        } else {
          // Case 2: Non-internal (application) caller, check
          // externalQueue
          if (!externalQueue.isEmpty()) {
            return externalQueue.remove(0);
          }
          // else try loop again unless connection is free
        }
      }

      // When we get here, this thread has not got its packet, and it's
      // allowed to enter the listening part of doReceive().
      isReceiving = true;
    }

    Log.writeToLog("Waiting for incoming packet in doReceive()", "AbstractConnection");

    KtnDatagram incomingPacket;

    /*
     * Waiting for internal and external packets should be handled
     * differently. Waiting for internal packets should time out.
     */
    if (internal) {
      // We are waiting for an internal packet, ie. a packet with a flag
      long start = System.currentTimeMillis();
      while (System.currentTimeMillis() - start < TIMEOUT) {
        ClSocketReceiver receiver = new ClSocketReceiver(myPort);
        receiver.start();
        // Wait at most what is left of the TIMEOUT period for thread to
        // die, but never less than 1 millisecond
        try {
          long timeout = TIMEOUT - (System.currentTimeMillis() - start);
          receiver.join(Math.max(timeout, 1));
        } catch (InterruptedException e) {
          /* do nothing */
        }

        receiver.stopReceive();
        incomingPacket = receiver.getPacket();
        if (incomingPacket == null) {
          // No packet was received
          synchronized (this) {
            isReceiving = false;
            notifyAll();
            return null;
          }
        } else {
          // We have a packet
          if (incomingPacket.getFlag() != Flag.NONE) {
            // Packet is internal
            Log.writeToLog("Received an internal packet in doReceive", "AbstractConnection");

            if (incomingPacket.getFlag() == Flag.FIN && state == State.ESTABLISHED) {
              // A FIN-packet has arrived in established state,
              // stop receiving and throw and exception
              disconnectRequest = incomingPacket;
              synchronized (this) {
                isReceiving = false;
                notifyAll();
                throw new EOFException("FIN packet received.");
              }
            } else {
              // Not a FIN packet in established state, return it
              // normally.
              synchronized (this) {
                isReceiving = false;
                notifyAll();
                return incomingPacket;
              }
            }
          } else {
            // Packet was meant for the application, continue
            // listening until timeout.
            Log.writeToLog("Received an external packet in doReceive", "AbstractConnection");

            synchronized (this) {
              synchronized (this) {
                externalQueue.add(incomingPacket);
                notifyAll();
              }
            }
          }
        }
      }
      // We have now waited at least TIMEOUT milliseconds, still no
      // packet.
      synchronized (this) {
        isReceiving = false;
        notifyAll();
        return null;
      }
    } else {
      // We are waiting for a packet to an external application, ie. a
      // packet with no flags. Can possibly wait forever.
      incomingPacket = new ClSocket().receive(myPort);
      if (incomingPacket == null) {
        // We should get a packet, try again.
        synchronized (this) {
          isReceiving = false;
          notifyAll();
        }
        return receivePacket(internal);
      } else {
        // We have a packet
        if (incomingPacket.getFlag() != Flag.NONE) {
          // Packet is internal
          Log.writeToLog("Received an internal packet in doReceive", "AbstractConnection");

          if (incomingPacket.getFlag() == Flag.FIN && state == State.ESTABLISHED) {
            // A FIN-packet has arrived in established state,
            // stop receiving and throw and exception
            disconnectRequest = incomingPacket;
            synchronized (this) {
              isReceiving = false;
              notifyAll();
              throw new EOFException("FIN packet received.");
            }
          } else {
            // Not a FIN packet, continue listening
            synchronized (this) {
              internalQueue.add(incomingPacket);
              isReceiving = false;
              notifyAll();
            }
            return receivePacket(internal);
          }
        } else {
          // Packet was meant for the application, yei!
          Log.writeToLog("Received an external packet in doReceive", "AbstractConnection");

          synchronized (this) {
            isReceiving = false;
            notifyAll();
            return incomingPacket;
          }
        }
      }
    }
  }