package yacs.solver.intervalsolver.consistency;

import yacs.exceptions.*;
import yacs.net.*;
import yacs.parser.*;
import yacs.solver.*;

import java.util.*;

import org.apache.log4j.Logger;

/**
 * <p>Einfacher Algorithmus zum Herstellen von Hull-Konsistenz für Variablen mit
 * Wertebereichen aus reellwertigen Intervallen. Der Algorithmus orientiert sich
 * am Waltz-Filteralgorithmus und den Beschreibungen von Davis (1987), Hyvönen
 * (1992) und Lhomme (1993). Eine Zerlegung der Constraints findet nicht statt,
 * außerdem lassen sich ausschließlich Constraints verarbeiten, die bereits
 *  vollständig als "solution functions" (vgl. Hyvönen 1992) bzw. als
 * "Projektionen" vorliegen (vgl. Lhomme 1993).</p>
 *
 * <p>Copyright (C) 2005  Wolfgang Runte
 * <br><br>
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * <br><br>
 * This library 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
 * Lesser General Public License for more details.
 * <br><br>
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA</p>
 *
 * @author Wolfgang Runte (woru@tzi.org)
 * @version YACS 0.1.1
 */
public class HullConsistencySolver extends ConsistencySolver {

// ********** private attributs **********

  /** Logger aktivieren: */
  private static Logger logger = Logger.getLogger("yacs.solver.intervalsolver.consistency.HullConsistencySolver");

// ********** public attributs **********

// ********** private methods **********

  /**
   * Gibt eine Liste mit Variablen (als <code>Strings</code>) zurück, die von
   * Wertebereichsänderungen betroffen sind. In dieser einfachen Version (es
   * sind ausschließlich Constraints erlaubt, die eine "solution function"
   * darstellen), wird ausschließlich eine Liste mit einer einzigen Variable
   * zurückgegeben.
   * @param expr Expression
   * @return List
   */
  private List revise(Expression expr) throws InconsistencyException {
    String curVariableName;
    BinaryOperator curConstraint;
    List changed = new LinkedList();
    // Sicherstellen, dass ausschließlich primitive Constraint-Ausdrücke
    // verarbeitet werden:
    Iterator it = expr.getPrimitiveExpressions().iterator();
    // Alle primitiven Constraints durchgehen:
    while (it.hasNext()) {
      this.logger.debug("revise(): expr(1): " + expr);
      curConstraint = (BinaryOperator)it.next();
      curVariableName = curConstraint.refine();
      this.logger.debug("revise(): expr(2): " + expr);
      if (curConstraint.hasFalseDomain()) {
        throw new InconsistencyException();
      }
      if (curVariableName != null) {
        changed.add(curVariableName);
        this.logger.info("revise(): added '" + curVariableName + "', modified domain: " + expr.getDomain(curVariableName));
      }
    }
    return changed;
  }

  /**
   * Liefert eine Liste mit den primitiven Constraints (Instanzen der Klasse
   * <code>Expression</code>), die eine Variable mit dem übergebenen Namen
   * enthalten.
   * @param primitiveConstraints List
   * @param variableName String
   * @return List
   */
  private List containsChanged(List primitiveConstraints, String variableName) {
    Expression curPrimitiveConstraint;
    List containsChangedList = new LinkedList();
    Iterator it = primitiveConstraints.iterator();
    // Alle primitiven Constraints durchgehen:
    while (it.hasNext()) {
      curPrimitiveConstraint = (Expression)it.next();
      // Wenn das aktuelle Constraint die übergebene Variable enthält:
      if (curPrimitiveConstraint.containsVariable(variableName)) {
        // ... zur Rückgabeliste hinzufügen:
        containsChangedList.add(curPrimitiveConstraint);
      }
    }
    return containsChangedList;
  }

// ********** public methods **********

  /**
   * Einfacher Solver zum Herstellen von Hull- bzw. 2B-Konsistenz, basierend auf
   * dem Waltz-Filteralgorithmus (in diesem Fall für Domänen mit reellwertigen
   * Intervallen).
   * @param expr Expression
   * @return boolean
   */
  public boolean hullSolve(Expression expr) throws InconsistencyException {
    this.logger.info("hullSolve [start]: " + expr);
    boolean domainModification = false;
    Iterator changed, it;
    List containsChangedVariableList;
    Expression curConstraint;
    Expression containsChangedVariable;
    String changedVariableName;
    // Primitive Constraints holen:
    List primitiveConstraints = expr.getPrimitiveExpressions();
    List workingList = expr.getPrimitiveExpressions();
    // Alle primitiven Constraints in der "Arbeitsliste" durchgehen:
    while (workingList.size() > 0) {
      // Erste Element holen:
      curConstraint = (Expression)workingList.get(0);
      // ... und löschen:
      workingList.remove(0);
      // Prüfen, ob mögliche Wertebereichseinschränkungen Auswirkungen auf
      // andere Variablen haben:
      changed = this.revise(curConstraint).iterator();
      // Für jede Variable, die von Änderungen betroffen ist:
      while (changed.hasNext()) {
        changedVariableName = (String)changed.next();
        // Sämtliche Constraints holen, die eine Variable mit geändertem
        // Wertebereich enthalten:
        containsChangedVariableList = this.containsChanged(primitiveConstraints, changedVariableName);
        // Alle gefundenen, bis auf das aktuelle (auslösende) Constraint, ...
        containsChangedVariableList.remove(curConstraint);
        // ... zur "Arbeitsliste" hinzufügen:
        if (!containsChangedVariableList.isEmpty()) {
          it = containsChangedVariableList.iterator();
          // Nur diejenigen hinzufügen, die nicht bereits enthalten sind:
          while (it.hasNext()) {
            containsChangedVariable = (Expression)it.next();
            if (!workingList.contains(containsChangedVariable)) {
              workingList.add(containsChangedVariable);
              this.logger.debug("hullSolve [added constraint to working list]: " + containsChangedVariable);
            }
          }
        }
        domainModification = true;
      }
      this.logger.info("hullSolve [iteration]: " + expr);
    }
    return domainModification;
  }

  /**
   * Benötigt als Eingabe ein Constraint-Netz. Liefert <code>true</code>, wenn
   * Wertebereichseinschränkungen vorgenommen wurden, <code>false</code> wenn
   * nicht.
   * @param constraintNet ConstraintNet
   * @return boolean
   */
  public boolean propagate(ConstraintNet constraintNet) throws InconsistencyException {
    return hullSolve(constraintNet.getSubproblem());
  }

}
