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

/* 
 *    LiteralSet.java
 *    Copyright (C) 2003 Peter A. Flach, Nicolas Lachiche
 *
 *    Thanks to Amelie Deltour for porting the original C code to Java
 *    and integrating it into Weka.
 */

package weka.associations.tertius;

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

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;

/**
 * Class representing a set of literals, being either the body or the head
 * of a rule.
 *
 * @author <a href="mailto:adeltour@netcourrier.com">Amelie Deltour</a>
 * @version $Revision: 1.7 $
 */

public abstract class LiteralSet
  implements Serializable, Cloneable, RevisionHandler {
  
  /** for serialization */
  private static final long serialVersionUID = 6094536488654503152L;
  
  /** Literals contained in this set. */
  private ArrayList m_literals;

  /** Last literal added to this set. */
  private Literal m_lastLiteral;
  
  /** Number of instances in the data the set deals with. */
  private int m_numInstances;

  /** Set of counter-instances of this part of the rule. */
  private ArrayList m_counterInstances;
  /* For a body, counter-instances are the instances satisfying the body.
   * For a head, conter-instances are the instances satisfying the negation. */

  /** Counter for the number of counter-instances. */
  private int m_counter;

  /** 
   * Type of properties expressed in this set 
   * (individual or parts properties).
   */
  private int m_type;
  
  /**
   * Constructor for a set that does not store its counter-instances.
   */
  public LiteralSet() {

    m_literals = new ArrayList();
    m_lastLiteral = null;
    m_counterInstances = null;
    m_type = -1;
  }

  /**
   * Constructor initializing the set of counter-instances to all the instances.
   *
   * @param instances The dataset.
   */
  public LiteralSet(Instances instances) {

    this();
    m_numInstances = instances.numInstances();
    m_counterInstances = new ArrayList(m_numInstances);
    Enumeration enu = instances.enumerateInstances();
    while (enu.hasMoreElements()) {
      m_counterInstances.add(enu.nextElement());
    }
  }

  /**
   * Returns a shallow copy of this set.
   * The structured is copied but the literals themselves are not copied.
   *
   * @return A copy of this LiteralSet.
   */
  public Object clone() {

    Object result = null;
    try {
      result = super.clone();
      /* Clone the set of literals, but not the literals themselves. */
      ((LiteralSet) result).m_literals = (ArrayList) m_literals.clone();
      if (m_counterInstances != null) {
	/* Clone the set of instances, but not the instances themselves. */
	((LiteralSet) result).m_counterInstances 
	  = (ArrayList) m_counterInstances.clone();
      }
    } catch(Exception e) {
      /* An exception is not supposed to happen here. */
      e.printStackTrace();
      System.exit(0);
    }
    return result;
  }

  /**
   * Update the number of counter-instances of this set in the dataset.
   * This method should be used is the set does not store its counter-instances.
   *
   * @param instances The dataset.
   */
  public void upDate(Instances instances) {

    Enumeration enu = instances.enumerateInstances();
    m_numInstances = instances.numInstances();
    m_counter = 0;
    while (enu.hasMoreElements()) {
      if (this.counterInstance((Instance) enu.nextElement())) {
	m_counter++;
      }
    }
  }

  /**
   * Get the number of counter-instances of this LiteralSet.
   *
   * @return The number of counter-instances.
   */
  public int getCounterInstancesNumber() {

    if (m_counterInstances != null) {
      return m_counterInstances.size();
    } else {
      return m_counter;
    }
  }

  /**
   * Get the frequency of counter-instances of this LiteralSet in the data.
   *
   * @return The frequency of counter-instances.
   */
  public double getCounterInstancesFrequency() {

    return (double) getCounterInstancesNumber() / (double) m_numInstances;
  }

  /**
   * Test if this LiteralSet has more counter-instances than the threshold.
   *
   * @param minFrequency The frequency threshold.
   * @return True if there are more counter-instances than the threshold.
   */
  public boolean overFrequencyThreshold(double minFrequency) {

    return getCounterInstancesFrequency() >= minFrequency;
  }

  /**
   * Test if all the intances are counter-instances.
   *
   * @return True if all the instances are counter-instances.
   */
  public boolean hasMaxCounterInstances() {

    return getCounterInstancesNumber() == m_numInstances;
  }

  /**
   * Add a Literal to this set.
   *
   * @param element The element to add.
   */
  public void addElement(Literal element) {

    m_literals.add(element);
    /* Update the last literal. */
    m_lastLiteral = element;
    /* Update the type in the case of individual-based learning. */
    if (element instanceof IndividualLiteral) {
      int type = ((IndividualLiteral) element).getType();
      if (type > m_type) {
	m_type = type;
      }
    }
    /* Update the set of counter-instances. */
    if (m_counterInstances != null) {
      for (int i = m_counterInstances.size() - 1; i >= 0; i--) {
	Instance current = (Instance) m_counterInstances.get(i);
	if (!canKeep(current, element)) {
	  m_counterInstances.remove(i);
	}
      }
    }
  }

  /**
   * Test if this set is empty.
   *
   * @return True if the set is empty.
   */
  public final boolean isEmpty() {

    return m_literals.size() == 0;
  }

  /**
   * Give the number of literals in this set.
   *
   * @return The number of literals.
   */
  public final int numLiterals() {

    return m_literals.size();
  }

  /**
   * Enumerate the literals contained in this set.
   *
   * @return An Iterator for the literals.
   */
  public final Iterator enumerateLiterals() {

    return m_literals.iterator();
  }

  /**
   * Give the last literal added to this set.
   *
   * @return The last literal added.
   */
  public Literal getLastLiteral() {

    return m_lastLiteral;
  }

  /**
   * Test if the negation of this LiteralSet is included in another LiteralSet.
   *
   * @param otherSet The other LiteralSet.
   * @return True if the negation of this LiteralSet is included in 
   * the other LiteralSet.
   */
  public boolean negationIncludedIn(LiteralSet otherSet) {

    Iterator iter = this.enumerateLiterals();
    while (iter.hasNext()) {
      Literal current = (Literal) iter.next();
      if (!otherSet.contains(current.getNegation())) {
	return false;
      }
    }      
    return true;
  }

  /**
   * Test if this LiteralSet contains a given Literal.
   *
   * @param lit The literal that is looked for.
   * @return True if this literal is contained in this LiteralSet.
   */
  public boolean contains(Literal lit) {

    return m_literals.contains(lit);
  }

  /**
   * Give the type of properties in this set (individual or part properties).
   */
  public int getType() {
	
    return m_type;
  }

  /**
   * Test if an individual instance, given a part instance of this individual,
   * is a counter-instance of this LiteralSet.
   *
   * @param individual The individual instance.
   * @param part The part instance.
   * @return True if the individual is a counter-instance.
   */
  public boolean counterInstance(Instance individual, Instance part) {
    Iterator iter = this.enumerateLiterals();
    while (iter.hasNext()) {
      IndividualLiteral current = (IndividualLiteral) iter.next();
      if (current.getType() == IndividualLiteral.INDIVIDUAL_PROPERTY
	  && !canKeep(individual, current))  {
	return false;
      } else if (current.getType() == IndividualLiteral.PART_PROPERTY
		 && !canKeep(part, current)) {
	return false;
      }
    }
    return true;
  }

  /**
   * Test if an instance is a counter-instance of this LiteralSet.
   *
   * @param instance The instance to test.
   * @return True if the instance is a counter-instance.
   */
  public boolean counterInstance(Instance instance) {
    if ((instance instanceof IndividualInstance)
	&& (m_type == IndividualLiteral.PART_PROPERTY)) {
      /* In the case of an individual instance, all the parts of the individual
       * have to be considered.
       * Part properties can be found in the body only, so here we test for
       * an instance satisfying the set.
       * It satisfies the set if there exists a part satisfying the set.
       */
      Enumeration enu 
	= ((IndividualInstance) instance).getParts().enumerateInstances();
      while (enu.hasMoreElements()) {
	if (counterInstance(instance, (Instance) enu.nextElement())) {
	  return true;
	}
      }
      return false;
    } else {
      /* Usual case. */
      Iterator iter = this.enumerateLiterals();
      while (iter.hasNext()) {
	Literal current = (Literal) iter.next();
	if (!canKeep(instance, current)) {
	  return false;
	}
      }
      return true;
    }
  }

  /**
   * Test if an instance can be kept as a counter-instance,
   * given a new literal.
   *
   * @param instance The instance to test.
   * @param newLit The new literal.
   * @return True if the instance is still a counter-instance.
   */
  public abstract boolean canKeep(Instance instance, Literal newLit);

  /**
   * Test if this LiteralSet is included in a rule.
   *
   * @param otherRule The rule to test.
   * @return True if this set of literals is included in the rule.
   */
  public abstract boolean isIncludedIn(Rule otherRule);
  
  /**
   * Gives a String representation for this set of literals.
   */
  public abstract String toString();  
}







