package click.runtime;

import jist.runtime.Controller;
import jist.runtime.JistAPI;
import jist.swans.net.NetAddress;
import click.gui.ClickController;

/**
 * Connection between Click/C++ and Jist/Java
 *
 * TODO ensure that to time elapses during sim_* calls
 *
 * @author kurth
 *
 */
public class ClickAdapter {

  public static interface SimCallbackHandler extends ClickInterface.CallbackHandler {

    int getIfId(String ifname);

    String getAddress(String ifname);

    String getMacAddress(String ifname);

    String getNodeName();

    int trace(String event);

    NetAddress getAddress();

    boolean[] getNicsReady();

  }


  //////////////////////////////////////////////////
  // class vars
  //

  /** link to the gui, if any */
  private static ClickController controllerGui;

  private static boolean clickLoaded = false;

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

  private long clickHandle = 0;

  protected SimCallbackHandler router;

  public static String libraryName = "jistclick";


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

  public ClickAdapter(String routerFile, SimCallbackHandler router, boolean[] nics_ready)
      throws ClickException {
    if (!clickLoaded) {
      System.loadLibrary(libraryName);
      clickLoaded = true;
    }

    this.router = router;
    this.clickHandle = click_create(routerFile, JistAPI.getTime());

    if (0 == clickHandle)
      throw new ClickException("Unable to create a Click instance");

    if (null != controllerGui)
      controllerGui.addSimNode(this);

    JistAPI.runAt(new ShutdownHook(), JistAPI.END);
  }

    private class ShutdownHook implements Runnable {
    public void run(){
      try {
        click_kill();
      } catch (ClickException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
  }

  public String toString() {
    return sim_get_node_name();
  }

  //////////////////////////////////////////////////
  // natives
  //

  protected native long click_create(
      String routerFile,
      long startSimulationTime);

  protected native void click_run(
      long clickHandle,
      long currSimulationTime,
      boolean[] nics_ready);

  protected native void click_kill(
      long clickHandle,
      long currSimulationTime);

  protected native int click_send(
      long clickHandle,
      long currSimulationTime,
      int ifid,
      int type,
      byte[] data,
      int id,
      int fid,
      int simtype,
      boolean txfeedback,
      boolean[] nics_ready);

  protected native String click_read_handler(
      long clickHandle,
      long currSimulationTime,
      String elemName,
      String handlerName);

  protected native int click_write_handler(
      long clickHandle,
      long currSimulationTime,
      String elemName,
      String handlerName,
      String value);

  //////////////////////////////////////////////////
  // call stubs for natives
  //

  public void click_run() throws ClickException {
    if (0 == clickHandle)
      throw new ClickException("Click not available");

    boolean[] nics_ready = router.getNicsReady();

    // Initial run, must register scheduled click timer in jist
    click_run(clickHandle, JistAPI.getTime(), nics_ready);
  }

  public void click_kill() throws ClickException {
    if (0 == clickHandle)
      throw new ClickException("Click not available");

    click_kill(clickHandle, JistAPI.getTime());
    clickHandle = 0;
  }

  public void click_send(
      int ifid,
      int type,
      byte[] data,
      int id,
      int fid,
      int simtype,
      boolean txfeedback) throws ClickException {
    if (0 == clickHandle)
      throw new ClickException("Click not available");

    boolean[] nics_ready = router.getNicsReady();
    click_send(clickHandle, JistAPI.getTime(), ifid, type, data, id,
        fid, simtype, txfeedback, nics_ready);
  }

  public String click_read_handler(
      String elemName,
      String handlerName) throws ClickException {
    if (0 == clickHandle)
      throw new ClickException("Click not available");

    return click_read_handler(clickHandle, JistAPI.getTime(),
        elemName, handlerName);
  }

  public int click_write_handler(
      String elemName,
      String handlerName,
      String value) throws ClickException {
    if (0 == clickHandle)
      throw new ClickException("Click not available");

    return click_write_handler(clickHandle, JistAPI.getTime(),
        elemName, handlerName, value);
  }


  //////////////////////////////////////////////////
  // click to jist call interface
  //

  /*
   * The simulated system also has to provide a few services to click,
   * notably some way of injecting packets back into the system,
   * mapping interface names to id numbers, and arranging for click
   * to execute at a specified time in the future.
   */

  public int sim_ifid_from_name(String ifname) {
    return this.router.getIfId(ifname);
  }

  public String sim_ipaddr_from_name(String ifname) {
    return this.router.getAddress(ifname);
  }

  public String sim_macaddr_from_name(String ifname) {
    return this.router.getMacAddress(ifname);
  }

  public int sim_send_to_if(
      int clickHandle,
      int ifid,
      int type,
      byte[] data,
      int id, int fid, int simtype) throws ClickException {

    this.router.recvFromClick(ifid, data, type);
    return 0;
  }

  public String sim_get_node_name() {
    return this.router.getNodeName();
  }

  public int sim_trace(int clickHandle, String event) {
    return this.router.trace(event);
  }

  public int sim_get_node_id(int clickHandle) {
    return this.router.getAddress().getId();
  }

  public int sim_get_next_pkt_id(int clickHandle) {
    // TODO
    return (0);
  }

  private Runnable scheduleRunner = new Runnable() {
    public void run() {
      try {
        click_run();
      } catch (ClickException e) {
      	throw new RuntimeException(e);
      }
    }
  };

  public int sim_schedule(int clickHandle, long when) {
    JistAPI.runAt(scheduleRunner, when);

    return 0;
  }


  //////////////////////////////////////////////////
  // entity implementation
  //

  public static void useGui() {
    if (null == controllerGui)
      controllerGui = ClickController.newWindow();
    Controller.getActiveController().addLogger(controllerGui);
  }

}
