package jist.swans.mac;

import java.util.Arrays;

import jist.swans.Constants;
import jist.swans.misc.Message;
import jist.swans.misc.Pickle;

public interface MacDcfMessage extends MacMessage {

  public static final int TYPE_ACK = 0;

  public static final int TYPE_DATA = 1;

  /**
   * Return packet type.
   *
   * @return packet type
   */
  int getType();

  /**
   * Return packet destination address.
   *
   * @return packet destination address
   */
  MacAddress getDst();

  /**
   * Return packet transmission duration.
   *
   * @return packet transmission duration
   */
  int getDuration();


  // ////////////////////////////////////////////////
  // ACK frame: (size = 14)
  // frame control size: 2
  // duration size: 2
  // address: destination size: 6
  // CRC size: 4

  /**
   * An 802_11 Acknowlege packet.
   *
   * @author Rimon Barr &lt;barr+jist@cs.cornell.edu&gt;
   * @since SWANS1.0
   */

  public static class Ack extends AbstractMacMessage
      implements MacDcfMessage, HasDst {

    /**
     * destination of the packet;
     */
    protected MacAddress dst;

    /**
     * Packet transmission duration.
     */
    protected int duration;

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

    /**
     * Create 802_11 ACK packet.
     *
     * @param dst
     *          packet destination address
     * @param duration
     *          packet transmission duration
     */
    public Ack(MacAddress dst, int duration) {
      this.dst = dst;
      this.duration = duration;
    }

    // ////////////////////////////////////////////////
    // accessors
    //

    /*
     * (non-Javadoc)
     * @see jist.swans.mac.MacMessage#getAdapter(java.lang.Class)
     */
    public Object getAdapter(Class adapter) {
      if (adapter == classHasDst)
        return this;
      return super.getAdapter(adapter);
    }

    /**
     * Return packet type.
     *
     * @return packet type
     */
    public int getType() {
      return TYPE_ACK;
    }

    /**
     * Return packet destination address.
     *
     * @return packet destination address
     */
    public MacAddress getDst() {
      return dst;
    }

    /**
     * Return packet transmission duration.
     *
     * @return packet transmission duration
     */
    public int getDuration() {
      return duration;
    }

    // ////////////////////////////////////////////////
    // message interface
    //

    /** {@inheritDoc} */
    public int getSize() {
      return getHeaderSize();
    }

    // Message interface
    /** {@inheritDoc} */
    public void getBytes(byte[] msg, int offset) {
      // 0: fc[0]
      Pickle.ubyteToArray(0xD4, msg, offset + 0);
      // 1: fc[1]
      Pickle.ubyteToArray(0x00, msg, offset + 1);
      // 2-3: dur
      Pickle.ushortToArray(duration, msg, offset + 2);
      // 4-9: addr1
      Pickle.MacAddressToArray(dst, msg, offset + 4);
      // 10-13: fcs
//      Pickle.MacAddressToArray(src, msg, offset + 10);
    }

    public static int getHeaderSize() {
      return 14;
    }

    public int hashCode() {
      final int PRIME = 31;
      int result = 1;
      result = PRIME * result + ((dst == null) ? 0 : dst.hashCode());
      result = PRIME * result + duration;
      return result;
    }

    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (getClass() != obj.getClass())
        return false;
      final Ack other = (Ack) obj;
      if (dst == null) {
        if (other.dst != null)
          return false;
      } else if (!dst.equals(other.dst))
        return false;
      if (duration != other.duration)
        return false;
      return true;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    public String toString() {
      return "Ack [->" + dst.toString() + "]";
    }
  } // class: ACK


  // ////////////////////////////////////////////////
  // DATA frame: (size = 28/34 + body)
  // frame control size: 2
  // duration / ID size: 2
  // address: destination size: 6
  // address: source size: 6
  // address: #3 size: 6
  // sequence control size: 2
  // address: #4 size: 6 - not used in mesh network!
  //   --> see 802.11, 7.2.2 Data frames (Table 4)
  // frame body size: 0 - 2312
  // CRC size: 4

  /**
   * An 802_11 Data packet.
   *
   * @author Rimon Barr &lt;barr+jist@cs.cornell.edu&gt;
   * @since SWANS1.0
   */

