package brn.swans.route;

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

import jist.runtime.JistAPI;
import jist.runtime.Main;
import jist.runtime.Util;
import jist.swans.Constants;
import jist.swans.Node;
import jist.swans.misc.MessageAnno;
import jist.swans.net.NetAddress;
import jist.swans.net.NetMessage;

import org.apache.log4j.Logger;

import brn.swans.route.metric.ArpTableInterface;
import brn.swans.route.metric.ETTMetric;
import brn.swans.route.metric.RouteMetricInterface;
import brn.swans.route.metric.RouteMetricInterface.NoLinkExistsException;
import brn.swans.route.metric.RouteMetricInterface.NoRouteExistsException;

/**
 * Abstract base class for all discovery modules
 *
 * @author kurth
 */
public abstract class RouteDsrDiscovery implements RouteDsrBrnInterface.Discovery {

  // ////////////////////////////////////////////////
  // constants
  //

  public final static int MIN_LINK_METRIC_FOR_DSR = 500;


  // ////////////////////////////////////////////////
  // locals
  //

  /**
   * McExOR logger.
   */
  private static Logger log = Logger.getLogger(RouteDsrDiscovery.class.getName());

  /**
   * The IP address of this node.
   */
  protected NetAddress localAddr;

  /**
   * Link to the dsr object.
   */
  protected RouteDsrBrnInterface dsr;

  /**
   * Metric to use for routing.
   */
  protected RouteMetricInterface metric;

  /**
   * Interface to the arp table.
   */
  protected ArpTableInterface arp;

  /** min link metric for routes */
  protected int minLinkMetric = MIN_LINK_METRIC_FOR_DSR;
  
  protected int minLinkEtx = MIN_LINK_METRIC_FOR_DSR;

  /** whether to query the link table for bit rates (needs ETT!) */
  protected boolean bitrateFromLinkTable = false;

  
  // ////////////////////////////////////////////////
  // Initialization
  //

  /**
   * Constructs a route discovery object
   *
   * @param dsr
   * @param metric
   * @param localAddr
   */
  public RouteDsrDiscovery(RouteDsrBrn dsr, RouteMetricInterface metric,
      ArpTableInterface arp, NetAddress localAddr) {
    this.dsr = dsr;
    this.metric = metric;
    this.arp = arp;
    this.localAddr = localAddr;
  }

  public void setNode(Node node, RouteDsrBrn dsr) {
    // noop
  }

  public int getMinLinkMetric() {
    return minLinkMetric;
  }

  public void setMinLinkMetric(int minLinkMetric) {
    this.minLinkMetric = minLinkMetric;
  }

  public boolean isBitrateFromLinkTable() {
    return bitrateFromLinkTable;
  }

  public void setBitrateFromLinkTable(boolean bitrateFromLinkTable) {
    this.bitrateFromLinkTable = bitrateFromLinkTable;
  }

  // ////////////////////////////////////////////////
  // Methods
  //

  /**
   * Retrieves a route from the link table (shortest path algorithm).
   *
   * @param dest
   */
  protected RouteDsrBrnMsg.RouteEntry[] getRouteFromLinkTable(NetAddress dest) {

    if (log.isDebugEnabled()) {
      log.debug(JistAPI.getTime() + ":" + this + " <" + this.metric + ">");
    }

    try {
      List routes = this.metric.queryRoute(localAddr, dest, minLinkMetric, minLinkEtx);
      int routeLen = this.metric.getRouteMetric(routes, minLinkMetric, minLinkEtx);

      // negative entries are not cached!
//      if (routes != null && routes.size() > 1 && -1 == routeLen) {
//        routes = this.metric.queryRoute(localAddr, dest, minLinkMetric, minLinkEtx, false);
//        routeLen = this.metric.getRouteMetric(routes, minLinkMetric, minLinkEtx);
//      }

      if (log.isInfoEnabled())
        log.info(JistAPI.getTime() + ":" + this + " routeLen = " + routeLen);

      if (routes == null || routes.size() < 2 || routeLen == -1) {
        // No cached route to this destination
        return null;
      }

      // strip off first entry (myself)
      // strip off last entry (final destination)
      RouteDsrBrnMsg.RouteEntry[] entries = new RouteDsrBrnMsg.RouteEntry[routes.size()-2];
      for (int i = 1; i < routes.size()-1; i++) {
        // metric to be filled in along the way
        entries[i-1] = new RouteDsrBrnMsg.RouteEntry((NetAddress) routes.get(i), (byte) 0);
        if (log.isDebugEnabled())
          log.debug(JistAPI.getTime() + ":" + this + " " +entries[i-1].addr);
      }

      //return the route
      return entries;
    } catch (NoRouteExistsException e) {
    } catch (NoLinkExistsException e) {
    }
    
    return null;
  }

