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 Kantenkonsistenz ü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 ACSolver extends ConsistencySolver {

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

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

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

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

  /**
   * Stellt Kantenkonsistenz für einen primitiven Ausdruck, d.h. für ein
   * einzelnes Constraint, her. Liefert <code>true</code>, wenn Werte aus einer
   * Domäne entfernt wurden, <code>false</code> wenn nicht.
   * @param expr Expression
   * @return boolean
   */
  private boolean arcConsistentPrimitive(Expression expr) {
    boolean removed = false;
    boolean satisfiable = false;
    List variablesList = expr.getVariables();
    // Nur auf binäre Constraints anwenden:
    if (variablesList.size() == 2) {
      // Variablen und Domänen sichern:
      String variable1 = (String)variablesList.get(0);
      String variable2 = (String)variablesList.get(1);
      Domain domain1 = expr.getDomain(variable1);
      Domain domain2 = expr.getDomain(variable2);
      Iterator it1 = domain1.getElements().iterator();
      Iterator it2;
      DomainElement value1, value2;
      // Domäne der ersten Variable durchgehen:
      while (it1.hasNext()) {
        satisfiable = false;
        this.logger.debug("domain1: "+domain1);
        value1 = (DomainElement)it1.next();
        expr.set(variable1, value1);
        it2 = domain2.getElements().iterator();
        // Domäne der zweiten Variable durchgehen:
        while ((it2.hasNext()) && (!satisfiable)) {
          this.logger.debug("domain2: "+domain2);
          value2 = (DomainElement)it2.next();
          expr.set(variable2, value2);
          // Ausdruck mit den gesetzten Variablen auf Erfüllbarkeit testen:
          this.logger.debug("expr: "+expr);
          if (expr.satisfiable()) {
            this.logger.debug("satisfiable");
            satisfiable = true;
          }
        }
        // Wenn kein passender Wert gefunden wurde, den Wert der ersten Variable
        // aus deren Domäne entfernen:
        if (!satisfiable) {
          this.logger.info("Entferne Wert '"+value1+"' aus Domäne von '"+variable1+"'.");
          domain1.deleteElement(value1);
          removed = true;
        }
      }
      // Die Domäne der zweiten Variable nur checken, falls die Domäne der
      // ersten Variable nicht bereits leer ist:
      if (!domain1.isEmpty()) {
        it2 = domain2.getElements().iterator();
        // Domäne der zweiten Variable durchgehen:
        while (it2.hasNext()) {
          satisfiable = false;
          this.logger.debug("domain2: "+domain2);
          value2 = (DomainElement)it2.next();
          expr.set(variable2, value2);
          it1 = domain1.getElements().iterator();
          // Domäne der zweiten Variable durchgehen:
          while ((it1.hasNext()) && (!satisfiable)) {
            this.logger.debug("domain1: "+domain1);
            value1 = (DomainElement)it1.next();
            expr.set(variable1, value1);
            // Ausdruck mit den gesetzten Variablen auf Erfüllbarkeit testen:
            this.logger.debug("expr: "+expr);
            if (expr.satisfiable()) {
              this.logger.debug("satisfiable");
              satisfiable = true;
            }
          }
          // Wenn kein passender Wert gefunden wurde, den Wert der zweiten
          // Variable aus deren Domäne entfernen:
          if (!satisfiable) {
            this.logger.info("Entferne Wert '"+value2+"' aus Domäne von '"+variable2+"'.");
            domain2.deleteElement(value2);
            removed = true;
          }
        }
      }
    }
    return removed;
  }

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

  /**
   * Einfacher Algorithmus zum Herstellen von Kantenkonsistenz. Liefert
   * <code>true</code>, wenn Wertebereichseinschränkungen vorgenommen wurden,
   * <code>false</code> wenn nicht.
   * @param expr Expression
   * @return boolean
   * @throws InconsistencyException
   */
  public boolean arcSolve(Expression expr) throws InconsistencyException {
    boolean domainModification;
    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.
    do {
      domainModification = false;
      // Alle primitiven Constraint-Ausdrücke durchgehen:
      for (int i = 0; i < primitiveExpressions.size(); i++) {
        primitiveExpr = (Expression)primitiveExpressions.get(i);
        // Primitives Constraint auf Konsistenz prüfen:
        if (arcConsistentPrimitive(primitiveExpr)) {
          domainModification = true;
        }
        // Prüfen, ob eine Domäne leer ist (keine Kantenkonsistenz herstellbar):
        if (primitiveExpr.hasFalseDomain()) {
          throw new InconsistencyException();
        }
      }
    } while (domainModification);
    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 arcSolve(constraintNet.getSubproblem());
  }

}
