package brn.swans.mac;

import jist.swans.Constants;
import jist.swans.mac.MacAddress;
import jist.swans.mac.MacMessage;
import jist.swans.mac.MacMessage.AbstractMacMessage;
import jist.swans.misc.Message;
import jist.swans.misc.Util;

import java.util.*;

/**
 * @author Zubow
 */
public abstract class MacMcExORMessage extends AbstractMacMessage implements MacMessage {

  /** used to derive message ids */
  protected static int messageIdBase = 0;

  public static final short ADDR_LEN = 6; /* Ethernet address */

  // ////////////////////////////////////////////////
  // frame control
  //

  /** RTS packet constant */
  public static final byte TYPE_RTS   = 27;

  /** CTS packet constant */
  public static final byte TYPE_CTS   = 28;

  /** ACK packet constant */
  public static final byte TYPE_ACK   = 29;

  /** DATA packet constant */
  public static final byte TYPE_DATA  = 32;

  /**
   * packet type.
   */
  private byte type;

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

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

  /**
   * RF channel. used by the radio.
   */
  private int channel;

  /**
   * Unique id per packet, for viz purposes.
   */
  protected int messageId;

  /**
   * Duration for which the medium should be reserved after reception of the
   * data packet (virtual carrier sensing).
   */
  protected int duration;

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

  private MacMcExORMessage() {
    messageId = messageIdBase++;
  }

  /**
   * Create a McExOR mac packet.
   *
   * @param type  packet type
   */
  protected MacMcExORMessage(byte type) {
    this();
    this.type = type;
  }

  /**
   * Create a McExOR mac packet.
   *
   * @param type  packet type
   * @param retry packet retry bit
   */
  protected MacMcExORMessage(byte type, boolean retry, short seq) {
    this(type);
    this.retry = retry;
    this.seq = seq;
  }

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

  public int getId() {
    return messageId;
  }

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

  /**
   * Return retry bit.
   *
   * @return retry bit
   */
  public boolean getRetry() {
    return retry;
  }

  /**
   * Return packet source address.
   *
   * @return packet source address
   */
  public abstract MacAddress getSrc();

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

  /**
   * Return packet sequence number.
   *
   * @return packet sequence number
   */
  public short getSeq() {
    return seq;
  }

  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    final MacMcExORMessage that = (MacMcExORMessage) o;

    if (retry != that.retry) return false;
    if (seq != that.seq) return false;
    if (type != that.type) return false;

