package yacs.domain;

import yacs.parser.*;

import net.sourceforge.interval.ia_math.*;

/**
 * <p>Klasse zur Repräsentation einer reellwertigen Intervalldomäne. Diese
 * Klasse stützt sich auf die in der Bibliothek
 * <a href="http://interval.sourceforge.net/interval/java/ia_math/README.html">IAMath</a>
 * von <a href="http://www.cs.brandeis.edu/~tim/">Timothy J. Hickey</a>
 * implementierte Repräsentation von Intervallen.</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 IntervalDomain  extends Domain {

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

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

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

  /**
   * Fügt die übergebene Liste zu den Elementen der Domäne hinzu. Die Liste muss
   * <code>RealIntervall</code>-Objekte enthalten.
   * @param domain List
   */
/*  public void addAll(List domain) {
    Iterator it = domain.iterator();
    while (it.hasNext()) {
      this.add(new IntervalDomainElement((RealInterval)it.next()));
    }
  }*/

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

  /**
   * Konstruktor -- Instantiiert eine Intervalldomäne.
   */
  public IntervalDomain() {
    super();
  }

  /**
   * Konstruktor -- Die Domäne wird mit einem Wert (hier:
   * <code>RealInterval</code>) instantiiert.
   * @param value RealInterval
   */
  public IntervalDomain(RealInterval value) {
    this();
    this.add(new IntervalDomainElement(value));
  }

  /**
   * Konstruktor -- Erlaubt die vereinfachte Instantiierung einer
   * Intervalldomäne durch Angabe der unteren (<code>lo</code>) und oberen
   * Schranke (<code>hi</code>) des Intervalls als <code>Double</code>-Objekt.
   * @param lo double
   * @param hi double
   */
  public IntervalDomain(Double lo, Double hi) {
    this(new RealInterval(lo.doubleValue(), hi.doubleValue()));
  }

  /**
   * Konstruktor -- Erlaubt die vereinfachte Instantiierung einer
   * Intervalldomäne durch Angabe der unteren (<code>lo</code>) und oberen
   * Schranke (<code>hi</code>) des Intervalls als <code>double</code>-Wert.
   * @param lo double
   * @param hi double
   */
  public IntervalDomain(double lo, double hi) {
    this(new RealInterval(lo, hi));
  }

  /**
   * Der Domäne wird ein Element hinzugefügt. Überschreibt die übergeordnete
   * <code>add()</code>-Methode in der Art, dass sichergestellt wird, dass das
   * übergebene Element anschließend das einzige innerhalb der Domäne ist.
   * Dadurch wird ein konvexes Intervall repräsentiert. Die Methode liefert
   * <code>true</code> zurück, wenn die Belegung erfolgreich war,
   * <code>false</code> wenn nicht.
   * @param value DomainElement
   * @return boolean
   */
  public boolean add(DomainElement value) {
    this.domain.clear();
    return super.add(value);
  }

  /**
   * Vereinfacht das Hinzufügen von Elementen zu Domäne, indem lediglich ein
   * <code>RealIntervall</code> übergeben werden muss. Liefert <code>true</code>
   * zurück, wenn dies erfolgreich war, <code>false</code> wenn nicht
   * (Intervall bereits vorhanden). Diese Methode stellt sicher, dass das
   * übergebene Intervall anschließend das einzige Element innerhalb der Domäne
   * ist, wodurch ein konvexes Intervall repräsentiert wird.
   * @param value RealInterval
   * @return boolean
   */
  public boolean add(RealInterval value) {
    return this.add(new IntervalDomainElement(value));
  }

  /**
   * Vereinfacht das Hinzufügen von Elementen zu Domäne, indem lediglich die
   * untere (<code>lo</code>) und obere Schranke (<code>hi</code>) des
   * Intervalls als <code>Double</code>-Objekt angegeben werden muss. Liefert
   * <code>true</code> zurück, wenn dies erfolgreich war, <code>false</code>
   * wenn nicht (Intervall bereits vorhanden). Diese Methode stellt sicher, dass
   * das übergebene Intervall anschließend das einzige Element innerhalb der
   * Domäne ist, wodurch ein konvexes Intervall repräsentiert wird.
   * @param lo Double
   * @param hi Double
   * @return boolean
   */
  public boolean add(Double lo, Double hi) {
    return this.add(new RealInterval(lo.doubleValue(), hi.doubleValue()));
  }

  /**
   * Vereinfacht das Hinzufügen von Elementen zu Domäne, indem lediglich die
   * untere (<code>lo</code>) und obere Schranke (<code>hi</code>) des
   * Intervalls als <code>double</code>-Wert angegeben werden muss. Liefert
   * <code>true</code> zurück, wenn dies erfolgreich war, <code>false</code>
   * wenn nicht (Intervall bereits vorhanden). Diese Methode stellt sicher, dass
   * das übergebene Intervall anschließend das einzige Element innerhalb der
   * Domäne ist, wodurch ein konvexes Intervall repräsentiert wird.
   * @param lo double
   * @param hi double
   * @return boolean
   */
  public boolean add(double lo, double hi) {
    return this.add(new RealInterval(lo, hi));
  }

  /**
   * Liefert die untere Schranke dieser Intervalldomäne.
   * @return double
   */
  public double getLowerBound() {
    return ((IntervalDomainElement)this.domain.firstKey()).getLowerBound();
  }

  /**
   * Liefert die obere Schranke dieser Intervalldomäne.
   * @return double
   */
  public double getUpperBound() {
    return ((IntervalDomainElement)this.domain.lastKey()).getUpperBound();
  }

  /**
   * Setzt die untere Schranke dieser Intervalldomäne.
   * @param lowerBound double
   */
  public void setLowerBound(double lowerBound) {
    ((IntervalDomainElement)this.domain.firstKey()).setLowerBound(lowerBound);
    this.update((IntervalDomainElement)this.domain.firstKey());
  }

  /**
   * Setzt die obere Schranke dieser Intervalldomäne.
   * @param upperBound double
   */
  public void setUpperBound(double upperBound) {
    ((IntervalDomainElement)this.domain.lastKey()).setUpperBound(upperBound);
    this.update((IntervalDomainElement)this.domain.lastKey());
  }

  /**
   * Führt eine Beschränkung der Domäne durch, so dass sich das enthaltene
   * Intervall anschließend innerhalb den Grenzen des übergebenen Intervalls
   * befindet. Lässt sich diese Beschränkung nicht durchführen, wird das in
   * dieser Domäne enthaltenen Intervall gelöscht, wodurch eine "false domain"
   * und dadurch eine Inkonsistenz entsteht. Liefert <code>true</code> wenn die
   * Domäne eingeschränkt wurde, <code>false</code> wenn nicht. (Anm.: Derzeit
   * wird auf diese Art ausschließlich das Einschränken der Intervallgrenzen für
   * den Gleichheitsoperator implementiert.)
   * @param operatorType int
   * @param element DomainElement
   * @return boolean
   */
  public boolean narrow(int operatorType, DomainElement element) {
    boolean domainModification = false;
    IntervalDomainElement narrowElement = (IntervalDomainElement)element;
    //
    // - Vielleicht einfach das eigene Intervall mit dem übergebenen schneiden?
    // - Operatoren berücksichtigen?! = != < > <= >= ...
    // - Hilfsmethoden in IntervalDomainElement: contains(), disjoint(), intersect()?
    //
    // Unterscheidung nach dem Typ des Operators:
    switch (operatorType) {
      case sym.EQUAL :
        // Obere Grenze checken:
        if (narrowElement.getUpperBound() < this.getUpperBound() &&
            narrowElement.getUpperBound() >= this.getLowerBound()) {
          // Wenn sich die obere Grenze des übergebenen Intervalls unterhalb der
          // eigenen oberen Grenze aber oberhalb der eigenen unteren Grenze
          // befindet, wird die eigene obere Grenze angepasst:
          this.setUpperBound(narrowElement.getUpperBound());
          domainModification = true;
        }
        // Untere Grenze checken:
        if (narrowElement.getLowerBound() > this.getLowerBound() &&
            narrowElement.getLowerBound() <= this.getUpperBound()) {
          // Wenn sich die untere Grenze des übergebenen Intervalls oberhalb der
          // eigenen unteren Grenze aber unterhalb der eigenen oberen Grenze
          // befindet, wird die eigene untere Grenze angepasst:
          this.setLowerBound(narrowElement.getLowerBound());
          domainModification = true;
        }
        // Prüfen, ob das eigene Intervall in dem übergebenen enthalten ist:
        if (!narrowElement.contains((IntervalDomainElement)this.domain.firstKey())) {
//          System.err.println("Lösche Intervall: " + (IntervalDomainElement)this.domain.firstKey());
//          System.err.println("Weil es ungleich diesem Intervall ist (bzw. nicht enthalten): " + narrowElement);
          // Inkonsistenz, das eigene Intervall wird aus der Domäne entfernt
          // (produziert eine "false domain"):
          this.deleteElement((IntervalDomainElement)this.domain.firstKey());
        }
        break;
      case sym.NOT_EQUAL :
        domainModification = false;
        break;
      case sym.GREATER :
        domainModification = false;
        break;
      case sym.LOWER :
        domainModification = false;
        break;
      case sym.GREATER_EQUAL :
        domainModification = false;
        break;
      case sym.LOWER_EQUAL :
        domainModification = false;
        break;
      case sym.SEMI :
        System.err.println("Error in IntervalDomain.narrow(): operator with type " + operatorType + " is not allowed here.");
        domainModification = false;
        break;
      default:
        System.err.println("Error in IntervalDomain.narrow(): unknown operator with type " + operatorType);
        domainModification = false;
    }
    return domainModification;
  }

