package yacs.parser;

import yacs.domain.*;

import java.util.*;

/**
 * <p>Klasse zur Repräsentation einer numerischen Variablen innerhalb einer
 * rekursiven Struktur zur Repräsentation eines algebraischen
 * Constraint-Problems (Dokumentation siehe <code>Expression</code>).</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 Variable extends Expression {

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

  /** Der Name der Variablen. */
  private String name;

  /** Die zur Variablen zugehörige Domäne. */
  private Domain domain;

  /** Aktuelle Belegung der Variable. */
  private DomainElement element;

  /** Singleton */
  private boolean signleton;

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

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

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

  /**
   * Konstruktor -- Instantiiert die Variable mit ihrem Namen.
   * @param variableName String
   */
  public Variable(String variableName) {
    this.name = variableName;
  }

  /**
   * Konstruktor -- Instantiiert die Variable mit ihrem Namen und ihrer Domäne.
   * @param variableName String
   * @param domain Domain
   */
  public Variable(String variableName, Domain domain) {
    this(variableName);
    this.setDomain(variableName, domain);
  }

  /**
   * Konstruktor -- Instantiiert die Variable mit ihrem Namen, ihrer Domäne und
   * einer aktuellen Belegung.
   * @param variableName String
   * @param domain Domain
   * @param element DomainElement
   */
  public Variable(String variableName, Domain domain, DomainElement element) {
    this(variableName, domain);
    this.setValue(element);
  }

  /**
   * Liefert den Namen der Variablen.
   * @return String
   */
  public String getName() {
    return this.name;
  }

  /**
   * Liefert die zugehörige Domäne der Variablen. Diese Methode ermöglicht die
   * direkte Abfrage (anstatt der <code>getDomain(String)</code>-Methode) unter
   * Umgehung der rekursiven Struktur, wenn sichergestellt ist, dass das
   * vorhandene Objekt eine Instanz der Klasse <code>yacs.parser.Variable</code>
   * ist.
   * @return Domain
   */
  public Domain getDomain() {
    return this.domain;
  }

  /**
   * Belegt diese Variable mit einem Element aus ihrer Domäne. Liefert
   * <code>true</code>, wenn dies erfolgreich war, <code>false</code> wenn
   * nicht (Wert des Elements in der Domäne nicht vorhanden). Diese Methode
   * ermöglicht den direkten Aufruf (anstatt der <code>set()</code>-Methode)
   * unter Umgehung der rekursiven Struktur, wenn sichergestellt ist, dass das
   * vorhandene Objekt eine Instanz der Klasse <code>yacs.parser.Variable</code>
   * ist.
   * @param element DomainElement
   * @return boolean
   */
  public boolean setValue(DomainElement element) {
    if ((element != null) && (!this.domain.containsElement(element))) {
      System.err.println("Error in Variable.setValue(): trying to assign value '" + element + "' to variable '"+this.getName()+"' not existing in its actual domain: " + this.domain);
      return false;
    }
    this.element = element;
    return true;
  }

  /**
   * Liefert die Wertebelegung der Variable. Diese Methode ermöglicht die
   * direkte Abfrage (anstatt der <code>get()</code>-Methode) unter Umgehung der
   * rekursiven Struktur, wenn sichergestellt ist, dass das vorhandene Objekt
   * eine Instanz der Klasse <code>yacs.parser.Variable</code> ist.
   * @return DomainElement
   */
  public DomainElement getValue() {
    return this.element;
  }

  /**
   * Entfernt das übergebene Element aus der Domäne der Variablen. Ist die
   * Variable mit diesem Element aktuell belegt, so ist sie anschließend
   * unbelegt (<code>null</code>). Liefert <code>true</code>, wenn das Entfernen
   * erfolgreich war (Element war in der Domäne enthalten), <code>false</code>
   * wenn nicht.
   * @param element DomainElement
   * @return boolean
   */
  public boolean deleteValue(DomainElement element) {
    if (this.element.equals(element)) {
      this.element = null;
    }
    return this.domain.deleteElement(element);
  }

  /**
   * Liefert <code>true</code>n wenn der Wertebereich der Variable eingeschränkt
   * wurde. Gibt <code>false</code> zurück, wenn keine Änderung vorgenommen
   * wurde.
   * @param operatorType int
   * @param element DomainElement
   * @return boolean
   */
  public boolean narrow(int operatorType, DomainElement element) {
    return this.domain.narrow(operatorType, element);
  }

  public boolean setDomain(String variableName, Domain domain) {
    if (this.name.equals(variableName)) {
      this.domain = domain;
      return true;
    }
    return false;
  }

  public Domain getDomain(String variableName) {
    if (this.name.equals(variableName)) {
      return this.domain;
    }
    return null;
  }

  public List getVariables() {
    List variableList = new LinkedList();
    if (!this.signleton) {
      variableList.add(this.name);
    }
    return variableList;
  }

  public List getUnsetVariables() {
    if (this.element == null) {
      return this.getVariables();
    }
    return new LinkedList();
  }

  public boolean unsetVariablesLeft() {
    if (this.element == null) {
      return true;
    }
    return false;
  }

  public boolean set(String variableName, DomainElement element) {
    if (this.name.equals(variableName)) {
      return this.setValue(element);
    }
    return false;
  }

  public DomainElement get(String variableName) {
    if (this.name.equals(variableName)) {
      return this.getValue();
    }
    return null;
  }

  public boolean satisfiable() {
    // Eine einzelne mit einem *gültigen* Wert belegte Variable, die "gefragt"
    // wird, ob sie erfüllbar ist, wird immer mit "true" antworten.
    // Anders sieht das natürlich aus, wenn man einen Operator nach seiner
    // Erfüllbarkeit "befragt" ...
    if (this.element == null) {
      System.err.println("Error in Variable.satisfiable(): variable '" + this.getName() + "' was not assigned with a value!");
      return false;
    } else if (!this.domain.containsElement(this.element)) {
      System.err.println("Error in Variable.satisfiable(): variable '" + this.getName() + "' was assigned with value '" + this.element + "' not existing in its actual domain: " + this.domain);
      return false;
    }
    return true;
  }

  public DomainElement evaluate() {
    return this.element;
  }

  public List getPrimitiveExpressions() {
    return new LinkedList();
  }

  public Variable getVariableReference(String variableName) {
    if (this.name.equals(variableName)) {
      return this;
    }
    return null;
  }

  public boolean containsVariable(String variableName) {
    if (this.name.equals(variableName)) {
      return true;
    }
    return false;
  }

  public boolean hasFalseDomain() {
    if (this.domain.isEmpty()) {
      return true;
    }
    return false;
  }

  public boolean hasValuationDomain() {
    if (this.domain.size() == 1) {
      return true;
    }
    return false;
  }

  public boolean setValuationDomain() {
    if (this.hasValuationDomain()) {
      this.setValue((DomainElement)this.domain.getElements().get(0));
      return true;
    }
    return false;
  }

  public void singletons() {
    if (this.element != null) {
      DomainElement domainElement;
      Iterator it = this.domain.getElements().iterator();
      while (it.hasNext()) {
        domainElement = (DomainElement)it.next();
        if (!this.element.equals(domainElement)) {
          this.domain.deleteElement(domainElement);
        }
      }
      this.signleton = true;
    }
  }

  public int count(String variableName) {
    if (this.name.equals(variableName)) {
      return 1;
    }
    return 0;
  }

  public void init() {
    this.element = null;
  }

  public String toString() {
    if (this.element == null) {
      return this.name;
    }
    return ("var(" +this.name+ "," +this.element.toString()+ ")");
  }

}
