/*
 *    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.
 */

/*
 * Element.java
 * Copyright (C) 2007 Sebastian Beer
 *
 */

package weka.associations.gsp;

import weka.core.FastVector;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.RevisionHandler;
import weka.core.RevisionUtils;

import java.io.Serializable;

/**
 * Class representing an Element, i.e., a set of events/items.
 * 
 * @author  Sebastian Beer
 * @version $Revision: 1.2 $
 */
public class Element
  implements Cloneable, Serializable, RevisionHandler {

  /** for serialization */
  private static final long serialVersionUID = -7900701276019516371L;
  
  /** events/items stored as an array of ints */
  protected int[] m_Events;

  /**
   * Constructor
   */
  public Element() {
  }

  /**
   * Constructor accepting an initial size of the events Array as parameter.
   * 
   * @param size 	the size
   */
  public Element(int size) {
    m_Events = new int[size];
  }

  /**
   * Returns all events of the given data set as Elements containing a single 
   * event. The order of events is determined by the header information of 
   * the corresponding ARFF file.
   * 
   * @param instances 	the data set
   * @return 		the set of 1-Elements
   */
  public static FastVector getOneElements (Instances instances) {
    FastVector setOfOneElements = new FastVector();
    Element curElement;

    for (int i = 0; i < instances.numAttributes(); i++) {
      for (int j = 0; j < instances.attribute(i).numValues(); j++) {
	curElement = new Element();
	curElement.setEvents(new int [instances.numAttributes()]);
	for (int k = 0; k < instances.numAttributes(); k++) {
	  curElement.getEvents()[k] = -1;
	}
	curElement.getEvents()[i] = j;
	setOfOneElements.addElement(curElement);
      }
    }
    return setOfOneElements;
  }

  /**
   * Merges two Elements into one.
   * 
   * @param element1 	first Element
   * @param element2 	second Element
   * @return 		the merged Element
   */
  public static Element merge(Element element1, Element element2) {
    int[] element1Events = element1.getEvents();
    int[] element2Events = element2.getEvents();
    Element resultElement = new Element(element1Events.length);
    int[] resultEvents = resultElement.getEvents();

    for (int i = 0; i < element1Events.length; i++) {
      if (element2Events[i] > -1) {
	resultEvents[i] = element2Events[i];
      } else {
	resultEvents[i] = element1Events[i];
      }
    }
    resultElement.setEvents(resultEvents);

    return resultElement;
  }

  /**
   * Returns a deep clone of an Element.
   * 
   * @return 		the cloned Element
   */
  public Element clone() {
    try {
      Element clone = (Element) super.clone();
      int[] cloneEvents = new int[m_Events.length];

      for (int i = 0; i < m_Events.length; i++) {
	cloneEvents[i] = m_Events[i];
      }
      clone.setEvents(cloneEvents);

      return clone;
    } catch (CloneNotSupportedException exc) {
      exc.printStackTrace();
    }
    return null;
  }

  /**
   * Checks if an Element contains over one event.
   * 
   * @return 		true, if the Element contains over one event, else false
   */
  public boolean containsOverOneEvent() {
    int numEvents = 0;
    for (int i = 0; i < m_Events.length; i++) {
      if (m_Events[i] > -1) {
	numEvents++;
      }
      if (numEvents == 2) {
	return true;
      }
    }
    return false;
  }

  /**
   * Deletes the first or last event of an Element.
   * 
   * @param position 	the position of the event to be deleted (first or last)
   */
  public void deleteEvent(String position) {
    if (position.equals("first")) {
      //delete first event
      for (int i = 0; i < m_Events.length; i++) {
	if (m_Events[i] > -1) {
	  m_Events[i] = -1;
	  break;
	}
      }
    }
    if (position.equals("last")) {
      //delete last event
      for (int i = m_Events.length-1; i >= 0; i--) {
	if (m_Events[i] > -1) {
	  m_Events[i] = -1;
	  break;
	}
      }
    }
  }

  /**
   * Checks if two Elements are equal.
   * 
   * @return 		true, if the two Elements are equal, else false
   */
  public boolean equals(Object obj) {
    Element element2 = (Element) obj;

    for (int i=0; i < m_Events.length; i++) {
      if (!(m_Events[i] == element2.getEvents()[i])) {
	return false;
      }
    }
    return true;
  }

  /**
   * Returns the events Array of an Element.
   * 
   * @return 		the events Array
   */
  public int[] getEvents() {
    return m_Events;
  }

  /**
   * Checks if an Element is contained by a given Instance.
   * 
   * @param instance 	the given Instance
   * @return 		true, if the Instance contains the Element, else false
   */
  public boolean isContainedBy(Instance instance) {
    for (int i=0; i < instance.numAttributes(); i++) {
      if (m_Events[i] > -1) {
	if (instance.isMissing(i)) {
	  return false;
	}
	if (m_Events[i] != (int) instance.value(i)) {
	  return false;
	}
      }
    }
    return true;
  }

  /**
   * Checks if the Element contains any events.
   * 
   * @return 		true, if the Element contains no event, else false 
   */
  public boolean isEmpty() {
    for (int i=0; i < m_Events.length; i++) {
      if (m_Events[i] > -1) {
	return false;
      }
    }
    return true;
  }

  /**
   * Sets the events Array of an Element.
   * 
   * @param events 	the events Array to set
   */
  protected void setEvents(int[] events) {
    m_Events = events;
  }

  /**
   * Returns a String representation of an Element where the numeric value 
   * of each event/item is represented by its respective nominal value.
   * 
   * @param dataSet 	the corresponding data set containing the header information
   * @return 		the String representation
   */
  public String toNominalString(Instances dataSet) {
    StringBuffer result = new StringBuffer();
    int addedValues = 0;

    result.append("{");

    for (int i=0; i < m_Events.length; i++) {
      if (m_Events[i] > -1) {			
	result.append(dataSet.attribute(i).value(m_Events[i]) + ",");
	addedValues++;
      }
    }
    result.deleteCharAt(result.length()-1);
    result.append("}");

    return result.toString();
  }

  /**
   * Returns a String representation of an Element.
   * 
   * @return 		the String representation
   */
  public String toString() {
    String result = "";

    result += "{";

    for (int i=0; i < m_Events.length; i++) {
      result += m_Events[i];
      if (i+1 < m_Events.length) {
	result += ",";
      }
    }
    result += "}";

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