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.mac.MacAddress;
import jist.swans.misc.Message;
import jist.swans.misc.MessageAnno;
import jist.swans.net.NetAddress;
import jist.swans.net.NetInterface;
import jist.swans.net.NetMessage;
import jist.swans.net.NetMessage.Ip;
import jist.swans.route.AbstractRoute;
import jist.swans.route.RouteInterface;

import org.apache.log4j.Logger;

import brn.swans.route.metric.ArpTableInterface;

/**
 * An implementation of the DSR Routing protocol with ETX link metrics.
 *
 * @author Zubow
 */
public class RouteDsrBrn extends AbstractRoute implements RouteDsrBrnInterface {

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

  /** The maximum amount of jitter before sending a packet. */
  public static final long BROADCAST_JITTER = 10 * Constants.MILLI_SECOND;


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

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

  /**
   * The interface to the network layer.
   */
  protected NetInterface netEntity;

  /**
   * The proxy interface for this object.
   */
  protected RouteDsrBrnInterface self;

  /**
   * Discovery part
   */
  protected RouteDsrBrnInterface.Discovery discovery;

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


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

  /**
   *
   * Creates a new RouteMcExOR object.
   *
   * @param localAddr local node address
   */
  public RouteDsrBrn(NetAddress localAddr, ArpTableInterface arp) {
    this.localAddr = localAddr;

    this.discovery = null;
    this.arp = arp;

    /*
     * NOTE: we use the artificial dlg class for proxy creation, so we can
     * support implementation inheritance.
     */
    self = (RouteDsrBrnInterface) JistAPI.proxy(
        new RouteDsrBrnInterface.Dlg(this), RouteDsrBrnInterface.class);
  }

  /*
   * (non-Javadoc)
   * @see jist.swans.route.AbstractRoute#setNode(jist.swans.Node)
   */
  public void setNode(Node node) {
    super.setNode(node);

    this.discovery.setNode(node, this);
  }

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

  // ////////////////////////////////////////////////
  // entity hookup
  //

  /**
   * Sets the interface to the network layer.
   *
   * @param netEntity the interface to the network layer
   */
  public void setNetEntity(NetInterface netEntity) {
    this.netEntity = netEntity;
  }

  /**
   * @param discovery the discovery to set
   */
  public void setDiscovery(RouteDsrBrnInterface.Discovery discovery) {
    this.discovery = discovery;
  }

  public Discovery getDiscovery() {
    return discovery;
  }

  /**
   * Gets the proxy interface for this object.
   *
   * @return the proxy <code>RouteDsrEtxInterface</code> interface for this
   *         object
   */
  public RouteDsrBrnInterface getProxy() {
    return self;
  }

  /*
   * (non-Javadoc)
   * @see jist.swans.route.AbstractRoute#getRouteProxy()
   */
  public RouteInterface getRouteProxy() {
    return self;
  }

  public AbstractRoute.DiscardEvent getDiscardEvent() {
    return discardEvent;
  }

  // ////////////////////////////////////////////////
  // Helper methods
  //

  /*
   * (non-Javadoc)
   * @see jist.swans.route.RouteInterface#peek(jist.swans.net.NetMessage, jist.swans.misc.MessageAnno)
   */
  public void peek(NetMessage msg, MacAddress lastHop, MessageAnno anno) {
    discovery.peek(msg, lastHop, anno);

    if (!(msg instanceof NetMessage.Ip))
      return;

    NetMessage.Ip ipMsg = (NetMessage.Ip) msg;
    if (ipMsg.getProtocol() != Constants.NET_PROTOCOL_MCEXOR)
      return;

    RouteDsrBrnMsg mcExORMsg = (RouteDsrBrnMsg) ipMsg.getPayload();

    if (log.isDebugEnabled())
      log.debug(JistAPI.getTime() + ":" + this + " saw message from " + ipMsg.getSrc()
              + "(" + lastHop + ") to " + ipMsg.getDst() + " [" + mcExORMsg + "]");

    RouteDsrBrnMsg.OptionSourceRoute sourceRoute = mcExORMsg.getSourceRoute();
    if (sourceRoute != null
        && localAddr.equals(sourceRoute.nextRecipient(ipMsg.getDst()))
        && !localAddr.equals(ipMsg.getDst())) {
      forwardPacket(mcExORMsg, anno, lastHop, sourceRoute,  ipMsg.getSrc(),
          ipMsg.getDst(), ipMsg.getProtocol(), ipMsg.getPriority(),
          ipMsg.getTTL(), ipMsg.getId(), ipMsg.getFragOffset());
    }
  }