//  /**
//   * Vereinfachte Abfragemöglichkeit, ob ein Element mit bestimmten
//   * Intervallgrenzen in der Domäne enthalten ist.
//   * @param lo double
//   * @param hi double
//   * @return boolean
//   */
/*  public boolean contains(double lo, double hi) {
    return this.containsValue(new RealInterval(lo,hi));
  } -> Funktioniert nur mit Standard-Klassen wie String und Integer!
       Vielleicht außer Klasse "Comparable" (mit Methode "compareTo()") auch
       Klasse "Comparator" (Methode "equals()")?! */

/*
  public static void main(String[] args) {
    //
    // Einige Tests zu containsElement(), containsValue() und contains():
    //
    IntervalDomain domain = new IntervalDomain();
    IntervalDomainElement a = new IntervalDomainElement(5,10);
    IntervalDomainElement b = new IntervalDomainElement(5,10);

    domain.add(a);
    System.out.println(domain.containsElement(b));
    //System.out.println(domain.containsValue(new net.sourceforge.interval.ia_math.RealInterval(5,10)));
    //System.out.println(domain.contains(5,10));

//    NumericFDDomain domain = new NumericFDDomain();
//    NumericFDElement a = new NumericFDElement(5);
//    NumericFDElement b = new NumericFDElement(5);
//
//    domain.add(a);
//    System.out.println(domain.containsElement(b));
//    //System.out.println(domain.containsValue(new Integer(5)));
//    System.out.println(domain.contains(5));
//
//    SymbolicFDDomain domain = new SymbolicFDDomain();
//    SymbolicFDElement a = new SymbolicFDElement("test");
//    SymbolicFDElement b = new SymbolicFDElement("test");
//
//    domain.add(a);
//    System.out.println(domain.containsElement(b));
//    //System.out.println(domain.containsValue("test"));
//    System.out.println(domain.contains("test"));

    //-> containsValue() funktioniert nicht bei eigenen KLassen!
    //Obige Methode wird mehrfach in Domain/Variable aufgerufen ...
    //TreeMap durch TreeSet ersetzen??
    // - Warum TreeMap? Problematik "contains()"? ... Oder noch etwas anderes?
    // - Rückgabe einer Liste wird für Ordnungsheuristiken benötigt!

    System.out.println("a.equals(b): " + a.equals(b));
    java.util.LinkedList list = new java.util.LinkedList();
    list.add(a);
    System.out.println("list.contains(a): " + list.contains(a));
    System.out.println("list.contains(b): " + list.contains(b));

  }
*/

}
