package click.swans.net;

import jist.runtime.JistAPI;
import jist.swans.Constants;
import jist.swans.mac.MacAddress;
import jist.swans.misc.Mapper;
import jist.swans.misc.Protocol;
import jist.swans.net.NetAddress;
import jist.swans.net.PacketLoss;

import org.apache.log4j.Logger;

import click.runtime.ClickAdapter;
import click.runtime.ClickException;
import click.runtime.ClickInterface;
import click.runtime.ClickInterfaceSimImpl;

/**
 *
 * TODO: implement an 802.3 mac/radio/field
 *
 * @author kurth
 *
 */
public class ClickRouter extends AbstractClickRouter
    implements Protocol, ClickAdapter.SimCallbackHandler {

  /**
   * IP logger.
   */
  public static final Logger log = Logger.getLogger(ClickRouter.class.getName());

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

  /** self-referencing proxy entity. */
  protected Protocol selfProtocol;


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

  /**
   * Initialize IP implementation with given address and protocol mapping.
   *
   * @param addr local network address
   * @param protocolMap protocol number mapping
   * @param in incoming packet loss model
   * @param out outgoing packet loss model
   */
  public ClickRouter(NetAddress addr, Mapper protocolMap, PacketLoss in,
      PacketLoss out) throws ClickException
  {
    super(addr, protocolMap, in, out);
    this.selfProtocol = (Protocol) JistAPI.proxy(new Protocol.Dlg(this),
        Protocol.class);
    
    this.clickStartedEvent = new ClickStartetEvent(this);
    this.clickShutdownEvent = new ClickShutdownEvent(this);
    clickPullEvent = new ClickPullEvent(this, 500*Constants.MILLI_SECOND);
    if (clickPullEvent.isActive())
      clickPullEvent.reschedule(0);
  }

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

  public Protocol getProtocolProxy() {
    return this.selfProtocol;
  }

  /**
   * Create native click modular router instance
   *
   * @param routerFile the router file to use
   * @throws ClickException
   */
  public void createClick(String routerFile) throws ClickException {
    // create click adapter
    this.clickInterface = new ClickInterfaceSimImpl(
      new ClickAdapter(routerFile, this, this.nics_ready));
  }

  //////////////////////////////////////////////////
  // address
  //

  private boolean isTunTapDevice(String devName) {
    return (devName.startsWith("tun")
        || devName.startsWith("tap")
        || devName.startsWith("local")
        || devName.startsWith("serv"));
  }

  public MacAddress getMacAddress(int ifid) {
    return getNicInfo(ifid).addr;
  }

  public int getNumInterfaces() {
    return nics.length;
  }


  //////////////////////////////////////////////////
  // Protocol implementation
  //

  /*
   * (non-Javadoc)
   * @see jist.swans.misc.Protocol#start()
   */
  public void start() {
    try {
      ((ClickInterfaceSimImpl)this.clickInterface).getAdaptee().click_run();
    } catch (ClickException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    if (clickStartedEvent.isActive())
      clickStartedEvent.handle();
  }


  //////////////////////////////////////////////////
  // CallbackHandler implementation
  //

  /* (non-Javadoc)
   * @see click.runtime.ClickAdapter.SimCallbackHandler#getIfId(java.lang.String)
   */
  public int getIfId(String ifname) {
    int ifid = -1;

    if (isTunTapDevice(ifname)) {
      ifid = ClickInterface.IFID_KERNELTAP;
    }
    else if (ifname.startsWith("eth") || ifname.startsWith("ath")) {
      for (int i = 0; i < nics.length; i++)
        if (getNicInfo(i).name.equals(ifname))
          ifid = i;
    }
    log.debug("Interface " + ifname + " has id " + ifid);

    return (ifid);
  }

  /* (non-Javadoc)
   * @see click.runtime.ClickAdapter.SimCallbackHandler#getAddress(java.lang.String)
   */
  public String getAddress(String ifname) {
    // TODO an ip per device? or a single ip address??
    for (int i = 0; i < nics.length; i++) {
      if (getNicInfo(i).name.equals(ifname)) {
        log.debug("Interface " + ifname + " has ip " + localAddr);
        return this.localAddr.toString();
      }
    }

    if (isTunTapDevice(ifname)) {
      log.debug("Interface " + ifname + " has ip " + localAddr);
      return this.localAddr.toString();
    }

    return null;
  }

  /* (non-Javadoc)
   * @see click.runtime.ClickAdapter.SimCallbackHandler#getMacAddress(java.lang.String)
   */
  public String getMacAddress(String ifname) {
    String mac = null;
    for (int i = 0; i < nics.length; i++) {
      if (getNicInfo(i).name.equals(ifname)) {
        mac = getNicInfo(i).addr.toString();
        break;
      }
    }

    if (isTunTapDevice(ifname)) {
      // It is a fake mac, because we emit ip packets for tun/tap devices
      mac = "00:00:00:00:00:00";
    }

    log.debug("Interface " + ifname + " has mac " + mac);
    return mac;
  }


  /* (non-Javadoc)
   * @see click.runtime.ClickAdapter.SimCallbackHandler#getNodeName()
   */
  public String getNodeName() {
    // TODO what about node names?
    return localAddr.toString();
  }

  /* (non-Javadoc)
   * @see click.runtime.ClickAdapter.SimCallbackHandler#trace(java.lang.String)
   */
  public int trace(String event) {
    // TODO tracing
    log.debug(event);
    return 0;
  }

  /* (non-Javadoc)
   * @see click.runtime.ClickAdapter.SimCallbackHandler#getNicsReady()
   */
  public boolean[] getNicsReady() {
    return this.nics_ready;
  }

}