  /*
   * (non-Javadoc)
   * @see jist.swans.route.RouteInterface#send(jist.swans.net.NetMessage, jist.swans.misc.MessageAnno)
   */
  public void send(NetMessage msg, MessageAnno anno) {
    NetMessage.Ip ipMsg = (NetMessage.Ip) msg;
    if (null == anno)
      anno = new MessageAnno();

    if (ipMsg.getProtocol() == Constants.NET_PROTOCOL_MCEXOR) {
      // We're not really supposed to "send" this packet -- we might have to
      // to forward it, but that happens along with all the other option
      // processing in peek().
    } else {
      discovery.send(msg, anno);
    }
  }

  /*
   * (non-Javadoc)
   * @see jist.swans.net.NetInterface.RawNetHandler#receive(jist.swans.net.NetMessage, jist.swans.misc.MessageAnno, jist.swans.mac.MacAddress, byte)
   */
  public void receive(NetMessage msg, MessageAnno anno, MacAddress lastHop,
      byte macId) {
    NetMessage.Ip ipmsg = (NetMessage.Ip) msg;
    this.receive(ipmsg.getPayload(), ipmsg.getSrc(), lastHop, macId,
        ipmsg.getDst(), ipmsg.getPriority(), ipmsg.getTTL(), anno);
  }

  /*
   * (non-Javadoc)
   * @see jist.swans.net.NetInterface.NetHandler#receive(jist.swans.misc.Message, jist.swans.net.NetAddress, jist.swans.mac.MacAddress, byte, jist.swans.net.NetAddress, byte, byte, jist.swans.misc.MessageAnno)
   */
  public void receive(Message msg, NetAddress src, MacAddress lastHop,
      byte macId, NetAddress dst, byte priority, byte ttl, MessageAnno anno) {
    if (log.isInfoEnabled() && localAddr.equals(dst))
      // Don't count received broadcast packets?
      log.info(JistAPI.getTime() + ":" + this + " Received packet from " + src
          + " at " + dst);

    // Don't process any options here -- that's all done by peek. Just forward
    // any content on to the transport layer (or whatever).
    RouteDsrBrnMsg mcExORMsg = (RouteDsrBrnMsg) msg;

    RouteDsrBrnMsg.OptionSourceRoute sourceRoute = mcExORMsg.getSourceRoute();
    if (sourceRoute != null) {
      // Strange as it may seem, we will discard this packet, which is
      // in fact intended for us, if it arrives here before traversing
      // the other links in the intended route. (Route shortening should
      // prevent this from happening too often.)
      if (!localAddr.equals(sourceRoute == null ? dst : sourceRoute.nextRecipient(dst)))
        return;
    }

    if (mcExORMsg.getContent() != null) {
      // Preserve flow and packet id
      RouteDsrBrnMsg.OptionId id = (RouteDsrBrnMsg.OptionId)
          mcExORMsg.getOption(RouteDsrBrnMsg.OPT_ID);
      anno.put(MessageAnno.ANNO_RTG_FLOWID, new Integer(id.getAnnoFlowId()));
      anno.put(MessageAnno.ANNO_RTG_PACKETID, new Integer(id.getAnnoPacketId()));
      anno.put(MessageAnno.ANNO_RTG_HOPCOUNT, new Integer(id.getHopCount()+1));

      if (packetForwardedEvent.isActive())
        packetForwardedEvent.handle(msg, anno, arp.getArpEntry(lastHop), localAddr);

      // Now go through some strange contortions to get this message
      // received by the proper protocol handler
      NetMessage.Ip newIp = new NetMessage.Ip(mcExORMsg.getContent(),
              src, dst, mcExORMsg.getNextHeaderType(), priority, ttl);

      netEntity.receive(newIp, lastHop, macId, false, anno);

      if (log.isInfoEnabled())
        log.info(JistAPI.getTime() + ":" + this + " Received data packet from "
            + src + " at " + dst + " " + sourceRoute);
    }
  }

  /*
   * (non-Javadoc)
   * @see brn.swans.route.RouteDsrBrnInterface#send(jist.swans.net.NetMessage.Ip, int, jist.swans.mac.MacAddress, jist.swans.misc.MessageAnno)
   */
  public void send(Ip msg, int interfaceId, MacAddress nextHop, MessageAnno anno) {
    if (candSelectedEvent.isActive()
        && !msg.getDst().equals(NetAddress.ANY)) {
      // TODO flow id
      Integer packetId = (Integer) anno.get(MessageAnno.ANNO_RTG_PACKETID);
      List lstMacCandidates = new ArrayList();
      lstMacCandidates.add(nextHop);
      if (null != packetId)
        candSelectedEvent.handle(packetId.intValue(), lstMacCandidates , msg, anno);
    }

    Util.assertion(nextHop != null);

    // delegate to network layer
    netEntity.send(msg, interfaceId, nextHop, anno);
  }

