package yacs.domain;

import java.util.*;
import java.io.*;

/**
 * <p>Klasse zur Repräsentation der Domäne einer Constraint-Variablen.</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 Domain implements Cloneable, Serializable {

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

// ********** protected attributs **********

  /**
   * Eine <code>TreeMap</code> in der die Elemente der Domäne sortiert abgelegt
   * werden (müssen dafür das Interface <code>Comparable</code> implementieren).
   * Der "Key" ist jeweils das Element selbst (Instanzen des Interface
   * <code>yacs.domain.DomainElement</code>), der "Value" ist der Wert des
   * Elements (<code>DomainElement.getValue()</code>).
   */
  protected TreeMap domain = new TreeMap();

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

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

  /**
   * Fügt die übergebene Liste zu den Elementen der Domäne hinzu. Die Liste muss
   * Instanzen der Klasse <code>yacs.domain.DomainElement</code> enthalten.
   * @param domain List
   */
  private void addAll(List domain) {
    Iterator it = domain.iterator();
    while (it.hasNext()) {
      this.add((DomainElement)it.next());
    }
  }

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

  /**
   * Konstruktor -- Instantiiert die Domäne.
   */
  public Domain() {}

  /**
   * Konstruktor -- Die Domäne wird mit einem Element instantiiert.
   * @param element DomainElement
   */
  public Domain(DomainElement element) {
    this();
    this.add(element);
  }

  /**
   * Konstruktor -- Die Domäne wird mit einer Liste mit Elementen instantiiert
   * (Instanzen des Interface <code>yacs.domain.DomainElement</code>).
   * @param domain List
   */
  public Domain(List domain) {
    this();
    this.addAll(domain);
  }

  /**
   * Der Domäne wird ein Element hinzugefügt, wenn sein Wert noch nicht
   * vorhanden ist. Liefert <code>true</code> zurück, wenn dies erfolgreich war,
   * <code>false</code> wenn nicht (Wert bereits vorhanden).
   * @param element DomainElement
   */
  public boolean add(DomainElement element) {
    if (!this.containsElement(element)) {
      this.domain.put(element, element.getValue());
      return true;
    }
    return false;
  }

  /**
   * Aktualisiert den Wert für das übergebene Element in der Domäne.
   * @param element DomainElement
   * @return boolean
   */
  public boolean update(DomainElement element) {
    if (this.containsElement(element)) {
      this.domain.put(element, element.getValue());
      return true;
    }
    return false;
  }

  /**
   * Liefert eine "unmodifizierbare" Liste mit den Elementen der Domäne
   * (Instanzen des Interface <code>yacs.domain.DomainElement</code>).
   * @return List
   */
  public List getElements() {
    return Collections.unmodifiableList(new LinkedList(this.domain.keySet()));
  }

  /**
   * Liefert eine Referenz auf das in dieser Domäne vorhandene Element, welches
   * der Belegung des übergebenen Elements entspricht. Liefert
   * <code>null</code>, wenn kein Element mit der entsprechenden Belegung
   * existiert.
   * @return List
   */
  public DomainElement getElement(DomainElement element) {
    DomainElement availableElement;
    Iterator it = this.getElements().iterator();
    while (it.hasNext()) {
      availableElement = (DomainElement)it.next();
      if (availableElement.equals(element)) {
        return availableElement;
      }
    }
    return null;
  }

  /**
   * Gibt des erste Element der Domäne zurück, <code>null</code> wenn die Domäne
   * leer ist.
   * @return DomainElement
   */
/*  public DomainElement firstValue() {
    Iterator it = this.domain.iterator();
    if (it.hasNext()) {
      return (DomainElement)it.next();
    } else {
      return null;
    }
  }*/

  /**
   * Gibt des letzte Element der Domäne zurück, <code>null</code> wenn die
   * Domäne leer ist.
   * @return DomainElement
   */
