/**
 *
 */
package brn.gui.datasource;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import jist.runtime.JistAPI;

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

import brn.distsim.ormapper.util.DbBinaryLoader;
import brn.gui.Activator;
import brn.gui.datasource.actions.ProviderAction;
import brn.sim.DriverRemote;
import brn.sim.DataManager.DataContribution;

/**
 * @author kurth
 *
 */
public class DatabaseSource implements Source {

  private ProviderAction action;

  private List<Source.Item> listItems;

  private Map<Integer, DataContribution> mapContributions;

  private int configId;

  private DbBinaryLoader loader;

  private boolean connected;

  private int simulationId;

  public DatabaseSource(ProviderAction action) {
    this.configId = -1;
    this.listItems = new ArrayList<Source.Item>();
    this.mapContributions = new HashMap<Integer, DataContribution>();
    this.action = action;
    this.connected = false;
  }

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

  /* (non-Javadoc)
   * @see brn.gui.datasource.Source#getContribution(int)
   */
  public DataContribution getContribution(int id) throws CoreException {
    DataContribution contrib = mapContributions.get(id);
    if (null != contrib)
      return contrib;

    try {
      contrib = (DataContribution) loader.load(simulationId, id);
    } catch (Exception e) {
      throw new CoreException(Activator.createError(
          "Error fetching contribution from database", e));
    }
    mapContributions.put(id, contrib);
    return contrib;
  }

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

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

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

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

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

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

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

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

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

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

  /* (non-Javadoc)
   * @see brn.gui.datasource.Source#pauseSim()
   */
  public void pauseSim() {
    // pass
  }

  /* (non-Javadoc)
   * @see brn.gui.datasource.Source#runSim()
   */
  public void runSim() {
    // pass
  }

  /* (non-Javadoc)
   * @see brn.gui.datasource.Source#runSimDebug()
   */
  public void runSimDebug() {
    // pass
  }

  /* (non-Javadoc)
   * @see brn.gui.datasource.Source#stopSim()
   */
  public void stopSim() {
    // pass
  }

  /* (non-Javadoc)
   * @see brn.gui.datasource.Source#unlisten()
   */
  public void unlisten() {
    connected = false;
    this.configId = -1;
    this.listItems.clear();
    this.mapContributions.clear();
    try {
      this.loader.finalize();
    } catch (Throwable e) {
      Activator.getDefault().logError("Error closing database connection", e);
    }
    this.loader = null;
  }

  /* (non-Javadoc)
   * @see brn.gui.datasource.Source#addPropertyChangeListener(org.eclipse.jface.util.IPropertyChangeListener)
   */
  public void addPropertyChangeListener(IPropertyChangeListener listener) {
      action.addPropertyChangeListener(listener);
  }

  /* (non-Javadoc)
   * @see brn.gui.datasource.Source#removePropertyChangeListener(org.eclipse.jface.util.IPropertyChangeListener)
   */
  public void removePropertyChangeListener(IPropertyChangeListener listener) {
    action.removePropertyChangeListener(listener);
  }

  /*
   * (non-Javadoc)
   * @see brn.gui.datasource.Source#dispose()
   */
  public void dispose() {
    if (loader != null) {
      try {
        loader.finalize();
      } catch (Throwable e) {
        Activator.getDefault().logError("Error closing database connection", e);
      }
      loader = null;
    }
    action = null;
  }

  public void connect(String host, String database, String user, String password,
      int jobId) throws CoreException {
    String url = "jdbc:mysql://" + host + "/" + database;
    try {
      this.loader = new DbBinaryLoader(url, user, password, 
          DatabaseSource.class.getClassLoader());
      this.simulationId = jobId;
      this.connected = true;
    } catch (ClassNotFoundException e) {
      throw new CoreException(Activator.createError("Error connecting to database", e));
    } catch (SQLException e) {
      throw new CoreException(Activator.createError("Error connecting to database", e));
    } catch (Throwable e) {
      throw new CoreException(Activator.createError("Error connecting to database", e));
    }
  }

  /* (non-Javadoc)
   * @see brn.gui.datasource.Source#listen()
   */
  public void listen() {
    try {
      List dbEntries = loader.loadEntries(simulationId);
      action.setBatch(true);

      Iterator iter = dbEntries.iterator();
      while (null != iter && iter.hasNext()) {
        DbBinaryLoader.DbEntry entry = (DbBinaryLoader.DbEntry) iter.next();

        Source.Item item = ItemImpl.createItem(entry.id, entry.type,
            unparse(entry.path), this, action);

        this.listItems.add(item);
        if (entry.type == DriverRemote.ITEM_TYPE_CONFIG)
          configId = entry.id;
      }
    } catch (Throwable e) {
      // TODO differentiated error handling
      Activator.getDefault().logError("Error retrieving data from db.", e);
    }
    action.setBatch(false);
  }

  private String[] unparse(String path) {
    StringTokenizer tok = new StringTokenizer(path, ",");
    String[] ret = new String[tok.countTokens()];
    for (int i = 0; i < ret.length; i++) {
      ret[i] = tok.nextToken();
    }
    return ret;
  }

}
