package brn.sim;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.rmi.ConnectException;
import java.rmi.NoSuchObjectException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.Map;
import java.util.Vector;

import jist.runtime.Controller;
import jist.runtime.Event;
import jist.runtime.JistAPI;
import jist.runtime.guilog.LogInterface;
import jist.swans.misc.Util;
import brn.sim.DataManager.DataContribution;
import brn.sim.DriverRemote.Item;
import brn.sim.data.AbstractDiagramData;
import brn.sim.data.ForwardGraphData;
import brn.sim.data.LinkTableData;

public class UiConnector {

  protected static final long UI_UPDATE_INTERVAL = 500 /* ms */;

  //////////////////////////////////////////////////
  // gui support
  //

  private class GuiDriver extends UnicastRemoteObject implements DriverRemote {

    private static final long serialVersionUID = 1L;

    protected GuiDriver() throws RemoteException {
      super();

      JistAPI.runAt(new Runnable() {
        public void run() {
          try {
            if (callback != null)
              logger.execute(JistAPI.getTime());
            UnicastRemoteObject.unexportObject(GuiDriver.this, true);
          } catch (NoSuchObjectException e) {}
        }
      }, JistAPI.END);
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.DriverRemote#getConfig()
     */
    public AbstractParams getConfig(int id) throws RemoteException {
      return (AbstractParams) dataManager.getContribution(id);
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.DriverRemote#getData(int)
     */
    public AbstractDiagramData getData(int id) throws RemoteException {
      return (AbstractDiagramData) dataManager.getContribution(id);
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.DriverRemote#getForwardGraph()
     */
    public ForwardGraphData getForwardGraph(int id) throws RemoteException {
      return (ForwardGraphData) dataManager.getContribution(id);
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.DriverRemote#getArpTable(int)
     */
    public Map getArpTable(int id) throws RemoteException {
      return (Map) dataManager.getContribution(id);
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.DriverRemote#getLinkTable(int)
     */
    public LinkTableData getLinkTable(int id) throws RemoteException {
      return (LinkTableData) dataManager.getContribution(id);
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.DriverRemote#getContribution(int)
     */
    public DataContribution getContribution(int id) throws RemoteException {
      return dataManager.getContribution(id);
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.DriverRemote#simPause()
     */
    public void simPause() throws RemoteException {
      logger.nextBreakpoint = 0;
      logger.attach();
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.DriverRemote#simRun(long)
     */
    public void simRun(long nextStop) throws RemoteException {
      logger.step(nextStop);
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.DriverRemote#simStop()
     */
    public void simStop() throws RemoteException {
      JistAPI.end();
      logger.detach();
      logger.step(JistAPI.END);
      if (null != threadGuiUpdater)
        threadGuiUpdater.interrupt();

      try {
        UnicastRemoteObject.unexportObject(GuiDriver.this, true);
      } catch (NoSuchObjectException e) {}

      threadGuiUpdater = null;
      callback = null;
    }
  }

  private class GuiLogger implements LogInterface {
    public long nextBreakpoint = 0;
    public Object pauseLock = new Object();
    public boolean paused = false;
    private boolean attached = false;

    public GuiLogger() {
      attach();
    }

    public void step(long nextStop) {
      logger.nextBreakpoint = nextStop;
      synchronized (logger.pauseLock) {
        logger.pauseLock.notify();
      }
    }

    public void attach() {
      if (!attached)
        Controller.getActiveController().addLogger(this);
      attached = true;
    }

    public void detach() {
      if (attached)
        Controller.getActiveController().removeLogger(this);
      attached = false;
    }

    public void add(Event id, Event parent) {}

    public void del(Event id) {}

    public void execute(Event id) {
      execute(id.time);
    }

    public void execute(long time) {
      if (time < nextBreakpoint)
        return;

      paused = true;
      try {
        callback.simPaused(time);
      } catch (RemoteException e1) {
        detach();
        return;
      }

      synchronized (pauseLock) {
        try {
          pauseLock.wait();
        } catch (InterruptedException e) {
          Thread.currentThread().interrupt();
        }
      }

      if (logger.nextBreakpoint == JistAPI.END)
        detach();
    }
  }

  private class ThreadGuiUpdater extends Thread implements JistAPI.DoNotRewrite {
    public ThreadGuiUpdater() {
      super("ThreadGuiUpdater");
    }
    public void run() {
      try {
        while (true) {
          DriverRemote.Item[] items = new DriverRemote.Item[0];

          synchronized (vecNewInformationItems) {
            items = (DriverRemote.Item[]) vecNewInformationItems.toArray(items);
            vecNewInformationItems.clear();
          }

          try {
            callback.addSimulatorItems(items);
          } catch (RemoteException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
          }

          Thread.sleep(UI_UPDATE_INTERVAL);
        }
      } catch (InterruptedException e) { // pass
      }
    }
  }

  protected DriverRemote.Callback callback;

  private GuiLogger logger;

  private Vector vecNewInformationItems = new Vector();

  private ThreadGuiUpdater threadGuiUpdater;

  private DataManager dataManager;

  public UiConnector(DataManager dataManager) {
    this.dataManager = dataManager;
  }

  /**
   * Connect current simulation instance to gui.
   * Installs a jist simulator logger to intervene execution and starts a gui
   * update thread.
   * @param host host name where the gui runs
   * @param port port on which the gui runs (defalt: Main.JIST_PORT)
   */
  public void connect(String host, int port) {
    try {
      Registry r = LocateRegistry.getRegistry(host, port);
      callback = (DriverRemote.Callback) r.lookup(DriverRemote.CALLBACK_RMI_NAME);
      callback.connectDriver(new GuiDriver());
    } catch (NotBoundException e) {
      callback = null;
      return;
    } catch (NoSuchObjectException e) {
      callback = null;
      return;
    } catch (ConnectException e) {
      callback = null;
      return;
    } catch (RemoteException e) {
      e.printStackTrace();
      callback = null;
      return;
    }

    Util.assertion(logger == null);
    logger = new GuiLogger();
    dataManager.addPropertyChangeListener(
        new PropertyChangeListener() {
          public void propertyChange(PropertyChangeEvent evt) {
            addInformationItem((DriverRemote.Item)evt.getNewValue());
          }
    });

    Util.assertion(threadGuiUpdater == null);
    threadGuiUpdater = new ThreadGuiUpdater();
    threadGuiUpdater.setDaemon(true);
    threadGuiUpdater.start();
  }

  protected void addInformationItem(Item item) {
//    if (DriverRemote.ITEM_TYPE_DATA == item.type) {
//      AbstractDiagramData data = (AbstractDiagramData)
//        dataManager.getContribution(item.id);
//
//      if (!data.isVisible())
//        return;
//    }

    vecNewInformationItems.add(item);
  }

}
