package click.swans.mac;

import jist.swans.mac.MacAddress;
import jist.swans.mac.MacDcfMessage;
import jist.swans.mac.MacMessage;
import jist.swans.mac.MacMessageFactory;
import jist.swans.misc.Message;
import jist.swans.misc.MessageAnno;
import jist.swans.misc.MessageBytes;
import jist.swans.misc.Pickle;
import jist.swans.net.NetMessage;
import jist.swans.Constants;
import click.runtime.ClickException;

/**
 * Message Factory for 802.11 click packets.
 *
 * @author kurth
 */
public class MacClickMessageFactory extends MacMessageFactory.M802_11 {

    public MacDcfMessage.Data createDataBroadcast(MacAddress dst, MacAddress src,
        int duration, Message body, MessageAnno bodyAnno) {
      MacClickMessage.AbstractMessageWithBody data = (MacClickMessage.AbstractMessageWithBody) body;
      data.setDuration(0);
      data.setSeq((short)0);
      data.setRetry(false);
      return data;
    }

    public MacDcfMessage.Data createDataUnicast(MacAddress dst, MacAddress src,
        int duration, short seqNo, boolean retry, Message body, MessageAnno bodyAnno) {
      MacClickMessage.AbstractMessageWithBody data = (MacClickMessage.AbstractMessageWithBody) body;
      data.setDuration(duration);
      data.setSeq(seqNo);
      data.setRetry(retry);
      return data;
    }

    public Message createExraHeader(int flags, byte rssi, byte silence, MacClickMessage body) {
      return new MacClickMessage.ExtraHeader(flags, rssi, silence, body);
    }

    public MacMessage fromBytes(byte[] msg, int offset) throws Message.IllegalFormatException {
      return fromBytes(msg, offset, false);
    }

    public MacMessage fromBytes(byte[] msg, int offset, boolean raw) 
        throws Message.IllegalFormatException {
      MacClickMessage ret = null;

      // 0: fc[0]
      int fc0 = Pickle.arrayToUByte(msg, offset + 0);
      int type = fc0 & MacClickMessage.WIFI_FC0_TYPE_MASK;
      int subtype = fc0 & MacClickMessage.WIFI_FC0_SUBTYPE_MASK;

      if (type == MacClickMessage.WIFI_FC0_TYPE_CTL 
          && subtype == MacClickMessage.WIFI_FC0_SUBTYPE_RTS)
        throw new RuntimeException("Not implemented");
      else if (type == MacClickMessage.WIFI_FC0_TYPE_CTL 
          && subtype == MacClickMessage.WIFI_FC0_SUBTYPE_CTS)
        throw new RuntimeException("Not implemented");
      else if (type == MacClickMessage.WIFI_FC0_TYPE_CTL 
          && subtype == MacClickMessage.WIFI_FC0_SUBTYPE_ACK)
        throw new RuntimeException("Not implemented");
      else if (type == MacClickMessage.WIFI_FC0_TYPE_DATA 
          && subtype == MacClickMessage.WIFI_FC0_SUBTYPE_DATA)
        ret = dataFromBytes(msg, offset, raw);
      else if (type == MacClickMessage.WIFI_FC0_TYPE_MGT)
        ret = managementFromBytes(msg, offset, raw);

      return (ret);
    }

    public int getSize(int type) {
      switch (type) {
      case MacDcfMessage.TYPE_DATA:
        return 0; // return null since the header is already accounted for
//      case MacDcfMessage.TYPE_ACK:
//        return MacClickMessage.Ack.getHeaderSize();
//      case Mac802_11Message.TYPE_RTS:
//        return MacClickMessage.Rts.getHeaderSize();
//      case Mac802_11Message.TYPE_CTS:
//        return MacClickMessage.Cts.getHeaderSize();
      }
      return super.getSize(type);
    }

