package jist.swans.mac;

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

/* see table 20en page 66 for needed frames in an QIBSS */
public interface Mac802_11eMessage extends Mac802_11Message {

  /** TYPE b3 b2 */

  /** 00 00 */
  public static final int WIFI_FC0_TYPE_MANAGEMENT = 0x00;

  /** 01 00 */
  public static final int WIFI_FC0_TYPE_CONTROL = 0x10;

  /** 10 00 */
  public static final int WIFI_FC0_TYPE_DATA = 0x08;

  /** 11 00 */
  public static final int WIFI_FC0_TYPE_ACTION = 0xc0;

  /** SUBTYPE b7 b6 b5 b4 */

  /* for TYPE_CTL */
  public static final int WIFI_FC0_SUBTYPE_BLOCKACK_REQ = 0x08;

  public static final int WIFI_FC0_SUBTYPE_BLOCKACK_RESP = 0x09;

  // public static final int WIFI_FC0_SUBTYPE_PS_POLL = 0x0a;

  // public static final int WIFI_FC0_SUBTYPE_RTS = 0x0b;
  //  
  // public static final int WIFI_FC0_SUBTYPE_CTS = 0x0c;
  //  
  // public static final int WIFI_FC0_SUBTYPE_ACK = 0x0d;

  // public static final int WIFI_FC0_SUBTYPE_CF_END = 0x0e;
  //  
  // public static final int WIFI_FC0_SUBTYPE_CF_END_CF_ACK = 0x0f;

  /* for TYPE DATA */

  public static final int WIFI_FC0_SUBTYPE_DATA = 0x00;

  // public static final int WIFI_FC0_SUBTYPE_DATA_CF_ACK = 0x10;
  //  
  // public static final int WIFI_FC0_SUBTYPE_DATA_CF_POLL = 0x20;
  //  
  // public static final int WIFI_FC0_SUBTYPE_DATA_CF_ACK_CF_POLL = 0x30;

  public static final int WIFI_FC0_SUBTYPE_NULL = 0x40;

  // public static final int WIFI_FC0_SUBTYPE_CF_ACK = 0x50;
  //  
  // public static final int WIFI_FC0_SUBTYPE_CF_POLL = 0x60;
  //  
  // public static final int WIFI_FC0_SUBTYPE_CF_ACK_CF_POLL = 0x70;

  public static final int WIFI_FC0_SUBTYPE_QOS_DATA = 0x80;

  // public static final int WIFI_FC0_SUBTYPE_QOS_DATA_CFACK = 0x90;
  //
  // public static final int WIFI_FC0_SUBTYPE_QOS_DATA_CFPOLL = 0xa0;
  //  
  // public static final int WIFI_FC0_SUBTYPE_QOS_DATA_CFACK_CFPOLL = 0xb0;

  public static final int WIFI_FC0_SUBTYPE_QOS_NULL = 0xc0;

  // public static final int WIFI_FC0_SUBTYPE_QOS_DATA_CFPOLL_NODATA = 0xe0;
  //  
  // public static final int WIFI_FC0_SUBTYPE_QOS_DATA_CFACK_CFPOLL_NODATA =
  // 0xf0;

  /* TYPE COMPARE */

  // /** has CF_ACK */
  // public static final int TYPE_CFACK = WIFI_FC0_TYPE_DATA | 0x10;
  //  
  // /** has CF_POLL */
  // public static final int TYPE_CFPOLL = WIFI_FC0_TYPE_DATA | 0x20;
  /** is NULL DATA */
  public static final int TYPE_NODATA = WIFI_FC0_TYPE_DATA | 0x40;

  /** has QOS Control field in Header */
  public static final int TYPE_HAS_QSCTL = WIFI_FC0_TYPE_DATA | 0x80;

  /*
   * overriden classes
   */

  // TODO ORDER field always 0
  // TODO Seq numbers per TID 7.1.3.4.1
  // TODO RTS AND CTS overide for duration 7.2.1.1
  // TODO override getBytes
  // TODO need action frames: Block Ack Action to setup blockack
  // TODO QOS_NULL & sup with normal NULL
  /**
   * Basic QOS_DATA frame.
   */
  public static class QOS_Data extends Mac802_11Message.Data implements
      Mac802_11eMessage {

    /**
     * QOSControl field. 16-bit
     */
    protected short qosControl;

    public QOS_Data(MacAddress dst, MacAddress src, int duration, short seq,
        short frag, boolean moreFrag, boolean retry, Message body, short qosCtl) {
      super(dst, src, duration, seq, frag, moreFrag, retry, body);
      this.qosControl = qosCtl;
    }

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

    /**
     * Return packet subtype.
     * 
     * @return packet subtype
     */
    public int getSubType() {
      return WIFI_FC0_SUBTYPE_QOS_DATA;
    }

    public void get802_11eHeaderBytes(byte[] msg, int offset) {
      get802_11HeaderBytes(msg, offset);

      // 24-25: qosCtl
      Pickle.shortToArray(qosControl, msg, offset + 24);
    }

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

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

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

    public static int getHeaderSize() {
      return 28 + 2 + 8; // 28 mac + 2 qosctl + 8 llc
    }
    
    // TODO sucks
//    public int getSize() {
//      return getHeaderSize();
//    }
    public int getSize() {
      int size = body.getSize();
      if (size == Constants.ZERO_WIRE_SIZE) {
        return Constants.ZERO_WIRE_SIZE;
      }
      return size + getHeaderSize();
    }

    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (!super.equals(obj))
        return false;
      final QOS_Data other = (QOS_Data) obj;
      if (getQoSControl() != other.getQoSControl())
        return false;
      return true;
    }

