package brn.swans.route;

import jist.swans.Constants;
import jist.swans.misc.Message;
import jist.swans.net.NetAddress;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;


/**
 * A DSR ETX message.
 */
public class RouteDsrBrnMsg implements Message, Cloneable {
  // ////////////////////////////////////////////////
  // constants
  //

  // Option types

  /**
   * Type of a Route Request option.
   */
  public static final byte OPT_ROUTE_REQUEST = 2;

  /**
   * Type of a Route Reply option.
   */
  public static final byte OPT_ROUTE_REPLY = 1;

  /**
   * Type of a Route Error option.
   */
  public static final byte OPT_ROUTE_ERROR = 3;

  /**
   * Type of an Acknowledgement Request option.
   */
  public static final byte OPT_ACK_REQUEST = (byte) 160;

  /**
   * Type of an Acknowledgement option.
   */
  public static final byte OPT_ACK = 32;

  /**
   * Type of an id option
   */
  public static final byte OPT_ID = 64;

  /**
   * Type of a Source Route option.
   */
  public static final byte OPT_SOURCE_ROUTE = 96;

//  /**
//   * Type of a Pad1 option.
//   */
//  public static final byte OPT_PAD1 = (byte) 224;
//
//  /**
//   * Type of a PadN option.
//   */
//  public static final byte OPT_PADN = 0;

  // Error types

  /**
   * Error code for "unreachable node".
   */
  public static final byte ERROR_NODE_UNREACHABLE = 1;

  /**
   * Error code for "flow state not supported".
   */
  public static final byte ERROR_FLOW_STATE_NOT_SUPPORTED = 2;

  /**
   * Error code for "option not supported".
   */
  public static final byte ERROR_OPTION_NOT_SUPPORTED = 3;

  // Ways of dealing with unrecognized options

//  /**
//   * Code for ignoring unrecognized options.
//   */
//  public static final int UNRECOGNIZED_OPT_IGNORE = 0;
//
//  /**
//   * Code for removing unrecognized options.
//   */
//  public static final int UNRECOGNIZED_OPT_REMOVE = 1;
//
//  /**
//   * Code for marking unrecognized options.
//   */
//  public static final int UNRECOGNIZED_OPT_MARK = 2;
//
//  /**
//   * Code for dropping unrecognized options.
//   */
//  public static final int UNRECOGNIZED_OPT_DROP = 3;

  /**
   * Type of a channel header.
   */
  public static final byte OPT_USED_CHANNELS = 77;

  // ////////////////////////////////////////////////
  // locals
  //

  /** id counter field */
  private static short nextId = 0;

  /**
   * The payload of the McExOR message.
   */
  private Message content;

  /**
   * The list of options contained in the McExOR header.
   */
  private ArrayList options;

  /**
   * The protocol number of the message contained in this McExOR header.
   */
  private short nextHeaderType = Constants.NET_PROTOCOL_NO_NEXT_HEADER;

  // ////////////////////////////////////////////////
  // initialization
  //

  public RouteDsrBrnMsg() {
    this.options = new ArrayList();
  }

  /**
   * Creates a new <code>RouteMcExORMsg</code> with the given payload.
   *
   * @param content the payload of the McExOR message. This can be <code>null</code>.
   */
  public RouteDsrBrnMsg(Message content, int flowId, int packetId, List forwarder) {
    this();
    this.content = content;

    addOption(new RouteDsrBrnMsg.OptionId(nextId++, flowId, packetId, forwarder, 0));
  }

  // ////////////////////////////////////////////////
  // helpers
  //

  public String toString() {
    String ret = "DsrEtxMsg";

      Iterator iter = getOptions().iterator();
      while (iter.hasNext()) {
        Option optBuf = (Option) iter.next();
        ret += " [" + optBuf.toString() + "]";
      }

      return ret;
    }


  /**
   * Returns the options contained in the McExOR header.
   *
   * @return <code>List</code> of options.
   */
  public List getOptions() {
    return options;
  }

  /**
   * Returns the the content of the McExOR message.
   *
   * @return the content of the McExOR message, or <code>null</code> if none.
   */
  public Message getContent() {
    return content;
  }

  /**
   * Returns the network protocol number of the content of the McExOR message.
   *
   * @return the protocol number of the content of the McExOR message. This is
   *         equal to IPv6's No Next Header protocol number if the McExOR header
   *         has no content.
   */
  public short getNextHeaderType() {
    return nextHeaderType;
  }