  /**
   * Handle transmission errors in discovery
   *
   * @param msg the message with transmission failures
   * @param anno the referenced annotations.
   */
  protected void handleTransmitError(Ip msg, MessageAnno anno) {
    discovery.handleTransmitError(msg, anno);
  }

  /**
   * Forwards a McExOR packet containing a Source Route option to the next
   * intended recipient. An Acknowledgement Request option is added to the
   * headers, and the packet is retransmitted if no acknowledgement is
   * received before the allotted timeout elapses.
   *
   * @param msg        the <code>RouteMcExORMsg</code> to be forwarded
   * @param anno
   * @param opt        the Source Route option
   * @param src        the address of the originator of this packet
   * @param dest       the address of the ultimate destination of this packet
   * @param protocol   the IP protocol of this packet (usually McExOR)
   * @param priority   the IP priority of this packet
   * @param ttl        the IP time to live of this packet
   * @param id         the IP identification of this packet
   * @param fragOffset the IP fragmentation offset of this packet
   */
  protected void forwardPacket(RouteDsrBrnMsg msg, MessageAnno anno,
      MacAddress lastHop, RouteDsrBrnMsg.OptionSourceRoute opt,
      NetAddress src, NetAddress dest, short protocol, byte priority, byte ttl,
      short id, short fragOffset) {
    // Remember mac address in arp table
    NetAddress prevNode = opt.prevRecipient(src);
    arp.addArpEntry(prevNode, lastHop);

    // Check if I am the intended next recipient of this packet
    if (localAddr.equals(opt.nextRecipient(dest))) {
      // Preserve flow and packet id
      RouteDsrBrnMsg.OptionId optId = (RouteDsrBrnMsg.OptionId)
          msg.getOption(RouteDsrBrnMsg.OPT_ID);
      if (null != optId) {
        anno.put(MessageAnno.ANNO_RTG_FLOWID, new Integer(optId.getAnnoFlowId()));
        anno.put(MessageAnno.ANNO_RTG_PACKETID, new Integer(optId.getAnnoPacketId()));
      }
      long arrival = (Long) anno.get(MessageAnno.ANNO_NET_ARRIVAL);
//      anno.put(MessageAnno.ANNO_RTG_FORWARDER, optId.getForwarder());

      if (packetForwardedEvent.isActive())
        packetForwardedEvent.handle(msg, anno, arp.getArpEntry(lastHop), localAddr);

      // construct new source route header
      RouteDsrBrnMsg.OptionSourceRoute srcRoute = new RouteDsrBrnMsg.
        OptionSourceRoute(opt.isFirstHopExternal(), opt.isLastHopExternal(),
          opt.getSalvageCount(), opt.getNumSegmentsLeft() - 1, opt.getRoute());

      // construct new packet with new id field
      RouteDsrBrnMsg newMsg = (RouteDsrBrnMsg) msg.clone();
      List newOptions = newMsg.getOptions();
      newOptions.remove(opt);
      newMsg.addOption(srcRoute);

      if (null != optId) {
        RouteDsrBrnMsg.OptionId optIdNew = (RouteDsrBrnMsg.OptionId) optId.clone();
        optIdNew.addForwarder(this.localAddr);
        optIdNew.intHopCount();
        newMsg.removeOption(RouteDsrBrnMsg.OPT_ID);
        newMsg.addOption(optIdNew);
        if (Main.ASSERT)
          Util.assertion(optId.getHopCount() + 1 == optIdNew.getHopCount());
      }

      // Construct net message
      NetMessage.Ip ipMsg = new NetMessage.Ip(newMsg, src, dest, protocol,
          priority, (byte) (ttl - 1), id, fragOffset);

      // Construct new anno (we do not want to get in confusion with old annos)
      // do not recycle the old one, because we need it in mac.
      anno = new MessageAnno();
      if (null != optId) {
        anno.put(MessageAnno.ANNO_RTG_FLOWID, new Integer(optId.getAnnoFlowId()));
        anno.put(MessageAnno.ANNO_RTG_PACKETID, new Integer(optId.getAnnoPacketId()));
      }
      anno.put(MessageAnno.ANNO_NET_ARRIVAL, arrival);

      // give discovery the opportunity for shortening etc.
      discovery.forwardPacket(ipMsg, anno, srcRoute.nextRecipient(dest));
    }
  }

}
