package yacs.solver.fdsolver.consistency;

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

import java.util.*;

import org.apache.log4j.*;

/**
 * <p>Einfacher Algorithmus zum Herstellen von Knotenkonsistenz über finite
 * Domänen nach Marriott und Stuckey (1999, S. 94).</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 NCSolver extends ConsistencySolver {

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

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

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

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

  /**
   * Stellt Knotenkonsistenz für einen primitiven Constraint-Ausdruck her.
   * Liefert <code>true</code>, wenn Werte aus der Domäne entfernt wurden,
   * <code>false</code> wenn nicht.
   * @param expr Expression
   * @return boolean
   */
  private boolean nodeConsistentPrimitive(Expression expr) {
    String variable;
    Domain domain;
    DomainElement value;
    boolean removed = false;
    List variablesList = expr.getVariables();
    // Nur auf unäre Constraints anwenden:
    if (variablesList.size() == 1) {
      // Variablen und Domänen sichern:
      variable = (String)variablesList.get(0);
      domain = expr.getDomain(variable);
      Iterator it = domain.getElements().iterator();
      // Domäne der Variable durchgehen:
      while (it.hasNext()) {
        this.logger.debug("domain: "+domain);
        value = (DomainElement)it.next();
        expr.set(variable, value);
        // Ausdruck mit der gesetzten Variablen auf Erfüllbarkeit testen:
        this.logger.debug("expr: "+expr);
        if (expr.satisfiable()) {
          this.logger.debug("satisfiable");
        } else {
          // Wenn kein passender Wert gefunden wurde, den Wert der Variable
          // aus deren Domäne entfernen:
          this.logger.info("Entferne Wert '"+value+"' aus Domäne von '"+variable+"'.");
          domain.deleteElement(value);
          removed = true;
        }
      }
    }
    return removed;
  }

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

  /**
   * Eine Solver der Knotenkonsistenz herstellt, d.h. es werden alle unären
   * Constraints auf konsistente Wertebereiche überprüft und selbige ggf. um
   * unzulässige Werte reduziert. Liefert <code>true</code>, wenn
   * Wertebereichseinschränkungen vorgenommen wurden, <code>false</code> wenn
   * nicht.
   * @param expr Expression
   * @return boolean
   * @throws InconsistencyException
   */
  public boolean nodeSolve(Expression expr) throws InconsistencyException {
    boolean domainModification = false;
    Expression primitiveExpr;
    // Liste der (primitiven) Constraints bzw. Constraint-Ausdrücke holen:
    List primitiveExpressions = expr.getPrimitiveExpressions();
    // (Anm.: Anstatt an dieser Stelle die Methode "getConstraints()" aus der
    // Klasse "ConstraintNet" zu verwenden, hat der obige Aufruf den Vorteil
    // unabhängig von einem bestimmten Constraint-Netz zu sein. Bestehende
    // Konsistenzalgorithmen können dadurch z.B. auch von Suchalgorithmen
    // als "look-ahead"-Mechanismus für bestimmte Teilprobleme ("Expressions")
    // aufgerufen werden.
    for (int i = 0; i < primitiveExpressions.size(); i++) {
      primitiveExpr = (Expression)primitiveExpressions.get(i);
      // Primitives Constraint auf Konsistenz prüfen:
      domainModification |= this.nodeConsistentPrimitive(primitiveExpr);
    }
    // Prüfen, ob eine Domäne leer ist (keine Knotenkonsistenz herstellbar):
    if (expr.hasFalseDomain()) {
      throw new InconsistencyException();
    }
    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
   * @throws InconsistencyException
   */
  public boolean propagate(ConstraintNet constraintNet) throws InconsistencyException {
    return nodeSolve(constraintNet.getSubproblem());
  }

}