  /**
   * Sets the protocol number of the content of the McExOR message.
   *
   * @param nextHeaderType the protocol number of the content of the McExOR message.
   */
  public void setNextHeaderType(short nextHeaderType) {
    this.nextHeaderType = nextHeaderType;
  }

  /**
   * {@inheritDoc}
   */
  public void getBytes(byte[] buf, int offset) {
    // Copy the fixed part of the McExOR Options header
    buf[offset + 0] = (byte) nextHeaderType;
    buf[offset + 1] = 0;
    buf[offset + 2] = (byte) ((getOptionsSize() >> 8) & 0xFF);
    buf[offset + 3] = (byte) (getOptionsSize() & 0xFF);
    offset += 4;

    // Copy the options
    for (int i = 0; i < options.size(); i++) {
      Option option = (Option) options.get(i);

      option.getBytes(buf, offset);
      offset += option.getSize();
    }

    // Copy the remainder of the message
    if (content != null) {
      content.getBytes(buf, offset);
    }
  }

  /*
   * (non-Javadoc)
   * @see jist.swans.misc.Message#getSize()
   */
  public int getSize() {
    int headerSize = 4 + getOptionsSize();

    return headerSize + (content == null ? 0 : content.getSize());
  }

  /**
   * Adds a new option to the McExOR message.
   *
   * @param opt the option to add
   */
  public void addOption(Option opt) {
    options.add(opt);
  }

  /**
   * Determines whether the McExOR header contains an option of the given type.
   *
   * @param optType the type of option to search for
   * @return whether the McExOR header contains an option of type
   *         <code>optType</code>.
   */
  public boolean hasOption(byte optType) {
    Iterator iter = options.iterator();

    while (iter.hasNext()) {
      Option opt = (Option) iter.next();
      if (opt.getType() == optType)
        return true;
    }

    return false;
  }

  /**
   * Returns the first option of the specified type
   *
   * @param optType the type to look for
   * @return the first option of the specified type
   */
  public Option getOption(byte optType) {
    Iterator iter = options.iterator();

    while (iter.hasNext()) {
      Option opt = (Option) iter.next();
      if (opt.getType() == optType)
        return opt;
    }

    return null;
  }

  /**
   * Removes the given option.
   *
   * @param optType the type to be removed
   * @return <code>true</code> if it was possible
   */
  public boolean removeOption(byte optType) {
    Iterator iter = options.iterator();

    while (iter.hasNext()) {
      Option opt = (Option) iter.next();
      if (opt.getType() == optType) {
        iter.remove();
        return true;
      }
    }

    return false;
  }
  /**
   * Determines the size in bytes of the options contained in this McExOR
   * message.
   *
   * @return the size in bytes of the options contained in this McExOR message.
   *         This value does not include the size of the fixed portion of the
   *         McExOR header or the payload of the message.
   */
  private int getOptionsSize() {
    int optionsSize = 0;

    for (int i = 0; i < options.size(); i++) {
      optionsSize += ((Option) options.get(i)).getSize();
    }

    return optionsSize;
  }

  /**
   * Retrieves the Source Route option from the given McExOR message, or
   * <code>null</code> if none exists.
   *
   * @return the Source Route option from <code>msg</code>, or
   *         <code>null</code> if none exists.
   */
  public OptionSourceRoute getSourceRoute() {
    Iterator iter = getOptions().iterator();

    while (iter.hasNext()) {
      Option opt = (Option) iter.next();
      if (opt.getType() != RouteDsrBrnMsg.OPT_SOURCE_ROUTE)
        continue;

      return (RouteDsrBrnMsg.OptionSourceRoute)opt;
    }

    return null;
  }

  /**
   * {@inheritDoc}
   */
  public Object clone() {
    // Note that this clone is fairly shallow -- it doesn't copy the
    // content of the McExOR message, and the individual options are
    // not cloned either.
    RouteDsrBrnMsg copy = new RouteDsrBrnMsg();
    copy.content = content;
    copy.options = (ArrayList) options.clone();
    copy.nextHeaderType = nextHeaderType;
    return copy;
  }

  /*
   * (non-Javadoc)
   * @see java.lang.Object#hashCode()
   */
  public int hashCode() {
    final int PRIME = 31;
    int result = 1;
    result = PRIME * result + ((content == null) ? 0 : content.hashCode());
    result = PRIME * result + nextHeaderType;
    result = PRIME * result + ((options == null) ? 0 : options.hashCode());
    return result;
  }

