package brn.swans.route.metric;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.log4j.Logger;

import brn.swans.route.metric.LinkStat.LinkInfo;

import jist.runtime.JistAPI;
import jist.runtime.Main;
import jist.runtime.Util;
import jist.swans.Constants;
import jist.swans.misc.Message;
import jist.swans.misc.Pickle;
import jist.swans.net.NetAddress;
import jist.swans.radio.RadioInterface;
import jist.swans.radio.RadioInterface.RFChannel;

/**
 * Represents a link probe message.
 */
public class LinkProbe implements Message {

  public static final Logger log = Logger.getLogger(LinkProbe.class);

  public static class LinkEntry implements Message {
    protected final int BASE_SIZE = 6;

    protected NetAddress ip;

    protected List<LinkInfo> linkinfo;

    public LinkEntry(NetAddress ip, Collection linkinfo) {
      this.ip = ip;
      this.linkinfo = new ArrayList<LinkInfo>();
      this.linkinfo.addAll(linkinfo);
    }

    public LinkEntry(byte[] data, int offset) {
      ip = new NetAddress(data, offset + 0);
      int no = (int) Pickle.arrayToUShort(data, offset + 4);
      this.linkinfo = new ArrayList<LinkInfo>();
      for (int i = 0; i < no; i++) {
        linkinfo.add(new LinkInfo(data, offset + 6 + i*LinkInfo.BASE_SIZE));
      }
    }

