package brn.gui.datasource;

import java.rmi.NoSuchObjectException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.List;

import jist.runtime.JistAPI;
import jist.runtime.Main;
import jist.runtime.Util;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.util.IPropertyChangeListener;

import brn.gui.Activator;
import brn.gui.datasource.actions.ProviderAction;
import brn.sim.DriverRemote;
import brn.sim.DataManager.DataContribution;

public class SimulatorSource implements Source {

  /**
	 * Processes callbacks from the simulator and updates UI accordingly.
	 *
	 * @author kurth
	 */
	public class DriverCallback extends UnicastRemoteObject implements
			DriverRemote.Callback {

    private static final long serialVersionUID = 1L;
    protected Registry registry;

    /**
     * Constructs a driver callback and exports this rmi object;
     * @throws RemoteException
     */
		protected DriverCallback() throws RemoteException {
			super();
		}

    /**
     * Creates a rmi registry and listens for connections.
     * @throws RemoteException
     */
		public void listen() throws RemoteException {
			assert (null == registry);
			registry = LocateRegistry.createRegistry(Main.JIST_PORT);
			registry.rebind(DriverRemote.CALLBACK_RMI_NAME, this);
		}

    /**
     * Closes connection to simulator.
     */
		public void release() {
			if (null != registry) {
				try {
					registry.unbind(DriverRemote.CALLBACK_RMI_NAME);
				} catch (Exception e) {}
				try {
					UnicastRemoteObject.unexportObject(registry, true);
				} catch (NoSuchObjectException e) {}
				registry = null;
			}
			try {
				UnicastRemoteObject.unexportObject(this, true);
			} catch (NoSuchObjectException e) {}
		}

		public void addSimulatorItems(DriverRemote.Item[] items) throws RemoteException {
			Util.assertion(isConnected());

      for (int i = 0; i < items.length; i++) {
        DriverRemote.Item driverItem = items[i];

        Source.Item item = ItemImpl.createItem(driverItem.id,
            driverItem.type, driverItem.path, SimulatorSource.this, action);
        if (null == item) {
          Activator.getDefault().logError("Unknown mapping for data item "
              + driverItem.path);
          continue;
        }

        listItems.add(item);
        if (driverItem.type == DriverRemote.ITEM_TYPE_CONFIG)
          configId = driverItem.id;
      }
		}

    /*
     * (non-Javadoc)
     * @see brn.sim.DriverRemote$Callback#simPaused(long)
     */
		public void simPaused(long time) throws RemoteException {
			Util.assertion(isConnected());

			paused = true;
			SimulatorSource.this.time = time;
			action.simPaused(time);
		}

    /*
     * (non-Javadoc)
     * @see brn.sim.DriverRemote$Callback#connectDriver(brn.sim.DriverRemote)
     */
		public void connectDriver(DriverRemote driver) throws RemoteException {
			Util.assertion(isListening());
			Util.assertion(!isConnected());

			SimulatorSource.this.driver = driver;
      SimulatorSource.this.time = 0;

			try {
				action.simConnected(driver.toString());
			} catch (Throwable e) {
				e.printStackTrace();
				driver = null;
				action.simConnected(null);
				return;
			}
			if (null != registry) {
				try {
					registry.unbind(DriverRemote.CALLBACK_RMI_NAME);
				} catch (Exception e) {}
				try {
					UnicastRemoteObject.unexportObject(registry, true);
				} catch (NoSuchObjectException e) {}
				registry = null;
			}
			action.simListening();
		}

		public boolean isListening() {
			return registry != null;
		}

	}

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

	//  /** Thread executing the jist server */
	//  private Thread jistServerThread;

	//  /** The jist client connecting to the server */
	//  private JistClient client;

	/** Our driver hook within the jist simulation */
	private DriverRemote driver;

	/** Callback for the simulation driver */
	private DriverCallback driverCallback;

	/** whether the simulator is paused */
	private boolean paused;

	private long time;

	private ProviderAction action;

  private int configId;

  private List<Source.Item> listItems;


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

	public SimulatorSource(ProviderAction action) {
		this.paused = false;
		this.time = -1;
    this.configId = -1;
    this.listItems = new ArrayList<Source.Item>();
		this.action = action;
	}

	// ////////////////////////////////////////////////
	// Accessors
	//

  /* (non-Javadoc)
   * @see brn.gui.datasource.Source#getDriverCallback()
   */
  public DriverCallback getDriverCallback() {
    return driverCallback;
  }

  /* (non-Javadoc)
   * @see brn.gui.datasource.Source#getItems()
   */
  public List<Source.Item> getItems() {
    return listItems;
  }

  /* (non-Javadoc)
   * @see brn.gui.datasource.Source#getDriver()
   */
  public DriverRemote getDriver() {
    Util.assertion(isConnected());
    return driver;
  }