  /*
   * (non-Javadoc)
   * @see java.lang.Object#equals(java.lang.Object)
   */
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    final RouteDsrBrnMsg other = (RouteDsrBrnMsg) obj;
    if (content == null) {
      if (other.content != null)
        return false;
    } else if (!content.equals(other.content))
      return false;
    if (nextHeaderType != other.nextHeaderType)
      return false;
    if (options == null) {
      if (other.options != null)
        return false;
    } else if (!options.equals(other.options))
      return false;
    return true;
  }

  // ////////////////////////////////////////////////
  // internal classes
  //

  /**
   * The base class for all McExOR header options.
   */
  public abstract static class Option implements Message {

    private byte type;
    private int length;

    /**
     * Creates a new option from the given byte array, starting at the given
     * offset.
     * @param type   the type of option
     * @param length the length of the option
     */
    protected Option(byte type, int length) {
      this.type = type;
      this.length = length;
    }

    /**
     * Returns the McExOR type code for this kind of option.
     *
     * @return the McExOR type code for this kind of option.
     */
    public byte getType() {
      return type;
    }

    /**
     * Returns the size in bytes of this option.
     *
     * @return the size in bytes of this option.
     */
    public int getSize() {
      return this.length;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
      final int PRIME = 31;
      int result = 1;
      result = PRIME * result + length;
      return result;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (getClass() != obj.getClass())
        return false;
      final Option other = (Option) obj;
      if (length != other.length)
        return false;
      if (type != other.type)
        return false;
      return true;
    }
  }

  /**
   * A Route Request option.
   */
  public static class OptionRouteRequest extends Option {

    protected short id;

    protected NetAddress targetAddress;

    protected RouteDsrBrnMsg.RouteEntry[] addrs;

    /**
     * Creates a new Route Request option.
     *
     * @param id     the identification number of the route request
     * @param target the address of the node being searched for
     * @param addrs  the addresses of the nodes that have forwarded this request
     */
    public OptionRouteRequest(short id, NetAddress target, RouteDsrBrnMsg.RouteEntry[] addrs) {
      // Set the Option Type and Option Data Length fields
      super(OPT_ROUTE_REQUEST, 8 + 4*addrs.length);

      this.id = id;
      this.targetAddress = target;
      this.addrs = addrs;
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    public String toString() {
      String strRoute = "Route Request " + getId();
      return strRoute;
    }

    /**
     * Returns the number of addresses listed in the route request option (not
     * includingd the target address).
     *
     * @return the number of addresses listed in the route request option (not
     *         including the target address).
     */
    public int getNumAddresses() {
      return this.addrs.length;
    }

    /**
     * Returns the nth route entry listed in the route request option, counting from
     * zero.
     *
     * @param n the index into the Route Request
     * @return the address indexed by <code>n</code>.
     */
    public RouteDsrBrnMsg.RouteEntry getAddress(int n) {
      return addrs[n];
    }

    public RouteDsrBrnMsg.RouteEntry[] getAddrs() {
      return addrs;
    }

    public short getId() {
      return id;
    }

    public NetAddress getTargetAddress() {
      return targetAddress;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
      final int PRIME = 31;
      int result = super.hashCode();
      result = PRIME * result + Arrays.hashCode(addrs);
      result = PRIME * result + id;
      result = PRIME * result + ((targetAddress == null) ? 0 : targetAddress.hashCode());
      return result;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (!super.equals(obj))
        return false;
      if (getClass() != obj.getClass())
        return false;
      final OptionRouteRequest other = (OptionRouteRequest) obj;
      if (!Arrays.equals(addrs, other.addrs))
        return false;
      if (id != other.id)
        return false;
      if (targetAddress == null) {
        if (other.targetAddress != null)
          return false;
      } else if (!targetAddress.equals(other.targetAddress))
        return false;
      return true;
    }

    /*
     * (non-Javadoc)
     * @see jist.swans.misc.Message#getBytes(byte[], int)
     */
    public void getBytes(byte[] opt, int offset) {
      // Set the Option Type and Option Data Length fields
      opt[offset + 0] = getType();
      opt[offset + 1] = (byte)(this.getSize() - 2);

      // Set the identification field
      opt[offset + 2] = (byte)(id >>> 8);
      opt[offset + 3] = (byte)(id & 0xFF);

      // Set the target and intermediate addresses
      System.arraycopy(targetAddress.toIP().getAddress(), 0, opt, offset + 4, 4);

      for (int i = 0; i < addrs.length; i++) {
        System.arraycopy(addrs[i].addr.toIP().getAddress(), 0, opt, offset + 4*(i+2), 4);
      }
    }

  }

  /**
   * A Route Reply option.
   */
  public static class OptionRouteReply extends Option {

    protected boolean lastHopExternal;

    protected RouteDsrBrnMsg.RouteEntry[] addrs;

    /**
     * Creates a new Route Reply option with the Last Hop External field set to
     * false.
     *
     * @param addrs the addresses of the nodes in the route
     */
    public OptionRouteReply(boolean lastHopExternal, RouteDsrBrnMsg.RouteEntry[] addrs) {
      super(OPT_ROUTE_REPLY, 3 + (4 + 1) * addrs.length);
      this.addrs = addrs;
      this.lastHopExternal = lastHopExternal;
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    public String toString() {
      String strRoute = "Route Reply [... ";
      for (int i = 0; i < getNumAddresses(); i++) {
        strRoute += getAddress(i) + " ";
      }
      return strRoute + " ...]";
    }

    /**
     * Returns the number of addresses listed in the Route Reply option.
     *
     * @return the number of addresses listed in the Route Reply option.
     */
    public int getNumAddresses() {
      return addrs.length;
    }

    /**
     * Returns the nth address listed in the route request option, counting from
     * zero.
     *
     * @param n the index into the route
     * @return the address indexed by <code>n</code>.
     */
    public RouteDsrBrnMsg.RouteEntry getAddress(int n) {
      return this.addrs[n];
    }

    /**
     * Returns the addresses listed in the route request option
     *
     * @return the addresss
     */
    public RouteDsrBrnMsg.RouteEntry[] getAddrs() {
      return addrs;
    }

    /**
     * Determines whether the last hop of the path is external.
     *
     * @return whether the last hop of this route is external.
     */
    public boolean isLastHopExternal() {
      return lastHopExternal;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
      final int PRIME = 31;
      int result = super.hashCode();
      result = PRIME * result + Arrays.hashCode(addrs);
      result = PRIME * result + (lastHopExternal ? 1231 : 1237);
      return result;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (!super.equals(obj))
        return false;
      if (getClass() != obj.getClass())
        return false;
      final OptionRouteReply other = (OptionRouteReply) obj;
      if (!Arrays.equals(addrs, other.addrs))
        return false;
      if (lastHopExternal != other.lastHopExternal)
        return false;
      return true;
    }

    /*
     * (non-Javadoc)
     * @see jist.swans.misc.Message#getBytes(byte[], int)
     */
    public void getBytes(byte[] opt, int offset) {
      // Set the Option Type and Option Data Length fields
      opt[offset + 0] = getType();
      opt[offset + 1] = (byte)(this.getSize() - 2);

      // Set the Last Hop External and Reserved fields
      opt[offset + 3] = lastHopExternal ? (byte)0x80 : 0x00;

      // Set the route addresses
      for (int i = 0; i < addrs.length; i++) {
        System.arraycopy(addrs[i].addr.toIP().getAddress(), 0, opt, offset + 3 + 4*i, 4);
      }
    }

  }

  /**
   * A Route Error option.
   */
  public static class OptionRouteError extends Option {

    protected byte errorType;

    protected int salvageCount;

    protected NetAddress sourceAddress;

    protected NetAddress destAddress;

    protected byte[] tsi;

    /**
     * Creates a new Route Error option.
     *
     * @param type    the code corresponding to the type of error being reported
     * @param salvage the salvage count
     * @param src     the source address of the error
     * @param dest    the destination address of the error
     * @param tsi     any type-specific information for this error
     */
    public OptionRouteError(byte type, int salvage, NetAddress src,
        NetAddress dest, byte[] tsi) {
      super(OPT_ROUTE_ERROR, 12 + tsi.length);
    }

    public String toString() {
      String ret = "Route Error";
      return ret;
    }

    /**
     * Returns the error code contained in this Route Error.
     *
     * @return the error code contained in this Route Error.
     */
    public int getErrorType() {
      return errorType;
    }

    /**
     * Returns the salvage count of this Route Error.
     *
     * @return the salvage count of this Route Error.
     */
    public int getSalvageCount() {
      return salvageCount;
    }

    /**
     * Returns the source of this Route Error.
     *
     * @return the source of this Route Error.
     */
    public NetAddress getSourceAddress() {
      return sourceAddress;
    }

    /**
     * Returns the destination of this Route Error.
     *
     * @return the destination of this Route Error.
     */
    public NetAddress getDestAddress() {
      return destAddress;
    }

    /**
     * Returns the size in bytes of any type-specific information contained in
     * this Route Error.
     *
     * @return the size in bytes of the type-specific information.
     */
    public int getTypeSpecificInfoSize() {
      return tsi.length;
    }

    /**
     * Returns the type-specific information contained in this Route Error.
     *
     * @param buf    the byte array to copy the type-specific information into
     * @param offset the offset into <code>buf</code> where copying should begin
     * @return the number of bytes copied into <code>buf</code>.
     */
    public int getTypeSpecificInfoBytes(byte[] buf, int offset) {
      int numBytesToCopy = Math.min(tsi.length, buf.length - offset);
      System.arraycopy(tsi, 0, buf, offset, numBytesToCopy);
      return numBytesToCopy;
    }

    /*
     * (non-Javadoc)
     * @see jist.swans.misc.Message#getBytes(byte[], int)
     */
    public void getBytes(byte[] opt, int offset) {
      // Set the Option Type and Option Data Length fields
      opt[offset + 0] = getType();
      opt[offset + 1] = (byte)(this.getSize() - 2);

      opt[offset + 2] = errorType;

      // Set the Salvage and Reserved fields.
      if (salvageCount < 0 || salvageCount > 0xF)
        throw new IllegalArgumentException("Salvage count too high");

      opt[offset + 3] = (byte)salvageCount;

      // Set the source and destination fields
      System.arraycopy(sourceAddress.toIP().getAddress(), 0, opt, offset + 4, 4);
      System.arraycopy(destAddress.toIP().getAddress(), 0, opt, offset + 8, 4);

      // Set the Type-Specific Information field
      System.arraycopy(tsi, 0, opt, offset + 12, tsi.length);
    }

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
      final int PRIME = 31;
      int result = super.hashCode();
      result = PRIME * result + ((destAddress == null) ? 0 : destAddress.hashCode());
      result = PRIME * result + salvageCount;
      result = PRIME * result + ((sourceAddress == null) ? 0 : sourceAddress.hashCode());
      result = PRIME * result + Arrays.hashCode(tsi);
      return result;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (!super.equals(obj))
        return false;
      if (getClass() != obj.getClass())
        return false;
      final OptionRouteError other = (OptionRouteError) obj;
      if (destAddress == null) {
        if (other.destAddress != null)
          return false;
      } else if (!destAddress.equals(other.destAddress))
        return false;
      if (errorType != other.errorType)
        return false;
      if (salvageCount != other.salvageCount)
        return false;
      if (sourceAddress == null) {
        if (other.sourceAddress != null)
          return false;
      } else if (!sourceAddress.equals(other.sourceAddress))
        return false;
      if (!Arrays.equals(tsi, other.tsi))
        return false;
      return true;
    }
  }

  /**
   * An Acknowledgement Request option.
   */
  public static class OptionAckRequest extends Option {

    protected short id;

    /**
     * Creates a new Acknowledgement Request option.
     *
     * @param id the identification number of the acknowledgement request
     */
    public OptionAckRequest(short id) {
      super(OPT_ACK_REQUEST, 4);
      this.id = id;
    }

    public String toString() {
      String s = "Acknowledgement Request " + getId();
      return s;
    }

    /**
     * Returns the id number of the Acknowledgement Request.
     *
     * @return the id number of the Acknowledgement Request.
     */
    public short getId() {
      return id;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
      final int PRIME = 31;
      int result = super.hashCode();
      result = PRIME * result + id;
      return result;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (!super.equals(obj))
        return false;
      if (getClass() != obj.getClass())
        return false;
      final OptionAckRequest other = (OptionAckRequest) obj;
      if (id != other.id)
        return false;
      return true;
    }

    /*
     * (non-Javadoc)
     * @see jist.swans.misc.Message#getBytes(byte[], int)
     */
    public void getBytes(byte[] opt, int offset) {
      // Set the Option Type and Option Data Length fields
      opt[offset + 0] = getType();
      opt[offset + 1] = (byte)(this.getSize() - 2);

      // Set the identification field
      opt[offset + 2] = (byte)(id >>> 8);
      opt[offset + 3] = (byte)(id & 0xFF);
    }

  }

  /**
   * An Acknowledgement option.
   */
  public static class OptionAck extends Option {

    protected short id;

    protected NetAddress sourceAddress;

    protected NetAddress destAddress;

    /**
     * Creates a new Acknowledgement option.
     *
     * @param id   the identification number of the acknowledgement
     * @param sourceAddress  the source address of the acknowledgement
     * @param destAddress the destination address of the acknowledgement
     */
    public OptionAck(short id, NetAddress sourceAddress, NetAddress destAddress) {
      super(OPT_ACK, 12);
      this.id = id;
      this.sourceAddress = sourceAddress;
      this.destAddress = destAddress;
    }

    public String toString() {
      String s = "Acknowledgement " + getId();
      return s;
    }

    /**
     * Returns the id number of the Acknowledgement.
     *
     * @return the id number of the Acknowledgement.
     */
    public short getId() {
      return id;
    }

    /**
     * Returns the source of the Acknowledgement.
     *
     * @return the source of the Acknowledgement.
     */
    public NetAddress getSourceAddress() {
      return sourceAddress;
    }

    /**
     * Returns the destination of the Acknowledgement.
     *
     * @return the destination of the Acknowledgement.
     */
    public NetAddress getDestAddress() {
      return destAddress;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
      final int PRIME = 31;
      int result = super.hashCode();
      result = PRIME * result + ((destAddress == null) ? 0 : destAddress.hashCode());
      result = PRIME * result + id;
      result = PRIME * result + ((sourceAddress == null) ? 0 : sourceAddress.hashCode());
      return result;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (!super.equals(obj))
        return false;
      if (getClass() != obj.getClass())
        return false;
      final OptionAck other = (OptionAck) obj;
      if (destAddress == null) {
        if (other.destAddress != null)
          return false;
      } else if (!destAddress.equals(other.destAddress))
        return false;
      if (id != other.id)
        return false;
      if (sourceAddress == null) {
        if (other.sourceAddress != null)
          return false;
      } else if (!sourceAddress.equals(other.sourceAddress))
        return false;
      return true;
    }

    /*
     * (non-Javadoc)
     * @see jist.swans.misc.Message#getBytes(byte[], int)
     */
    public void getBytes(byte[] opt, int offset) {
      // Set the Option Type and Option Data Length fields
      opt[offset + 0] = getType();
      opt[offset + 1] = (byte)(this.getSize() - 2);

      // Set the identification field
      opt[offset + 2] = (byte)(id >>> 8);
      opt[offset + 3] = (byte)(id & 0xFF);

      // Set the source and destination fields
      System.arraycopy(sourceAddress.toIP().getAddress(), 0, opt, offset + 4, 4);
      System.arraycopy(destAddress.toIP().getAddress(), 0, opt, offset + 8, 4);
    }

  }

  /**
   * A Source Route option.
   */
  public static class OptionSourceRoute extends Option {

    protected boolean firstHopExternal;

    protected boolean lastHopExternal;

    protected int salvageCount;

    protected int numSegmentsLeft;

    protected RouteDsrBrnMsg.RouteEntry[] addrs;

    /**
     * Creates a new Source Route option.
     *
     * @param firstHopExternal whether the first hop on the route is external
     * @param lastHopExternal  whether the last hop on the route is external
     * @param salvageCount     the salvage count
     * @param numSegmentsLeft  the number of explicitly listed intermediate nodes still to be
     *                         visited before reaching the final destination
     * @param addrs            the addresses of the nodes along the route
     */
    public OptionSourceRoute(boolean firstHopExternal, boolean lastHopExternal,
        int salvageCount, int numSegmentsLeft, RouteDsrBrnMsg.RouteEntry[] addrs) {
      super(OPT_SOURCE_ROUTE, 4 + (4 + 1) * addrs.length);
      this.firstHopExternal = firstHopExternal;
      this.lastHopExternal = lastHopExternal;
      this.salvageCount = salvageCount;
      this.numSegmentsLeft = numSegmentsLeft;
      this.addrs = addrs;
    }

    public String toString() {
      String s = "Source Route [";
      for (int i = 0; i < getNumAddresses(); i++) {
        s += getAddress(i) + " ";
      }
      return s + "]";
    }

    /**
     * Indicates whether the first hop on the route is external to the McExOR
     * network.
     *
     * @return whether the first hop is external.
     */
    public boolean isFirstHopExternal() {
      return firstHopExternal;
    }

    /**
     * Indicates whether the last hop on the route is external to the McExOR
     * network.
     *
     * @return whether the last hop is external.
     */
    public boolean isLastHopExternal() {
      return lastHopExternal;
    }

    /**
     * Returns the salvage count of this option.
     *
     * @return the salvage count of this option.
     */
    public int getSalvageCount() {
      return salvageCount;
    }

    /**
     * Returns the number of segments on the source route that have yet to be
     * traversed.
     *
     * @return the number of segments on the route left to go.
     */
    public int getNumSegmentsLeft() {
      return numSegmentsLeft;
    }

    /**
     * Returns the number of addresses in the route, not including the source
     * and destination addresses.
     *
     * @return the number of addresses in the route.
     */
    public int getNumAddresses() {
      return addrs.length;
    }

    /**
     * Returns the nth address on the route, counting the first hop after the
     * source as node zero.
     *
     * @param n the index into the route
     * @return the address in the route indexed by <code>n</code>.
     */
    public RouteDsrBrnMsg.RouteEntry getAddress(int n) {
      return addrs[n];
    }

    /**
     * Returns the complete route. Please do not change it!
     * 
     * @return the complete route
     */
    public RouteDsrBrnMsg.RouteEntry[] getRoute() {
      return addrs;
    }

    /**
     * Determines the intended next recipient of a packet with the given source
     * route option and IP destination.
     *
     * @param dst         the destination IP address
     * @return the address of the next recipient of the message. If the Source
     *         Route option is invalid in some way, <code>null</code> can be
     *         returned.
     */
    public NetAddress nextRecipient(NetAddress dst) {
      int curSegment = getNumAddresses() - getNumSegmentsLeft();

      if (curSegment < getNumAddresses())
        return getAddress(curSegment).addr;
      else if (curSegment == getNumAddresses())
        return dst;

      return null;
    }

    /**
     * Determines the previous recipient of a packet with the given source route
     * option and IP source.
     *
     * @param src         the source IP address
     * @return the address of the previous recipient of this message. If the
     *         Source Route option is invalid in some way, <code>null</code>
     *         can be returned.
     */
    public NetAddress prevRecipient(NetAddress src) {
      int prevSegment = getNumAddresses() - getNumSegmentsLeft() - 1;

      if (0 <= prevSegment && prevSegment < getNumAddresses())
        return getAddress(prevSegment).addr;
      else if (prevSegment == -1)
        return src;

      return null;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
      final int PRIME = 31;
      int result = super.hashCode();
      result = PRIME * result + Arrays.hashCode(addrs);
      result = PRIME * result + (firstHopExternal ? 1231 : 1237);
      result = PRIME * result + (lastHopExternal ? 1231 : 1237);
      result = PRIME * result + numSegmentsLeft;
      result = PRIME * result + salvageCount;
      return result;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (!super.equals(obj))
        return false;
      if (getClass() != obj.getClass())
        return false;
      final OptionSourceRoute other = (OptionSourceRoute) obj;
      if (!Arrays.equals(addrs, other.addrs))
        return false;
      if (firstHopExternal != other.firstHopExternal)
        return false;
      if (lastHopExternal != other.lastHopExternal)
        return false;
      if (numSegmentsLeft != other.numSegmentsLeft)
        return false;
      if (salvageCount != other.salvageCount)
        return false;
      return true;
    }

    /*
     * (non-Javadoc)
     * @see jist.swans.misc.Message#getBytes(byte[], int)
     */
    public void getBytes(byte[] opt, int offset) {
      // Set the Option Type and Option Data Length fields
      opt[offset + 0] = getType();
      opt[offset + 1] = (byte)(this.getSize() - 2);

      // Set the First Hop External, Last Hop External, and Reserved fields, along
      // with the upper two bits of the Salvage field
      if (salvageCount < 0 || salvageCount > 0xF)
        throw new IllegalArgumentException("Salvage count too high");

      opt[offset + 2] = 0;
      if (firstHopExternal) opt[offset + 2] |= 0x80;
      if (lastHopExternal) opt[offset + 2] |= 0x40;
      opt[offset + 2] |= (byte)(salvageCount >> 2);

      // Set the rest of the Salvage field and the Segments Left field
      if (numSegmentsLeft < 0 || numSegmentsLeft > 0x3F)
        throw new IllegalArgumentException("Segments Left count too high");

      opt[offset + 3] = 0;
      opt[offset + 3] |= (byte)((salvageCount << 6) & 0xC0);
      opt[offset + 3] |= numSegmentsLeft;

      // Set the addresses
      for (int i = 0; i < addrs.length; i++) {
        System.arraycopy(addrs[i].addr.toIP().getAddress(), 0, opt, offset + 4 + 4*i, 4);
      }
    }

  }

  /**
   * An Acknowledgement Request option.
   */
  public static class OptionId extends Option implements Cloneable {

    protected short id;

    /** flow id, it is only an annotation and not actually transmitted */
    protected int annoFlowId;

    /** packet id within flow, it is only an annotation and not actually transmitted */
    protected int annoPacketId;

    /** list of actual forwarders */
    protected List forwarder;
    
    /** number of hops the packet actually traveled, not necessarily forwarder.size() */
    protected int hopCount;

    /**
     * Creates a new id option.
     *
     * @param id the identification number
     */
    public OptionId(short id, int annoFlowId, int annoPacketId, List forwarder, int hopCount) {
      super(OPT_ID, 4);
      this.id = id;
      this.annoFlowId = annoFlowId;
      this.annoPacketId = annoPacketId;
      this.forwarder = forwarder;
      this.hopCount = hopCount;
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    public String toString() {
      String s = "ID " + getId() + " " + annoFlowId + "/"  + annoPacketId;
      return s;
    }

    /**
     * Returns the id number
     *
     * @return the id number
     */
    public short getId() {
      return id;
    }

    public int getAnnoFlowId() {
      return annoFlowId;
    }

    public int getAnnoPacketId() {
      return annoPacketId;
    }

    /**
     * @return the hopCount
     */
    public int getHopCount() {
      return hopCount;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
      final int PRIME = 31;
      int result = super.hashCode();
      result = PRIME * result + id;
      return result;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (!super.equals(obj))
        return false;
      if (getClass() != obj.getClass())
        return false;
      final OptionId other = (OptionId) obj;
      if (id != other.id)
        return false;
      return true;
    }

    /*
     * (non-Javadoc)
     * @see jist.swans.misc.Message#getBytes(byte[], int)
     */
    public void getBytes(byte[] opt, int offset) {
      // Set the Option Type and Option Data Length fields
      opt[offset + 0] = getType();
      opt[offset + 1] = (byte)(this.getSize() - 2);

      // Set the identification field
      opt[offset + 2] = (byte)(id >>> 8);
      opt[offset + 3] = (byte)(id & 0xFF);
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#clone()
     */
    public Object clone() {
      return new OptionId(this.id, this.annoFlowId, this.annoPacketId, 
          new ArrayList(this.forwarder), this.hopCount);
    }

    /**
     * @return the forwarder
     */
    public List getForwarder() {
      return forwarder;
    }

    public void addForwarder(NetAddress localAddr) {
      this.forwarder.add(localAddr);
    }

    public void setHopCount(int hopCount) {
      this.hopCount = hopCount;
    }

    public void intHopCount() {
      this.hopCount++;
    }
  }

  /**
   * This is used by McExOR - move it to another class.
   */
  public static class OptionUsedChannels extends Option {

    protected byte[] usedChannels;

    /**
     * Creates a new id option.
     *
     * @param usedChannels the array of used RF channels
     */
    public OptionUsedChannels(byte[] usedChannels) {
      super(OPT_USED_CHANNELS, usedChannels.length);
      this.usedChannels = usedChannels;
    }

    public String toString() {
      StringBuffer str = new StringBuffer();

      for (int i = 0; i < usedChannels.length; i++) {
        str.append(usedChannels[i]);
        if (i < usedChannels.length - 1)
          str.append(", ");
      }

      return "Used channels: " + str.toString();
    }

    /*
     * (non-Javadoc)
     * @see jist.swans.misc.Message#getBytes(byte[], int)
     */
    public void getBytes(byte[] opt, int offset) {
      // Set the Option Type and Option Data Length fields
      opt[offset + 0] = getType();
      opt[offset + 1] = (byte)(this.getSize() - 2);

      // Set the used channels
      System.arraycopy(usedChannels, 0, opt, offset + 2, usedChannels.length);
    }

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
      final int PRIME = 31;
      int result = super.hashCode();
      result = PRIME * result + Arrays.hashCode(usedChannels);
      return result;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (!super.equals(obj))
        return false;
      if (getClass() != obj.getClass())
        return false;
      final OptionUsedChannels other = (OptionUsedChannels) obj;
      if (!Arrays.equals(usedChannels, other.usedChannels))
        return false;
      return true;
    }
  }

  /**
   * This class represents an entry into a route (used to update the link table)
   */
  public static class RouteEntry implements Comparable {
    public NetAddress addr;
    public int metric;

    public RouteEntry(NetAddress addr, int metric) {
      this.addr = addr;
      this.metric = metric;
    }

    public int compareTo(Object o) {
      RouteEntry other = (RouteEntry) o;
      return this.metric - other.metric;
    }

    public String toString() {
      return "[" + addr + "," + metric + "]";
    }

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
      final int PRIME = 31;
      int result = 1;
      result = PRIME * result + ((addr == null) ? 0 : addr.hashCode());
      result = PRIME * result + metric;
      return result;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (getClass() != obj.getClass())
        return false;
      final RouteEntry other = (RouteEntry) obj;
      if (addr == null) {
        if (other.addr != null)
          return false;
      } else if (!addr.equals(other.addr))
        return false;
      if (metric != other.metric)
        return false;
      return true;
    }
  }

}