  public static class Data extends AbstractMacMessage implements
      MacDcfMessage, HasSeq, HasDst, HasSrc, HasBody {

    /**
     * Packet sequence number limit.
     */
    public static final short MAX_SEQ = 4096;

    /**
     * source of the packet;
     */
    protected MacAddress src;

    /**
     * destination of the packet;
     */
    protected MacAddress dst;

    /**
     * Packet sequence number.
     */
    protected short seq;

    /**
     * Packet transmission duration.
     */
    protected int duration;

    /**
     * packet retry bit.
     */
    protected boolean retry;

    /**
     * Packet payload;
     */
    protected Message body;

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

    /**
     * Create 802_11 data packet.
     *
     * @param dst
     *          packet destination address
     * @param src
     *          packet source address
     * @param duration
     *          packet transmission duration
     * @param seq
     *          packet sequence number
     * @param retry
     *          packet retry bit
     * @param body
     *          packet data payload
     */
    public Data(MacAddress dst, MacAddress src, int duration, short seq,
        boolean retry, Message body) {
      this.dst = dst;
      this.src = src;
      this.duration = duration;
      this.seq = seq;
      this.retry = retry;
      this.body = body;
    }

    /**
     * Create 802_11 data packet.
     *
     * @param dst
     *          packet destination address
     * @param src
     *          packet source address
     * @param duration
     *          packet transmission duration
     * @param body
     *          packet data payload
     */
    public Data(MacAddress dst, MacAddress src, int duration, Message body) {
      this(dst, src, duration, (short) -1, false, body);
    }

    // ////////////////////////////////////////////////
    // accessors
    //

    /*
     * (non-Javadoc)
     * @see jist.swans.mac.MacMessage#getAdapter(java.lang.Class)
     */
    public Object getAdapter(Class adapter) {
      if (adapter == classHasSrc)
        return this;
      if (adapter == classHasDst)
        return this;
      if (adapter == classHasSrc)
        return this;
      if (adapter == classHasBody)
        return this;
      return super.getAdapter(adapter);
    }

    /**
     * Return packet type.
     *
     * @return packet type
     */
    public int getType() {
      return TYPE_DATA;
    }

    public MacAddress getSrc() {
      return src;
    }

    public MacAddress getDst() {
      return dst;
    }

    public int getDuration() {
      return duration;
    }

    public short getSeq() {
      return seq;
    }

    public boolean getRetry() {
      return retry;
    }

    public Message getBody() {
      return body;
    }

    // ////////////////////////////////////////////////
    // message interface
    //

    /** {@inheritDoc} */
    public int getSize() {
      int size = body.getSize();
      if (size == Constants.ZERO_WIRE_SIZE) {
        return Constants.ZERO_WIRE_SIZE;
      }
      return size + getHeaderSize();
    }

    public void get802_11HeaderBytes(byte[] msg, int offset) {
      // 0: fc[0]
      Pickle.ubyteToArray(0x08, msg, offset + 0);
      // 1: fc[1]
      Pickle.ubyteToArray(retry ? 0x08 : 0, msg, offset + 1);
      // 2-3: dur
      Pickle.ushortToArray(duration, msg, offset + 2);
      // 4-9: addr1
      Pickle.MacAddressToArray(dst, msg, offset + 4);
      // 10-15: addr2
      Pickle.MacAddressToArray(src, msg, offset + 10);
      // 16-21: addr3
      Arrays.fill(msg, offset + 16, offset + 20, (byte)0);
      // 22-23: seq
      Pickle.shortToArray((short)(seq << 4), msg, offset + 22);
    }

    public static int getHeaderSize() {
      return 28 + 8; // 28 mac + 8 llc
    }

    /*
     * (non-Javadoc)
     * @see jist.swans.misc.Message#getBytes(byte[], int)
     */
    public void getBytes(byte[] msg, int offset) {
      // Prepare 802.11 header
      get802_11HeaderBytes(msg, offset);

      // 0-7 llc
      Pickle.uintToArray(0xaaaa0300, msg, offset + 24);
      msg[offset + 24 + 4] = 0;
      msg[offset + 24 + 5] = 0;
      Pickle.ushortToArray(0x0800, msg, offset + 24 + 6);

      // Prepend body
      body.getBytes(msg, offset + 24 + 8);
    }

    public int hashCode() {
      final int PRIME = 31;
      int result = 1;
      result = PRIME * result + ((body == null) ? 0 : body.hashCode());
      result = PRIME * result + ((dst == null) ? 0 : dst.hashCode());
      result = PRIME * result + duration;
      result = PRIME * result + (retry ? 1231 : 1237);
      result = PRIME * result + seq;
      result = PRIME * result + ((src == null) ? 0 : src.hashCode());
      return result;
    }

    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (getClass() != obj.getClass())
        return false;
      final Data other = (Data) obj;
      if (body == null) {
        if (other.body != null)
          return false;
      } else if (!body.equals(other.body))
        return false;
      if (dst == null) {
        if (other.dst != null)
          return false;
      } else if (!dst.equals(other.dst))
        return false;
      if (duration != other.duration)
        return false;
      if (retry != other.retry)
        return false;
      if (seq != other.seq)
        return false;
      if (src == null) {
        if (other.src != null)
          return false;
      } else if (!src.equals(other.src))
        return false;
      return true;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    public String toString() {
      return "Data [" + src.toString() + "->" + dst.toString() + ", seq "
        + seq + (retry ? ", retry][" : "][") + body.toString() + "]";
    }
  } // class: Data

}