    return true;
  }

  public int hashCode() {
    int result;
    result = (int) type;
    result = 29 * result + (retry ? 1 : 0);
    result = 29 * result + (int) seq;
    return result;
  }

  /**
   * The sorted list of forwarding candidates (1-n).
   *
   * @author zubow
   */
  public static class CSetAddress {

    /** sorted list of candidates */
    private List lstAddresses;

    public CSetAddress(CSetAddress address) {
      lstAddresses = new ArrayList(address.lstAddresses);
    }

    public CSetAddress(MacAddress any) {
      lstAddresses = new ArrayList();
      lstAddresses.add(any);
    }

    public CSetAddress(List other) {
      lstAddresses = new ArrayList(other);
    }

    /** Return the number of candidates. */
    public int getCandidateSetSize() {
      return lstAddresses.size();
    }

    /** Check for broadcast. */
    public boolean isBroadCast() {
      return (MacAddress.ANY.equals(lstAddresses.get(0)));
    }

    /** Checks whether the given candidate is enlisted in the cset. */
    public boolean contains(MacAddress localAddr) {
      return lstAddresses.contains(localAddr);
    }

    /** Returns the position of the candidate in the cset. */
    public byte getCandId(MacAddress localAddr) {
      for (byte i = 0; i < lstAddresses.size(); i++) {
        if (localAddr.equals(lstAddresses.get(i)))
          return i;
      }
      return Byte.MAX_VALUE;
    }

    public List getLstAddresses() {
      return lstAddresses;
    }

    public String toString() {
      return lstAddresses.toString();
    }

    public boolean isEmpty() {
      return lstAddresses.isEmpty();
    }

    public Object clone() {
      return new CSetAddress(this);
    }

    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      final CSetAddress that = (CSetAddress) o;
      if (lstAddresses != null ?
          !lstAddresses.equals(that.lstAddresses) : that.lstAddresses != null) return false;
      return true;
    }

    public int hashCode() {
      return (lstAddresses != null ? lstAddresses.hashCode() : 0);
    }
  }

  /**
   * An McExOR Acknowlege packet (slotted ack).
   * <p/>
   * Size of ACK packet:
   * - destinantion addr   6
   * - source addr         6
   * - candidate addr      (X-1)*6
   * - candidate id        1
   * - forward/own pref    1
   * - duration            4
   *
   * @author Zubow
   */
  public static class Ack extends MacMcExORMessage implements
    HasSrc, HasDst, HasSeq /*, HasCandidates */ {

    /**
     * ACK packet size.
     *
     * note the ExOR ack packet is variable sized
     */
    private static final int SIZE = 22;

    /**
     * Data frame senders address (-> the receiver for this ack).
     */
    private MacAddress dst;

    /**
     * Data frame receiver's address (-> the sender for this ack)
     */
    private MacAddress src;

    /**
     * Array of candidates (mac addresses, highest not included).
     */
    private CSetAddress candidates;

    /**
     * The position within the candidate list of the node with the highest
     * priority acknowledgment
     */
    private byte candidateId;

    /**
     * The preference with which the candidate is willing to forward the message.
     * Only the lower 4 Bits are used.
     */
    private byte forwardPref;

    /**
     * Preference of forwarding of the node identified by src.
     * Only the lower 4 Bits are used.
     */
    private byte ownPref;

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

    /**
     * Create McExOR ACK packet.
     *
     * @param src      packet source address
     * @param dst      packet destination address
     */
    public Ack(MacAddress src, MacAddress dst, int duration, byte candidateId,
               byte forwardPref, byte ownPref, short seq, CSetAddress candidates) {
      super(TYPE_ACK, false, seq);
      this.src = src;
      this.dst = dst;
      this.candidateId = candidateId;
      this.forwardPref = forwardPref;
      this.ownPref = ownPref;
      this.duration = duration;
      this.candidates = candidates;
    }

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

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

    public MacAddress getDst() {
      return (dst);
    }

    public MacAddress getSrc() {
      return (src);
    }

    public byte getCandidateId() {
      return (candidateId);
    }

    public byte getForwardPref() {
      return (forwardPref);
    }

    public byte getOwnPref() {
      return ownPref;
    }

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

    // Message interface
    /** {@inheritDoc} */
    public int getSize() {
      return SIZE + ADDR_LEN * candidates.getCandidateSetSize();
    }

    public static int getSize(int candidates) {
      return SIZE + ADDR_LEN * candidates;
    }

    // Message interface
    /** {@inheritDoc} */
    public void getBytes(byte[] msg, int offset) {
      throw new RuntimeException("todo: not implemented");
    }

    public CSetAddress getCandidates() {
      return (candidates);
    }

    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;
      if (!super.equals(o)) return false;

      final Ack ack = (Ack) o;

      if (candidateId != ack.candidateId) return false;
      if (duration != ack.duration) return false;
      if (forwardPref != ack.forwardPref) return false;
      if (ownPref != ack.ownPref) return false;
      if (candidates != null ? !candidates.equals(ack.candidates) : ack.candidates != null) return false;
      if (dst != null ? !dst.equals(ack.dst) : ack.dst != null) return false;
      if (src != null ? !src.equals(ack.src) : ack.src != null) return false;

      return true;
    }

    public int hashCode() {
      int result = super.hashCode();
      result = 29 * result + (dst != null ? dst.hashCode() : 0);
      result = 29 * result + (src != null ? src.hashCode() : 0);
      result = 29 * result + (candidates != null ? candidates.hashCode() : 0);
      result = 29 * result + (int) candidateId;
      result = 29 * result + (int) forwardPref;
      result = 29 * result + (int) ownPref;
      result = 29 * result + duration;
      return result;
    }
  } // class: ACK}


  /**
   * An McExOR Data packet.
   * <p/>
   * DATA frame: (size = 18 + 6 * X + body)
   * - frame control size: 2
   * - duration size: 2
   * - sequence control size: 2
   * - CRC size: 4
   * - source address size: 6
   * - multiple destinations size: 1 + 6 * X
   * - forwared pref size: 1
   * - frame body size: 0 - ...
   *
   * @author Zubow
   */
  public static class Data extends MacMcExORMessage implements
    HasSrc, HasCandidates, HasSeq {

    /**
     * Packet header size.
     */
    public static final short BASE_HEADER_SIZE = 18; // + for relay pref

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

    /**
     * Packet destination address.
     */
    private CSetAddress dst;

    /**
     * Packet source address.
     */
    private MacAddress src;

    /**
     * Packet data payload.
     */
    private Message body;

    /**
     * The preference with which the candidate is willing to forward the message.
     * Only the lower 4 Byte are used.
     */
    private byte forwardPref;

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

    /**
     * Create a multicast MCExOR data packet.
     *
     * @param dst      packet destination addresses (canidate set)
     * @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(CSetAddress dst, MacAddress src, int duration, short seq,
                boolean retry, byte forwardPref, Message body) {
      super(TYPE_DATA, retry, seq);
      this.dst = dst;
      this.src = src;
      this.duration = duration;
      this.forwardPref = forwardPref;
      this.body = body;
    }

    /**
     * Create broadcast MCExOR data packet.
     *
     * @param src      packet source address
     * @param body     packet data payload
     */
    public Data(MacAddress src, byte forwardPref, Message body) {
      super(TYPE_DATA, false, (byte)-1);

      this.dst = new CSetAddress(MacAddress.ANY);
      this.src = src;
      this.duration = 0;
      this.forwardPref = forwardPref;
      this.body = body;
    }

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

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

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

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

    public byte getForwardPref() {
      return (forwardPref);
    }

    /**
     * Return packet data payload.
     *
     * @return packet data payload
     */
    public Message getBody() {
      return body;
    }

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

    // Message interface
    /** {@inheritDoc} */
    public int getSize() {
      int size = body.getSize();
      if (size == Constants.ZERO_WIRE_SIZE) {
        return Constants.ZERO_WIRE_SIZE;
      }
      return BASE_HEADER_SIZE + (ADDR_LEN * getDsts().getCandidateSetSize()) + size;
    }

    public static int getSize(int size, int candidates) {
      if (size == Constants.ZERO_WIRE_SIZE) {
        return Constants.ZERO_WIRE_SIZE;
      }
      return BASE_HEADER_SIZE + (ADDR_LEN * candidates) + size;
    }

    // Message interface
    /** {@inheritDoc} */
    public void getBytes(byte[] msg, int offset) {
      throw new RuntimeException("todo: not implemented");
    }

    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;
      if (!super.equals(o)) return false;

      final Data data = (Data) o;

      if (duration != data.duration) return false;
      if (forwardPref != data.forwardPref) return false;
      if (body != null ? !body.equals(data.body) : data.body != null) return false;
      if (dst != null ? !dst.equals(data.dst) : data.dst != null) return false;
      if (src != null ? !src.equals(data.src) : data.src != null) return false;

      return true;
    }

    public int hashCode() {
      int result = super.hashCode();
      result = 29 * result + (dst != null ? dst.hashCode() : 0);
      result = 29 * result + (src != null ? src.hashCode() : 0);
      result = 29 * result + duration;
      result = 29 * result + (body != null ? body.hashCode() : 0);
      result = 29 * result + (int) forwardPref;
      return result;
    }

    public List getCandidates() {
      return dst.getLstAddresses();
    }
  } // class: Data

  /**
   * Support of RTS/CTS in McExOR.
   *
   * RTS frame: (size = 20)
   * frame control size: 2
   * duration size: 2
   * address: destination size: 6
   * address: source size: 6
   * CRC size: 4
   * @author Zubow
   */
  public static class Rts extends MacMcExORMessage implements HasSrc, HasCandidates {

    /**
     * RTS packet size.
     *
     * note the ExOR rts packet is variable sized
     */
    private static final int SIZE = 20;

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

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

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

    /**
     * Create an 802_11 RTS packet.
     *
     * @param dst
     *          packet destination address
     * @param src
     *          packet source address
     * @param duration
     *          packet transmission duration
     */
    public Rts(CSetAddress dst, MacAddress src, int duration) {
      super(TYPE_RTS);
      this.dst = dst;
      this.src = src;
      this.duration = duration;
    }

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

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

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

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

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

    public static int getSize(int candidates) {
      return SIZE + ADDR_LEN * candidates;
    }


    // Message interface
    /** {@inheritDoc} */
    public int getSize() {
      return SIZE + ADDR_LEN * getDsts().getCandidateSetSize();
    }

    // Message interface
    /** {@inheritDoc} */
    public void getBytes(byte[] msg, int offset) {
      throw new RuntimeException("todo: not implemented");
    }

    public List /* MacAddress */ getCandidates() {
      return dst.getLstAddresses();
    }
  } // class: RTS


  /**
   * Support of RTS/CTS in McExOR.
   *
   * CTS frame: (size = 14)
   * frame control size: 2
   * duration size: 2
   * address: destination size: 6
   * CRC size: 4
   *
   * @author Zubow
   */
  public static class Cts extends MacMcExORMessage implements HasSrc, HasDst {

    /** CTS packet size: note the ExOR cts packet is variable sized */
    private static final int SIZE = 14;

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

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

    /** Array of candidates (mac addresses, highest not included). */
    private CSetAddress candidates;
    /** the RTS packet was received with that power */
    private double rtsSnr;

    /** Bitmask contains information about the candidates from which we know that
     * they successfully received the rts packet. */
    private byte cand_recv_flags              = 0;

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

    /**
     * Create an 802_11 CTS packet.
     *
     * @param dst
     *          packet destination address
     * @param duration
     *          packet transmission duration
     */
    public Cts(MacAddress src, MacAddress dst, int duration, CSetAddress candidates) {
      super(TYPE_CTS);
      this.src = src;
      this.dst = dst;
      this.duration = duration;
      this.candidates = candidates;
      /** append myself to bitmask */
      setCandRecv(candidates.getCandId(src), true);
    }

    public Cts(MacAddress src, MacAddress dst, int duration, CSetAddress candidates, double rtsSnr) {
      this(src, dst, duration, candidates);
      this.rtsSnr = rtsSnr;
    }

    /**
     * Set candidate received flag.
     *
     * @param c_id we know that candidate with this id received the rts packet.
     * @param value whether to set or clear the flag
     */
    protected void setCandRecv(byte c_id, boolean value) {
      cand_recv_flags = Util.setFlag(cand_recv_flags, (byte)Math.pow(2, c_id), value);
    }

    protected void setCandRecv(Set cIds, boolean value) {
      Iterator it = cIds.iterator();
      while (it.hasNext()) {
        byte c_id = candidates.getCandId((MacAddress)it.next());
        cand_recv_flags = Util.setFlag(cand_recv_flags, (byte)Math.pow(2, c_id), value);
      }
    }

    protected boolean getCandRecv(byte c_id) {
      return Util.getFlag(cand_recv_flags, (byte)Math.pow(2, c_id));
    }

    protected Set getAllCandRecv() {
      Set recv_cands = new TreeSet();
      byte i = 0;
      while ((byte)Math.pow(2, i) <= cand_recv_flags) {
        if (getCandRecv(i))
          recv_cands.add(candidates.getLstAddresses().get(i));
        i++;
      }
      return recv_cands;
    }

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

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

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

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

    public CSetAddress getCandidates() {
      return candidates;
    }

    public double getRtsSnr() {
      return rtsSnr;
    }

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

    // Message interface
    /** {@inheritDoc} */
    public int getSize() {
      return SIZE + ADDR_LEN * candidates.getCandidateSetSize();
    }

    public static int getSize(int candidates) {
      return SIZE + ADDR_LEN * candidates;
    }

    // Message interface
    /** {@inheritDoc} */
    public void getBytes(byte[] msg, int offset) {
      throw new RuntimeException("todo: not implemented");
    }

    public String toString() {
      return src.getId() + "->" + dst.getId() + ", SNR= " + rtsSnr;
    }
  } // class: CTS

  public int getChannel() {
    return channel;
  }

  public void setChannel(int channel) {
    this.channel = channel;
  }
}