    /**
     * Return QoS Control.
     * 
     * @return qosControl
     */
    public short getQoSControl() {
      return qosControl;
    }
  }

  /**
   * QOS_Null frame. always has 0 length body.
   */
  public static class QOS_Null extends QOS_Data implements Mac802_11eMessage {

    public QOS_Null(MacAddress dst, MacAddress src, int duration, short seq,
        short frag, boolean moreFrag, boolean retry, short qosCtl) {
      super(dst, src, duration, seq, frag, moreFrag, retry, Message.NULL,
          qosCtl);
    }

    /**
     * Return packet subtype.
     * 
     * @return packet subtype
     */
    public int getSubType() {
      return WIFI_FC0_SUBTYPE_QOS_NULL;
    }

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

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

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

  /**
   * BlockAck 7.2.1.7/8
   * 
   * @author beilke
   * 
   */
  public static class QOS_BlockAck extends MacDcfMessage.Ack implements
      Mac802_11eMessage, HasSrc {

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

    /**
     * BAR Control
     */
    protected short barControl;

    /**
     * BlockAck Starting Sequence Control
     */
    protected short barSeqControl;

    /**
     * Create 802_11e BLOCK_ACK packet.
     * 
     * @param dst
     *          packet destination address
     * @param src
     *          packet source address
     * @param duration
     *          packet transmission duration
     * @param barControl
     *          packet Block Ack Control
     * @param barSeqControl
     *          packet Block Ack Sequence Starting Control
     */
    protected QOS_BlockAck(MacAddress dst, MacAddress src, int duration,
        short barControl, short barSeqControl) {
      super(dst, duration);
      this.src = src;
      this.barControl = barControl;
      this.barSeqControl = barSeqControl;
    }

    // //////////////////////////////////////////////
    // 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;
      return super.getAdapter(adapter);
    }

    /**
     * Return packet source address.
     * 
     * @return packet source address
     */
    public MacAddress getSrc() {
      return src;
    }

    /**
     * Return BockAck Control.
     * 
     * @return BockAck Control
     */
    public short getBARControl() {
      return barControl;
    }

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

    /**
     * Return BockAck Starting Sequence Control.
     * 
     * @return BockAck Starting Sequence Control
     */
    public short getBARSeqControl() {
      return barSeqControl;
    }

    public static int getHeaderSize() {
      return 20;
    }

    // TODO check
    public int hashCode() {
      final int PRIME = 31;
      int result = super.hashCode();
      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 QOS_BlockAck other = (QOS_BlockAck) obj;
    // if (dst == null) {
    // if (other.dst != null)
    // return false;
    // } else if (!dst.equals(other.dst))
    // return false;
    // if (duration != other.duration)
    // return false;
    // if (src == null) {
    // if (other.src != null)
    // return false;
    // } else if (!src.equals(other.src))
    // return false;
    // if (barControl != other.barControl)
    // return false;
    // if (barSeqControl != other.barSeqControl)
    // return false;
    // return true;
    // }
  }

  /**
   * BlockAckRequest
   * 
   * @author jdkbx
   * 
   */
  public static class QOS_BlockAckReq extends QOS_BlockAck implements
      Mac802_11eMessage {

    /**
     * Create 802_11e BLOCK_ACK_REQ packet.
     * 
     * @param dst
     *          packet destination address
     * @param src
     *          packet source address
     * @param duration
     *          packet transmission duration
     * @param barControl
     *          packet Block Ack Control
     * @param barSeqControl
     *          packet Block Ack Sequence Starting Control
     */
    public QOS_BlockAckReq(MacAddress dst, MacAddress src, int duration,
        short barControl, short barSeqControl) {
      super(dst, src, duration, barControl, barSeqControl);
    }

    /**
     * Return packet subtype.
     * 
     * @return packet subtype
     */
    public int getSubType() {
      return WIFI_FC0_SUBTYPE_BLOCKACK_REQ;
    }

    public int getSize() {
      return getHeaderSize() + 2 + 2;
    }

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

  /**
   * BlockAckResponse
   * 
   * @author jdkbx
   * 
   */
  public static class QOS_BlockAckResp extends QOS_BlockAck implements
      Mac802_11eMessage {

    /**
     * Block Ack Bitmap field.
     */
    protected short[] blockAckBitmap;

    /**
     * Create 802_11e BLOCK_ACK_RESP packet.
     * 
     * @param dst
     *          packet destination address
     * @param src
     *          packet source address
     * @param duration
     *          packet transmission duration
     * @param barControl
     *          packet Block Ack Control
     * @param barSeqControl
     *          packet Block Ack Sequence Starting Control
     * @param blockAckBitmap
     *          packet Block Ack Bitmap field
     */
    public QOS_BlockAckResp(MacAddress dst, MacAddress src, int duration,
        short barControl, short barSeqControl, short[] blockAckBitmap) {
      super(dst, src, duration, barControl, barSeqControl);
      if (blockAckBitmap.length != 64) {
        // throw new Exception();
      } else {
        this.blockAckBitmap = blockAckBitmap;
      }
    }

    /**
     * Return packet subtype.
     * 
     * @return packet subtype
     */
    public int getSubType() {
      return WIFI_FC0_SUBTYPE_BLOCKACK_RESP;
    }

    public int getSize() {
      return getHeaderSize() + 2 + 2 + 128;
    }

    /**
     * Return Block Ack Bitmap field.
     * 
     * @return Block Ack Bitmap field
     */
    public short[] getBlockAckBitmap() {
      return blockAckBitmap;
    }

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