    /*
     * (non-Javadoc)
     * @see jist.swans.misc.Message#getBytes(byte[], int)
     */
    public void getBytes(byte[] msg, int offset) {
      Pickle.NetAddressToArray(ip, msg, offset + 0);
      Pickle.ushortToArray(linkinfo.size(), msg, offset + 4);
      for (int i = 0; i < linkinfo.size(); i++) {
        linkinfo.get(i).getBytes(msg, offset + 6 + LinkInfo.BASE_SIZE*i);
      }
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    public String toString() {
      StringBuffer str = new StringBuffer();
      str.append("nb ").append(ip);

      for (int i = 0; i < linkinfo.size(); i++) {
        LinkInfo linkInfo = (LinkInfo) linkinfo.get(i);
        str.append("linkInfo ").append(linkInfo);
      }
      return str.toString();
    }

    /*
     * (non-Javadoc)
     * @see jist.swans.misc.Message#getSize()
     */
    public int getSize() {
      return BASE_SIZE + linkinfo.size() * LinkInfo.BASE_SIZE;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
      final int PRIME = 31;
      int result = 1;
      result = PRIME * result + ((ip == null) ? 0 : ip.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 LinkEntry other = (LinkEntry) obj;
      if (ip == null) {
        if (other.ip != null)
          return false;
      } else if (!ip.equals(other.ip))
        return false;
      return true;
    }
  }

  protected final int BASE_SIZE = 46;

  protected Integer rate;

  protected int size;

  /**
   * Net address of node issuing the link probe message.
   */
  protected NetAddress ip;

  /**
   * Sequence number of node issuing link probe message.
   */
  protected int seqNum;

  /**
   * period of this node's probe broadcasts, in msecs
   */
  protected int period;

  /**
   * this node's loss-rate averaging period, in msecs
   */
  protected int tau;

  /**
   * how many probes this node has sent
   */
  protected int sent;

  /**
   * number of probes monitored by this node
   */
  protected int numProbes;

  /**
   * channel used by this probe packet.
   */
  protected RadioInterface.RFChannel nextChannel;

  /**
   * number of available channels.
   */
  protected int numChannels;

  /**
   * my current home channel.
   */
  protected RadioInterface.RFChannel homeChannel;

//  protected int[] availableRates;

  protected List<LinkEntry> linkEntries;

  public LinkProbe(NetAddress me, int seqNum, long period, long tau, int sent,
      Integer rate, int size, int numProbes, RFChannel nextChannel, int numChannels,
      RFChannel homeChannel) {
    this.linkEntries = new ArrayList<LinkEntry>();
    this.ip = me;
    this.seqNum = seqNum;
    if (Main.ASSERT)
      Util.assertion(seqNum >= 0);
    this.period = (int) (period / Constants.MILLI_SECOND);
    this.tau = (int) (tau / Constants.MILLI_SECOND);
    this.sent = sent;
    this.rate = rate;
    this.size = size;
    this.numProbes = numProbes; // number of probes monitored by this

    // channel stuff
    this.nextChannel = nextChannel;
    this.numChannels = numChannels;

    this.homeChannel = homeChannel;//channelInterface.getChannel();
  }

  public LinkProbe(byte[] data, int offset) {
    ip = new NetAddress(data, offset + 0);
    seqNum = (int) Pickle.arrayToUInt(data, offset + 4);
    period = (int) Pickle.arrayToUInt(data, offset + 8);
    tau = (int) Pickle.arrayToUInt(data, offset + 12);
    sent = (int)Pickle.arrayToUInt(data, offset + 16);
    rate = (int)Pickle.arrayToUInt(data, offset + 20);
    size = (int)Pickle.arrayToUInt(data, offset + 24);
    numProbes = (int)Pickle.arrayToUInt(data, offset + 28);
    int next = (int)Pickle.arrayToUInt(data, offset + 32);
    numChannels = (int)Pickle.arrayToUInt(data, offset + 36);
    int home = (int)Pickle.arrayToUInt(data, offset + 40);
    int no = Pickle.arrayToUShort(data, offset + 44);

    nextChannel = RFChannel.DEFAULT_RF_CHANNEL;
    if (nextChannel.getChannel() != next)
      nextChannel = new RFChannel(nextChannel.getFrequency(), next);

    homeChannel = RFChannel.DEFAULT_RF_CHANNEL;
    if (homeChannel.getChannel() != home)
      homeChannel = new RFChannel(homeChannel.getFrequency(), home);

    offset += 46;
    this.linkEntries = new ArrayList<LinkEntry>();
    for (int i = 0; i < no; i++) {
      LinkEntry entry = new LinkEntry(data, offset);
      linkEntries.add(entry);
      offset += entry.getSize();
    }
  }

  /*
   * (non-Javadoc)
   * @see jist.swans.misc.Message#getBytes(byte[], int)
   */
  public void getBytes(byte[] msg, int offset) {
    Pickle.NetAddressToArray(ip, msg, offset + 0);
    // TODO long??
    Pickle.uintToArray(seqNum, msg, offset + 4);
    Pickle.uintToArray(period, msg, offset + 8);
    Pickle.uintToArray(tau, msg, offset + 12);
    Pickle.uintToArray(sent, msg, offset + 16);
    Pickle.uintToArray(rate, msg, offset + 20);
    Pickle.uintToArray(size, msg, offset + 24);
    Pickle.uintToArray(numProbes, msg, offset + 28);
    Pickle.uintToArray(nextChannel.getChannel(), msg, offset + 32);
    Pickle.uintToArray(numChannels, msg, offset + 36);
    Pickle.uintToArray(homeChannel.getChannel(), msg, offset + 40);
    Pickle.ushortToArray(linkEntries.size(), msg, offset + 44);
    offset += 46;
    for (int i = 0; i < linkEntries.size(); i++) {
      linkEntries.get(i).getBytes(msg, offset);
      offset += linkEntries.get(i).getSize();
    }
  }

  /**
   * Returns message ip field.
   *
   * @return message ip field
   */
  public NetAddress getIp() {
    return ip;
  }

  /**
   * @return the nextChannel
   */
  public RadioInterface.RFChannel getNextChannel() {
    return nextChannel;
  }

  /**
   * @return the rate
   */
  public Integer getRate() {
    return rate;
  }

  /*
   * (non-Javadoc)
   * @see jist.swans.misc.Message#getSize()
   */
  public int getSize() {
    int size = BASE_SIZE;
    for (int i = 0; i < linkEntries.size(); i++) {
      size += linkEntries.get(i).getSize();
    }
    if (size > this.size) {
      log.error(this + "(" + JistAPI.getTime() + "): link probe to large (max "
          + this.size + ", actual " + size);
    }
    return Math.max(size, this.size);
  }

  /*
   * (non-Javadoc)
   * @see java.lang.Object#toString()
   */
  public String toString() {
    StringBuffer str = new StringBuffer();
    str.append(", rate ").append(rate);
    str.append(", size ").append(size);

    str.append(", sender ").append(ip);

    str.append(", period ").append(period);
    str.append(", tau ").append(tau);

    str.append(", numProbes ").append(numProbes);
    str.append(", usedChannel ").append(nextChannel);
    str.append(", sender's myHomeChannel ").append(homeChannel);

//    for (int i = 0; null != availableRates && i < availableRates.length; i++)
//      str.append(",, avrateItem ").append(availableRates[i]);

    str.append(", numLinks ").append(linkEntries.size());
    for (int i = 0; i < linkEntries.size(); i++)
      str.append(",, numLinksItem ").append(linkEntries.get(i));

    return str.toString();
  }

  /* (non-Javadoc)
   * @see java.lang.Object#hashCode()
   */
  public int hashCode() {
    final int PRIME = 31;
    int result = 1;
    result = PRIME * result + ((ip == null) ? 0 : ip.hashCode());
    result = PRIME * result + ((homeChannel == null) ? 0 : homeChannel.hashCode());
    result = PRIME * result + ((nextChannel == null) ? 0 : nextChannel.hashCode());
    result = PRIME * result + numChannels;
    result = PRIME * result + numProbes;
    result = PRIME * result + (int) (period ^ (period >>> 32));
    result = PRIME * result + rate.hashCode();
    result = PRIME * result + sent;
    result = PRIME * result + (int)seqNum;
    result = PRIME * result + size;
    result = PRIME * result + (int) (tau ^ (tau >>> 32));
    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 LinkProbe other = (LinkProbe) obj;
    if (ip == null) {
      if (other.ip != null)
        return false;
    } else if (!ip.equals(other.ip))
      return false;
    if (homeChannel == null) {
      if (other.homeChannel != null)
        return false;
    } else if (!homeChannel.equals(other.homeChannel))
      return false;
    if (nextChannel == null) {
      if (other.nextChannel != null)
        return false;
    } else if (!nextChannel.equals(other.nextChannel))
      return false;
    if (numChannels != other.numChannels)
      return false;
    if (numProbes != other.numProbes)
      return false;
    if (period != other.period)
      return false;
    if (rate != other.rate)
      return false;
    if (sent != other.sent)
      return false;
    if (seqNum != other.seqNum)
      return false;
    if (size != other.size)
      return false;
    if (tau != other.tau)
      return false;
    return true;
  }

  /**
   * @return the tau
   */
  public int getTau() {
    return tau;
  }

  public int getPeriod() {
    return period;
  }

}
