/*
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 *    EnsembleLibrary.java
 *    Copyright (C) 2006 Robert Jung
 *
 */

package weka.classifiers;

import weka.core.RevisionHandler;
import weka.core.RevisionUtils;
import weka.gui.ensembleLibraryEditor.LibrarySerialization;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.Serializable;
import java.io.Writer;
import java.util.Iterator;
import java.util.TreeSet;
import java.util.Vector;

import javax.swing.JComponent;
import javax.swing.JOptionPane;

/**
 * This class represents a library of classifiers.  This class
 * follows the factory design pattern of creating LibraryModels
 * when asked.  It also has the methods necessary for saving
 * and loading models from lists.
 *
 * @author  Robert Jung (mrbobjung@gmail.com)
 * @version $Revision: 6041 $
 */
public class EnsembleLibrary
  implements Serializable, RevisionHandler {

  /** for serialization */
  private static final long serialVersionUID = -7987178904923706760L;

  /** The default file extension for model list files */
  public static final String XML_FILE_EXTENSION = ".model.xml";

  /** The flat file extension for model list files */
  public static final String FLAT_FILE_EXTENSION = ".mlf";

  /** the set of classifiers that constitute the library */
  public TreeSet m_Models;

  /** A helper class for notifying listeners when the library changes */
  private transient PropertyChangeSupport m_LibraryPropertySupport = new PropertyChangeSupport(this);

  /**
   * Constructor is responsible for initializing the data
   * structure hoilding all of the models
   *
   */
  public EnsembleLibrary() {

    m_Models = new TreeSet(new EnsembleLibraryModelComparator());
  }

  /**
   * Returns the number of models in the ensemble library
   *
   * @return	the number of models
   */
  public int size() {
    if (m_Models != null)
      return m_Models.size();
    else
      return 0;
  }

  /**
   * adds a LibraryModel to the Library
   *
   * @param model	the model to add
   */
  public void addModel(EnsembleLibraryModel model) {
    m_Models.add(model);
    if (m_LibraryPropertySupport != null)
      m_LibraryPropertySupport.firePropertyChange(null, null, null);
  }

  /**
   * adds a LibraryModel to the Library
   *
   * @param modelString	the model to add
   */
  public void addModel(String modelString) {
    m_Models.add(createModel(modelString));
    m_LibraryPropertySupport.firePropertyChange(null, null, null);
  }

  /**
   * removes a LibraryModel from the Library
   *
   * @param model	the model to remove
   */
  public void removeModel(EnsembleLibraryModel model) {
    m_Models.remove(model);
    m_LibraryPropertySupport.firePropertyChange(null, null, null);
  }

  /**
   * creates a LibraryModel from a string representing the command
   * line invocation
   *
   * @param classifier	the classifier to create a model from
   * @return		the generated model
   */
  public EnsembleLibraryModel createModel(Classifier classifier) {
    EnsembleLibraryModel model = new EnsembleLibraryModel(classifier);

    return model;
  }

  /**
   * This method takes a String argument defining a classifier and
   * uses it to create a base Classifier.
   *
   * @param modelString	the classifier string
   * @return		the generated model
   */
  public EnsembleLibraryModel createModel(String modelString) {

    String[] splitString = modelString.split("\\s+");
    String className = splitString[0];

    String argString = modelString.replaceAll(splitString[0], "");
    String[] optionStrings = argString.split("\\s+");

    EnsembleLibraryModel model = null;
    try {
      model = new EnsembleLibraryModel(AbstractClassifier.forName(className,
            optionStrings));
    } catch (Exception e) {
      e.printStackTrace();
    }
    return model;
  }

  /**
   * getter for the set of models in this library
   *
   * @return		the current models
   */
  public TreeSet getModels() {
    return m_Models;
  }

  /**
   * setter for the set of models in this library
   *
   * @param models	the models to use
   */
  public void setModels(TreeSet models) {
    m_Models = models;
    m_LibraryPropertySupport.firePropertyChange(null, null, null);
  }

  /**
   * removes all models from the current library
   */
  public void clearModels() {
    m_Models.clear();
    m_LibraryPropertySupport.firePropertyChange(null, null, null);
  }

  /**
   * Loads and returns a library from the specified file
   *
   * @param selectedFile	the file to load from
   * @param dialogParent	the parent component
   * @param library		will contain the data after loading
   */
  public static void loadLibrary(File selectedFile, JComponent dialogParent,
      EnsembleLibrary library) {

    try {
      loadLibrary(selectedFile, library);
    } catch (Exception ex) {
      JOptionPane.showMessageDialog(dialogParent, "Error reading file '"
          + selectedFile.getName() + "':\n" + ex.getMessage(),
          "Load failed", JOptionPane.ERROR_MESSAGE);
      System.err.println(ex.getMessage());
    }
  }

  /**
   * This method takes a model list file and a library object as arguments and
   * Instantiates all of the models in the library list file.  It is assumed
   * that the passed library was an associated working directory and can take
   * care of creating the model objects itself.
   *
   * @param selectedFile	the file to load
   * @param library		the library
   * @throws Exception		if something goes wrong
   */
  public static void loadLibrary(File selectedFile, EnsembleLibrary library)
    throws Exception {

    //decide what type of model file list we are dealing with and
    //then load accordingly

    //deal with XML extension for xml files
    if (selectedFile.getName().toLowerCase().endsWith(
          EnsembleLibrary.XML_FILE_EXTENSION)) {

      LibrarySerialization librarySerialization;

      Vector classifiers = null;

      try {
        librarySerialization = new LibrarySerialization();
        classifiers = (Vector) librarySerialization.read(selectedFile.getPath());
      } catch (Exception e) {
        e.printStackTrace();
      }

      //library.setClassifiers(classifiers);

      for (Iterator it = classifiers.iterator(); it.hasNext();) {

        EnsembleLibraryModel model = library.createModel((Classifier) it.next());
        model.testOptions();
        library.addModel(model);
      }

      //deal with MLF extesion for flat files
    } else if (selectedFile.getName().toLowerCase().endsWith(
          EnsembleLibrary.FLAT_FILE_EXTENSION)) {

      BufferedReader reader = null;

      reader = new BufferedReader(new FileReader(selectedFile));

      String modelString;

      while ((modelString = reader.readLine()) != null) {

        EnsembleLibraryModel model = library.createModel(modelString);

        if (model != null) {
          model.testOptions();
          library.addModel(model);
        } else {
          System.err.println("Failed to create model: " + modelString);
        }
      }

      reader.close();
    }
  }

  /**
   * This method takes an XML input stream and a library object as arguments
   * and Instantiates all of the models in the stream.  It is assumed
   * that the passed library was an associated working directory and can take
   * care of creating the model objects itself.
   *
   * @param stream		the XML stream to load
   * @param library		the library
   * @throws Exception		if something goes wrong
   */
  public static void loadLibrary(InputStream stream, EnsembleLibrary library)
    throws Exception {

    Vector classifiers = null;

    try {
      LibrarySerialization librarySerialization = new LibrarySerialization();
      classifiers = (Vector) librarySerialization.read(stream);
    }
    catch (Exception e) {
      e.printStackTrace();
    }

    for (int i = 0; i < classifiers.size(); i++) {
      EnsembleLibraryModel model = library.createModel((Classifier) classifiers.get(i));
      model.testOptions();
      library.addModel(model);
    }
  }


  /**
   * Saves the given library in the specified file.  This saves only
   * the specification of the models as a model list.
   *
   * @param selectedFile	the file to save to
   * @param library		the library to save
   * @param dialogParent	the component parent
   */
  public static void saveLibrary(File selectedFile, EnsembleLibrary library,
      JComponent dialogParent) {

    //save decide what type of model file list we are dealing with and
    //then save accordingly

    //System.out.println("writing to file: "+selectedFile.getPath());

    //deal with XML extension for xml files
    if (selectedFile.getName().toLowerCase().endsWith(
          EnsembleLibrary.XML_FILE_EXTENSION)) {

      LibrarySerialization librarySerialization;

      Vector classifiers = new Vector();

      for (Iterator it = library.getModels().iterator(); it.hasNext();) {
        EnsembleLibraryModel model = (EnsembleLibraryModel) it.next();
        classifiers.add(model.getClassifier());
      }

      try {
        librarySerialization = new LibrarySerialization();
        librarySerialization.write(selectedFile.getPath(), classifiers);
      } catch (Exception e) {
        e.printStackTrace();
      }

      //deal with MLF extesion for flat files
    } else if (selectedFile.getName().toLowerCase().endsWith(
          EnsembleLibrary.FLAT_FILE_EXTENSION)) {

      Writer writer = null;
      try {
        writer = new BufferedWriter(new FileWriter(selectedFile));

        Iterator it = library.getModels().iterator();

        while (it.hasNext()) {
          EnsembleLibraryModel model = (EnsembleLibraryModel) it.next();
          writer.write(model.getStringRepresentation() + "\n");
        }

        writer.close();
      } catch (Exception ex) {
        JOptionPane.showMessageDialog(dialogParent,
            "Error writing file '" + selectedFile.getName()
            + "':\n" + ex.getMessage(), "Save failed",
            JOptionPane.ERROR_MESSAGE);
        System.err.println(ex.getMessage());
      }
    }
  }

  /**
   * Adds an object to the list of those that wish to be informed when the
   * library changes.
   *
   * @param listener a new listener to add to the list
   */
  public void addPropertyChangeListener(PropertyChangeListener listener) {

    if (m_LibraryPropertySupport != null) {
      m_LibraryPropertySupport.addPropertyChangeListener(listener);

    }
  }

  /**
   * Returns the revision string.
   *
   * @return		the revision
   */
  public String getRevision() {
    return RevisionUtils.extract("$Revision: 6041 $");
  }
}
