package yacs.solver.fdsolver.search;

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

import java.util.*;

import org.apache.log4j.*;

/**
 * Ein rekursiver Backtracking"=Solver für finite Domänen in Anlehnung
 * an Marriott u. Stuckey (1999, S. 90). In dieser Implementierung
 * allerdings wird nicht nur <code>true</code>/<code>false</code>
 * zurückgegeben, sondern ggf. eine Lösung generiert. Dies ist die
 * erste Lösung, die durch den Suchalgorithmus aufgefunden wird. Der
 * Solver eignet sich in erster Linie für die Überprüfung, ob ein
 * Problem inkonsistent ist oder nicht.</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 SingleSolutionBTSolver extends SearchSolver {

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

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

  /**
   * Das zu verarbeitende Constraint-Netz (wird aufgrund des rekursiven
   * Suchalgorithmus an dieser Stelle abgelegt).
   */
  private ConstraintNet constraintNet;

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

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

  /**
   * Dieser Backtracking Solver arbeitet rekursiv. Für jede Rekursion muss
   * "by-value" ein neuer Expression übergeben werden, daher erfolgt hier die
   * Nutzung der Methode <code>cloneDeep()</code> innerhalb der
   * <code>Expression</code>-Objekte. Liefert <code>true</code>, wenn eine
   * Lösung gefunden wurde, <code>false</code> wenn nicht.
   * @param expr Expression
   * @param solutions Solutions
   * @return boolean
   */
  private boolean backSolve(Expression expr) {
    String actVariableName;
    // Wenn keine unbelegten Variablen mehr vorhanden sind:
    if (!expr.unsetVariablesLeft()) {
      // Wenn das Ergebnis von "partialSatisfiable" positiv:
      boolean partialResult = partialSatisfiable(expr);
      if (partialResult) {
        // Eine Lösung aus der bestehenden Belegung generieren:
        Solution solution = this.generateSolution(this.constraintNet, expr);
        // Lösungsobjekt dem "Lösungs-Container" des Teilproblems hinzuzufügen:
        this.constraintNet.addSolution(solution);
      }
      // Ergebnis von "partialSatisfiable" zurückgeben:
      return partialResult;
    } else {
      // Die nächste unbelegte Variable holen:
      Expression expr1;
      NumericFDElement nextValue;
      List unsetVariables = expr.getUnsetVariables();
      actVariableName = (String)unsetVariables.get(0);
      Domain actDomain = expr.getDomain(actVariableName);
      Iterator it = actDomain.getElements().iterator();
      while (it.hasNext()) {
        expr1 = expr.cloneDeep();
        nextValue = (NumericFDElement)it.next();
        expr1.set(actVariableName, nextValue);
        if (this.partialSatisfiable(expr1)) {
          if (this.backSolve(expr1)) {
            this.logger.debug("backSolve: [true]");
            return true;
          }
        }
      }
      this.logger.debug("backSolve: [false]");
      return false;
    }
  }

  /**
   * Prüft ob sämtliche primitive Constraints mit der aktuellen Belegung
   * erfüllbar sind. Wenn ja wird <code>true</code> zurückgegeben. Wenn es noch
   * unbelegte Variablen gibt ebenfalls, ansonsten <code>false</code>.
   * @param expr Expression
   * @return boolean
   */
  private boolean partialSatisfiable(Expression expr) {
    // Wegen Rekursion und cloneDeep() muss der aktuelle Constraint-Ausdruck
    // verwendet werden (anstatt this.constraintNet.getConstraints()):
    List exprList = expr.getPrimitiveExpressions();
    for (int i = 0; i < exprList.size(); i++) {
      // Nur wenn keine unbelegten Variablen vorhanden sind:
      if (!((Expression)exprList.get(i)).unsetVariablesLeft()) {
        // Wenn mit aktueller Belegung nicht erfüllbar, "false" zurückgeben:
        if (!((Expression)exprList.get(i)).satisfiable()) {
          this.logger.info("partialSatisfiable(): " + expr + " [false]");
          return false;
        }
      }
    }
    // ... ansonsten "true" zurückgeben:
    this.logger.info("partialSatisfiable(): " + expr + " [true]");
    return true;
  }

  /**
   * Generiert aus der aktuellen Belegung des übergebenen
   * <code>Expression</code> eine Lösung für das Constraint-Netz. Der
   * <code>Expression</code> muss identisch mit dem Subproblem des
   * Constraint-Netzes sein, kann allerdings eine davon abweichende
   * Wertebelegung der Constraint-Variablen beinhalten. Die generierte Lösung
   * wird nicht in das Constraint-Netz eingetragen sondern zurückgegeben.
   * @param constraintNet ConstraintNet
   * @param expr Expression
   * @return Solution
   */
  private Solution generateSolution(ConstraintNet constraintNet, Expression expr) {
    String actVariableName;
    NumericFDElement actVariableValue;
    Solution solution = new Solution();
    Iterator it = constraintNet.getVariables().iterator();
    // Sämtliche Variablen des Expression durchgehen:
    while (it.hasNext()) {
      actVariableName = (String)it.next();
      // Die Belegung auslesen:
      Domain actVariableDomain = constraintNet.getDomain(actVariableName);
      actVariableValue = (NumericFDElement)actVariableDomain.getElement(expr.get(actVariableName));
      // Anm.: Um zu verhindern, dass  Lösungen doppelt eingetragen werden
      // (z.B. bei wiederholten Iterationen des Lösungsprozesses durch den
      // YCM) muss in den Lösungs-Container die Originalreferenz des
      // ursprünglichen Domänenelements eingetragen werden! Dies ermöglicht
      // der "equals()"-Methode in "yacs.net.Solution" einen entsprechenden
      // Vergleich positiv zu beantworten (siehe auch "addSolution()" und
      // "containsSolution()" in "yacs.net.Solutions").
      //
      // Variable und Belegung dem Lösungsobjekt bekannt machen:
      solution.addVariable(actVariableName, actVariableValue);
    }
    return solution;
  }

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

  /**
   * 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 search(ConstraintNet constraintNet) throws InconsistencyException {
    this.constraintNet = constraintNet;
    if (!backSolve(constraintNet.getSubproblem())) {
      throw new InconsistencyException();
    }
    // Dieser Solver ermittelt ausschließlich Lösungen und modifiziert das
    // Constraint-Netz ansonsten nicht:
    return false;
  }

}
