package de.uni_hamburg.fs;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;


/**
 * Represents a partition of concepts in a feature structure system.
 * A partition is a collection of concepts that are mutually exclusive,
 * meaning no concept in the partition can be a subtype of another concept
 * in the same partition.
 */
public class Partition implements java.io.Serializable {

    /**
     * Set of concepts that belong to this partition.
     * Contains unique concepts that are mutually exclusive,
     * meaning no concept can be a subtype of another concept in this set.
     */
    private Set<ConceptImpl> _concepts = new HashSet<ConceptImpl>();

    /**
     * Creates an empty partition with no concepts.
     */
    public Partition() {}

    /**
     * Creates a partition containing a single concept.
     * The concept is added to this partition, and this partition is added
     * to the concept's set of partitions.
     *
     * @param con the concept to be added to this partition
     */
    public Partition(ConceptImpl con) {
        _concepts.add(con);
        con._partitions.add(this);
    }

    /**
     * Merges another partition into this partition.
     * All concepts from the given partition are added to this partition.
     *
     * @param partition the partition to be merged into this one
     * @throws TypeException if any concept in the given partition is a subtype
     *                       of, or has a subtype relationship with, any concept in this partition
     */
    public void union(Partition partition) throws TypeException {
        Iterator<ConceptImpl> conciterator = partition.concepts();
        while (conciterator.hasNext()) {
            ConceptImpl con = conciterator.next();
            addConcept(con);
        }
    }

    /**
     * Adds a new concept to this partition.
     *
     * @param newcon the concept to be added
     * @throws TypeException if the new concept is a subtype of,
     *                       or has a subtype relationship with, any existing concept in this partition
     */
    public void addConcept(ConceptImpl newcon) throws TypeException {
        for (Iterator<ConceptImpl> conciterator = _concepts.iterator(); conciterator.hasNext();) {
            ConceptImpl con = conciterator.next();
            if (con == newcon) {
                return;
            }
            if (con.isa(newcon) || newcon.isa(con)) {
                throw new TypeException();
            }
        }


        // When adding a concept, the hash code of this partition changes.
        // Therefore we first have to remove the partition from all hash
        // sets that still refer to the old hash code.
        for (Iterator<ConceptImpl> conciterator = _concepts.iterator(); conciterator.hasNext();) {
            ConceptImpl con = conciterator.next();
            con._partitions.remove(this);
        }

        // Then we can add the new concept.
        _concepts.add(newcon);


        // Now we inform all concepts (including the new one) about the
        // changed partition.       
        for (Iterator<ConceptImpl> conciterator = _concepts.iterator(); conciterator.hasNext();) {
            ConceptImpl con = conciterator.next();
            con._partitions.add(this);
        }
    }

    /**
     * Checks if this partition contains the specified concept.
     *
     * @param con the concept to check for
     * @return true if this partition contains the specified concept
     */
    public boolean containsConcept(ConceptImpl con) {
        return _concepts.contains(con);
    }

    /**
     * Returns an iterator over all concepts in this partition.
     *
     * @return an iterator over the concepts in this partition
     */
    public Iterator<ConceptImpl> concepts() {
        return _concepts.iterator();
    }

    @Override
    public String toString() {
        StringBuffer output = new StringBuffer();
        Iterator<ConceptImpl> concs = _concepts.iterator();
        for (int i = 1; concs.hasNext(); ++i) {
            if (i > 1) {
                output.append(',');
            }
            ConceptImpl con = concs.next();
            output.append(con.getName());
        }
        return output.toString();
    }

    /**
     * Compares this <code>Partition</code> object to the specified
     * <code>Object</code>.
     *
     * @param that the <code>Object</code> to compare.
     * @return <code>true</code> if the argument is a <code>Partition</code>
     * and comprises the same concepts as this partition.
     **/
    @Override
    public boolean equals(Object that) {
        if (that instanceof Partition) {
            return ((Partition) that)._concepts.equals(_concepts);
        }
        return false;
    }

    /**
     * Returns a hashcode for this <code>Partition</code>.
     *
     * @return a hashcode value for this <code>Partition</code>.
     **/
    @Override
    public int hashCode() {
        return _concepts.hashCode();
    }
}