/*  public DomainElement lastValue() {
    if (!this.domain.isEmpty()) {
      return ((DomainElement)this.domain.get(this.domain.size()-1));
    } else {
      return null;
    }
  }*/

  /**
   * Löscht eine Element aus der Domäne. Sollte nur aus der Klasse
   * <code>Variable</code> aufgerufen werden, denn u.U. ist der zu löschende
   * Wert die aktuelle Belegung der Variable. Dies wird in <code>Variable</code>
   * abgefangen. 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 deleteElement(DomainElement element) {
    if (this.domain.remove(element) != null) {
      return true;
    }
    return false;
  }

  /**
   * Abfragemöglichkeit, ob ein bestimmtes Element in der Domäne enthalten ist.
   * @param element DomainElement
   * @return boolean
   */
  public boolean containsElement(DomainElement element) {
    return this.domain.containsKey(element);
  }

  /**
   * Abfragemöglichkeit, ob ein Element mit einem bestimmten Wert in der Domäne
   * enthalten ist. (Anm.: Der Wert eines Elements wird durch
   * <code>DomainElement.getValue()</code> ermittelt.)
   * @param value Object
   * @return boolean
   */
/*  public boolean containsValue(Object value) {
    return this.domain.containsValue(value);
    //return this.domain.values().contains(value);
  } -> Funktioniert nur mit Standard-Klassen wie String und Integer!
       Vielleicht außer Klasse "Comparable" (mit Methode "compareTo()") auch
       Klasse "Comparator" (Methode "equals()")?! */

  /**
   * Liefert <code>true</code> wenn die Domäne leer ist, <code>false</code> wenn
   * nicht.
   * @return boolean
   */
  public boolean isEmpty() {
    return this.domain.isEmpty();
  }

  /**
   * Liefert die Anzahl der Elemente in der Domäne.
   * @return int
   */
  public int size() {
    return this.domain.size();
  }

  /**
   * Führt eine Beschränkung der Domäne durch, so dass sie nur noch das
   * übergebene Element enthält. Liefert <code>true</code> wenn die Domäne
   * eingeschränkt wurde (Element war enthalten), <code>false</code> wenn nicht.
   * (Anm.: Die Angabe <code>operatorType</code> (bezogen auf den Operator:
   * "=", "!=", "<", ">", "<=", ">=") wird derzeit an dieser Stelle ignoriert.)
   * @param operatorType int
   * @param element DomainElement
   * @return boolean
   */
  public boolean narrow(int operatorType, DomainElement element) {
    // Wenn das übergebene Element enthalten ist:
    if (this.containsElement(element)) {
      // Nur wenn außerdem noch andere Elemente enthalten sind:
      if (this.size() > 1) {
        // Domäne bis auf das übergenene Element löschen:
        this.domain.clear();
        this.add(element);
        return true;
      }
    }
    return false;
  }

  /**
   * Liefert die Domäne als String.
   * @return String
   */
  public String toString() {
    return domain.values().toString();
  }

  /**
   * Liefert eine Kopie dieses Objekts. Allerdings nur eine "shallow copy", d.h.
   * die Felder des Objektes werden nicht geklont.
   * @return Object
   */
  public Object clone() {
    try {
      return super.clone();
    }
    catch (CloneNotSupportedException e)
    {
      System.err.println("CloneNotSupportedException in Domain.clone()");
      e.printStackTrace();
    }
    catch (OutOfMemoryError e)
    {
      System.err.println("OutOfMemoryError in Domain.clone()");
      e.printStackTrace();
    }
    return null;
  }

  /**
   * Klont <code>this</code> und rekursiv alle darin referenzierten Objekte mit
   * (die Selbheit indirekt mehrfach referenzierter Objekte bleibt erhalten).
   * @return Domain
   */
  public Domain cloneDeep() {
    try {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      ObjectOutputStream oos = new ObjectOutputStream(baos);
      oos.writeObject(this); oos.flush();
      ObjectInputStream ois = new ObjectInputStream(
        new ByteArrayInputStream(baos.toByteArray())
      );
      // Klont durch Deserialisierung:
      return (Domain)ois.readObject();
    }
    catch (Exception e)
    {
      System.err.println("Exception in Domain.cloneDeep()");
      e.printStackTrace();
    }
    return null;
  }

}