  /* (non-Javadoc)
   * @see brn.gui.datasource.Source#getTime()
   */
  public long getTime() {
    return time;
  }

  /* (non-Javadoc)
   * @see brn.gui.datasource.Source#isConnected()
   */
  public boolean isConnected() {
		return driver != null;
	}

	/* (non-Javadoc)
   * @see brn.gui.datasource.Source#isPaused()
   */
	public boolean isPaused() {
		return paused;
	}

	/* (non-Javadoc)
   * @see brn.gui.datasource.Source#isRunning()
   */
	public boolean isRunning() {
		return isConnected() && !isPaused();
	}

  /* (non-Javadoc)
   * @see brn.gui.datasource.Source#isListening()
   */
  public boolean isListening() {
		return driverCallback != null && driverCallback.isListening();
	}

	/* (non-Javadoc)
   * @see brn.gui.datasource.Source#isListenPossible()
   */
	public boolean isListenPossible() {
		return driverCallback == null;
	}

	/* (non-Javadoc)
   * @see brn.gui.datasource.Source#isRunPossible()
   */
	public boolean isRunPossible() {
		return isConnected() && isPaused() && time < JistAPI.END;
	}

	/* (non-Javadoc)
   * @see brn.gui.datasource.Source#isStopPossible()
   */
	public boolean isStopPossible() {
		return isConnected();
	}

	/* (non-Javadoc)
   * @see brn.gui.datasource.Source#isPausePossible()
   */
	public boolean isPausePossible() {
		return isConnected() && !isPaused();
	}

	// ////////////////////////////////////////////////
	// Job Control
	//

	/* (non-Javadoc)
   * @see brn.gui.datasource.Source#listen()
   */
	public void listen() {
		assert (null == driverCallback);
		try {
			driverCallback = new DriverCallback();
			driverCallback.listen();
		} catch (RemoteException e) {
			e.printStackTrace();
			if (null != driverCallback)
				driverCallback.release();
			driverCallback = null;
		}
		action.simListening();
	}

	/* (non-Javadoc)
   * @see brn.gui.datasource.Source#unlisten()
   */
	public void unlisten() {
		assert (null != driverCallback);
		driverCallback.release();
		driverCallback = null;
		action.simListening();
	}

	private void disconnect() {
		if (isConnected())
			unlisten();
		paused = false;
		time = -1;
		driver = null;
    configId = -1;
    listItems.clear();

		action.simConnected(null);
	}

	/* (non-Javadoc)
   * @see brn.gui.datasource.Source#runSim()
   */
	public void runSim() {
		Util.assertion(isConnected());
		Util.assertion(isPaused());

		// Run till end
		try {
			paused = false;
			time = -1;
			driver.simRun(JistAPI.END);
		} catch (RemoteException e) {
			disconnect();
		}
	}

	/* (non-Javadoc)
   * @see brn.gui.datasource.Source#runSimDebug()
   */
	public void runSimDebug() {
		Util.assertion(isConnected());
		Util.assertion(isPaused());

		// Run till next breakpoint
		// TODO
		try {
			paused = false;
			time = -1;
			driver.simRun(0);
		} catch (RemoteException e) {
			disconnect();
		}
	}

	/* (non-Javadoc)
   * @see brn.gui.datasource.Source#pauseSim()
   */
	public void pauseSim() {
		Util.assertion(isConnected());
		Util.assertion(!isPaused());

		try {
			driver.simPause();
		} catch (RemoteException e) {
			disconnect();
		}
	}

	/* (non-Javadoc)
   * @see brn.gui.datasource.Source#stopSim()
   */
	public void stopSim() {
		Util.assertion(isConnected());

		try {
			driver.simStop();
		} catch (RemoteException e) {}
    finally {
      disconnect();
    }
	}

  /* (non-Javadoc)
   * @see brn.gui.datasource.Source#getConfigId()
   */
  public int getConfigId() {
    return configId;
  }

  public String toString() {
    return driver.toString();
  }

  public DataContribution getContribution(int id) throws CoreException {
    try {
      return driver.getContribution(id);
    } catch (RemoteException e) {
      throw new CoreException(Activator.createError("", e));
    }
  }

  public void addPropertyChangeListener(IPropertyChangeListener listener) {
    action.addPropertyChangeListener(listener);
  }

  public void removePropertyChangeListener(IPropertyChangeListener listener) {
    action.removePropertyChangeListener(listener);
  }

  /*
   * (non-Javadoc)
   * @see brn.gui.datasource.Source#dispose()
   */
  public void dispose() {
    if (null != driverCallback)
      driverCallback.release();
    action = null;
    driver = null;
    driverCallback = null;
    listItems.clear();
  }

}