    public MacClickMessage.Management managementFromBytes(byte[] msg, int offset,
        boolean raw) {
      // 0: fc[0]
      int type = Pickle.arrayToUByte(msg, offset + 0);
      // 1: fc[1]
//      byte dir = (byte)(Pickle.arrayToUByte(msg, offset + 1) & MacClickMessage.WIFI_FC1_DIR_MASK);
      boolean moreFrag = 1 == (Pickle.arrayToUByte(msg, offset + 1) & MacClickMessage.WIFI_FC1_MORE_FRAG);
      boolean retry= 1 == (Pickle.arrayToUByte(msg, offset + 1) & MacClickMessage.WIFI_FC1_RETRY);
      // 2-3: dur
      int duration = Pickle.arrayToUShort(msg, offset + 2);
      // 4-9: addr1
      MacAddress dst = new MacAddress(msg, offset + 4);
      // 10-15: addr2
      MacAddress src = new MacAddress(msg, offset + 10);
      // 16-21: addr3
      MacAddress bssid = new MacAddress(msg, offset + 16);
      // 22-23: seq, consists of (lower) 4 bit fragment number
      // and (higher) 12 bit sequence number
      short seq = (short) (Pickle.arrayToShort(msg, offset + 22) >> 4);

      int offset_payload = offset + 24;
      Message body;
      if (true == raw)
        body = new MacClickMessage.RawMessage(msg, offset_payload, msg.length - offset_payload);
      else
        body = new MessageBytes(msg, offset + 24, msg.length - offset_payload);

      short frag = 0;
      int subtype = type & MacClickMessage.WIFI_FC0_SUBTYPE_MASK;

      //      if (Main.ASSERT) {
//        byte[] t1 = new byte[getSize()];
//        byte[] t2 = new byte[getSize()];
//        this.getBytes(t2, 0);
//        System.arraycopy(msg, offset, t1, 0, getSize());
//        Util.assertion(Arrays.equals(t1, t2));
//      }

      return new MacClickMessage.Management(subtype, dst, src, bssid, duration,
          seq, frag, moreFrag, retry, body);
    }

    public MacClickMessage.Data dataFromBytes(byte[] msg, int offset, boolean raw) 
        throws Message.IllegalFormatException {
      // 0: fc[0]
      int type = Pickle.arrayToUByte(msg, offset + 0);
      // 1: fc[1]
      byte dir = (byte)(Pickle.arrayToUByte(msg, offset + 1) & MacClickMessage.WIFI_FC1_DIR_MASK);
      boolean moreFrag = 1 == (Pickle.arrayToUByte(msg, offset + 1) & MacClickMessage.WIFI_FC1_MORE_FRAG);
      boolean retry = 1 == (Pickle.arrayToUByte(msg, offset + 1) & MacClickMessage.WIFI_FC1_RETRY);
      // 2-3: dur
      int duration = Pickle.arrayToUShort(msg, offset + 2);
      // 4-9: addr1
      MacAddress dst = new MacAddress(msg, offset + 4);
      // 10-15: addr2
      MacAddress src = new MacAddress(msg, offset + 10);
      // 16-21: addr3
      MacAddress bssid = new MacAddress(msg, offset + 16);
      // 22-23: seq, consists of (lower) 4 bit fragment number
      // and (higher) 12 bit sequence number
      short seq = (short) (Pickle.arrayToShort(msg, offset + 22) >> 4);

      // inspect 8 byte llc header...
      // 4-5: uint16_t ether_type;
      int etherType = Pickle.arrayToUShort(msg, offset + 24 + 6);

      Message body;
      int offset_payload = offset + 24 + 8;
      if (true == raw) {
        body = new MacClickMessage.RawMessage(msg, offset_payload, msg.length - offset_payload);
      }
      else {
        switch (etherType) {
        case Constants.MAC_ETHERTYPE_IP:
          body = new NetMessage.Ip(msg, offset_payload);
          break;
        case Constants.MAC_ETHERTYPE_BRN:
       case Constants.MAC_ETHERTYPE_ARP:
        default:
          body = new MessageBytes(msg, offset_payload, msg.length - offset_payload);
          break;
        }
      }

      short frag = 0;
      int subtype = type & MacClickMessage.WIFI_FC0_SUBTYPE_MASK;

//      if (Main.ASSERT) {
//        byte[] t1 = new byte[getSize()];
//        byte[] t2 = new byte[getSize()];
//        this.getBytes(t2, 0);
//        System.arraycopy(msg, offset, t1, 0, getSize());
//        Util.assertion(Arrays.equals(t1, t2));
//      }

      return new MacClickMessage.Data(subtype, dir, dst, src, bssid, duration,
          seq, frag, moreFrag, retry, etherType, body);
    }