  /**
   * Inserts a new route into the link table.
   *
   * @param route the sequence of nodes (with metric between them) from here to <code>dest</code>.
   */
  protected void addRouteToLinkTable(RouteDsrBrnMsg.RouteEntry[] route) { //TODO: metric

    for (int i = 0; i < route.length - 1; i++) {

      NetAddress ip1 = route[i].addr;
      NetAddress ip2 = route[i + 1].addr;

      if (ip1.equals(ip2)) // should not be possible
        continue;

      int metric = route[i + 1].metric; //metric starts with offset 1

      this.metric.updateBothLinks(ip1, ip2, metric, false);

      if (log.isDebugEnabled())
        log.debug(JistAPI.getTime() + ":" + this + " linkTable.updateLink "
            + route[i].addr + " " + route[i + 1].addr + " " + metric);
      if (log.isDebugEnabled())
        log.debug(JistAPI.getTime() + ":" + this + " <" + this.metric + ">");
    }
  }

  /**
   * Removes the direct link between
   * <code>addr1</code> and <code>addr2</code> from link table.
   *
   * @param addr1 the first address in the link
   * @param addr2 the second address in the link
   */
  protected void removeLinkFromLinkTable(NetAddress addr1, NetAddress addr2) {
    // remove all links between nodes bad_src and bad_dst
    this.metric.removeLink(addr1, addr2);
    this.metric.removeLink(addr2, addr1);

    if (log.isDebugEnabled())
      log.debug(JistAPI.getTime() + ":" + this + " linkTable.removeLinkFromLinkTable "
          + addr1 + " " + addr2 + " ");
    if (log.isDebugEnabled())
      log.debug(JistAPI.getTime() + ":" + this + " <" + this.metric + ">");
  }

  /**
   * Sends the given message along the given route. An Acknowledgement Request
   * option is added to the message, and it is retransmitted if no
   * acknowledgement is received before a timeout occurs.
   *
   * @param msg   the <code>RouteMcExORMsg</code> to be sent
   * @param anno
   * @param route the sequence of nodes to route the message along
   */
  protected void sendWithRoute(NetMessage.Ip msg, MessageAnno anno,
                               RouteDsrBrnMsg.RouteEntry[] route) {
    if (Main.ASSERT)
      Util.assertion(route != null);

    int flowId = 0;
    Integer objFlowId = (Integer)anno.get(MessageAnno.ANNO_RTG_FLOWID);
    if (null != objFlowId)
      flowId = objFlowId.intValue();

    int packetId = 0;
    Integer objPacketId = (Integer)anno.get(MessageAnno.ANNO_RTG_PACKETID);
    if (null != objPacketId)
      packetId = objPacketId.intValue();

    List forwarder = (List) anno.get(MessageAnno.ANNO_RTG_FORWARDER);
    forwarder = (forwarder == null) ? new ArrayList() : new ArrayList(forwarder);
    forwarder.add(this.localAddr);

    // Slap on a McExOR Options header with the proper Source Route option
    RouteDsrBrnMsg mcExORMsg = new RouteDsrBrnMsg(msg.getPayload(),
        flowId, packetId, forwarder);
    mcExORMsg.setNextHeaderType(msg.getProtocol());

    NetAddress nextHop = msg.getDst();
    if (route.length > 0) {
      RouteDsrBrnMsg.OptionSourceRoute srcRoute = new RouteDsrBrnMsg.
          OptionSourceRoute(false, false, 0, route.length, route);
      mcExORMsg.addOption(srcRoute);
      nextHop = srcRoute.nextRecipient(msg.getDst());
    }

    if (log.isInfoEnabled()) {
      StringBuffer strRoute = new StringBuffer();
      for (int i = 0; i < route.length; i++)
        strRoute.append(route[i].addr).append(" ");
      log.info(JistAPI.getTime() + ":" + this + " SendWithRoute; len: " +
          route.length + " [" + strRoute + "]");
    }

    NetMessage.Ip ipMsg = new NetMessage.Ip(mcExORMsg, msg.getSrc(), msg.getDst(),
        Constants.NET_PROTOCOL_MCEXOR, msg.getPriority(), msg.getTTL());

    // query bit rate from link table
    if (bitrateFromLinkTable) {
      // with ETT metric, the link info is the bit rate 
      ETTMetric.EttData info = (ETTMetric.EttData) metric.getLink(localAddr, nextHop);
      if (Main.ASSERT)
        Util.assertion(null != info);
      anno.put(MessageAnno.ANNO_MAC_BITRATE, info.dataRate);
    }

    
    dsr.send(ipMsg, Constants.NET_INTERFACE_DEFAULT, arp.getArpEntry(nextHop), anno);
  }

  public void forwardPacket(NetMessage.Ip ipMsg, MessageAnno anno, NetAddress nextHop) {
    // determine bit rate
    if (bitrateFromLinkTable) {
      // with ETT metric, the link info is the bit rate 
      ETTMetric.EttData info = (ETTMetric.EttData) metric.getLink(localAddr, nextHop);
      if (Main.ASSERT)
        Util.assertion(null != info);
      anno.put(MessageAnno.ANNO_MAC_BITRATE, info.dataRate);
    }

    // get the mac address of next hop and send
    dsr.send(ipMsg, Constants.NET_INTERFACE_DEFAULT, arp.getArpEntry(nextHop), anno);
  }
  

  // ////////////////////////////////////////////////
  // Overwrites
  //

  public int hashCode() {
    return 1;
  }

  /*
   * (non-Javadoc)
   * @see java.lang.Object#toString()
   */
  public String toString() {
    return dsr.toString();
  }
}