    public MacClickMessage.ExtraHeader extraFromBytes(byte[] msg, int offset,
        boolean raw) throws ClickException, Message.IllegalFormatException {
        // flags
      long magic = Pickle.arrayToUInt(msg, offset + 0);
      if (0x01204907  != magic)
        throw new ClickException("No magic cookie found");
      // flags
      int flags = (int)Pickle.arrayToUInt(msg, offset + 4);

      // rssi;
      byte rssi = (byte)Pickle.arrayToUByte(msg, offset + 8);
      // silence;
      byte silence = (byte)Pickle.arrayToUByte(msg, offset + 9);
      // power;
      byte power = (byte)Pickle.arrayToUByte(msg, offset + 10);
      // pad;
      byte pad = (byte)Pickle.arrayToUByte(msg, offset + 11);

      // rate;         /* bitrate in Mbps*2 */
      byte rate = (byte)Pickle.arrayToUByte(msg, offset + 12);
      // rate1;        /* bitrate in Mbps*2 */
      byte rate1 = (byte)Pickle.arrayToUByte(msg, offset + 13);
      // rate2;        /* bitrate in Mbps*2 */
      byte rate2 = (byte)Pickle.arrayToUByte(msg, offset + 14);
      // rate3;        /* bitrate in Mbps*2 */
      byte rate3 = (byte)Pickle.arrayToUByte(msg, offset + 15);

      // max_tries;
      byte max_tries = (byte)Pickle.arrayToUByte(msg, offset + 16);
      // max_tries1;
      byte max_tries1 = (byte)Pickle.arrayToUByte(msg, offset + 17);
      // max_tries2;
      byte max_tries2 = (byte)Pickle.arrayToUByte(msg, offset + 18);
      // max_tries3;
      byte max_tries3 = (byte)Pickle.arrayToUByte(msg, offset + 19);

      // virt_col;
      byte virt_col = (byte)Pickle.arrayToUByte(msg, offset + 20);
      // retries;
      byte retries = (byte)Pickle.arrayToUByte(msg, offset + 21);
      // len;
      short len = (short)Pickle.arrayToUShort(msg, offset + 22);

      // MacMessage body;
      MacClickMessage body = (MacClickMessage) fromBytes(msg, offset + 24, raw);

//      if (Main.ASSERT) {
//        byte[] t1 = new byte[getSize()];
//        byte[] t2 = new byte[getSize()];
//        this.getBytes(t2, 0);
//        System.arraycopy(msg, offset, t1, 0, getSize());
//        Util.assertion(Arrays.equals(t1, t2));
//      }

      return new MacClickMessage.ExtraHeader(flags, rssi, silence, power, pad,
          rate, rate1, rate2, rate3, max_tries, max_tries1,
          max_tries2, max_tries3, virt_col, retries, len, body);
    }

    /*
     * (non-Javadoc)
     * @see jist.swans.mac.MacMessageFactory.Dcf#getPayload(jist.swans.mac.MacMessage)
     */
    public Message getPayload(MacMessage msg) {
      return ((MacDcfMessage.Data)msg);
    }

}
