package de.uni_hamburg.fs;

import java.util.Enumeration;

import collections.CollectionEnumeration;
import collections.HashedSet;
import collections.Set;
import collections.UpdatableSet;


/**
 * Represents a set of concepts with specialized operations for manipulating concept hierarchies.
 * This class provides methods for joining and meeting concepts, which are operations
 * in the concept lattice of the type system.
 */
public class ConceptSet implements java.io.Serializable {
    /** An empty concept set constant. */
    public static final ConceptSet EMPTY = new ConceptSet();

    /** The set containing all concepts in this collection. */
    private UpdatableSet _concepts = new HashedSet();

    /**
     * Cached hash code value for this concept set.
     * A value of -1 indicates that the hash code needs to be recalculated.
     */
    private int _hashCode = 0;

    /**
     * Constructs an empty concept set.
     */
    public ConceptSet() {}

    /**
     * Constructs a concept set containing a single concept.
     *
     * @param con the concept to include in the set
     */
    public ConceptSet(Concept con) {
        _concepts.include(con);
        _hashCode = con.hashCode();
    }

    /**
     * Constructs a concept set that is a copy of another concept set.
     *
     * @param cs the concept set to copy
     */
    public ConceptSet(ConceptSet cs) {
        _concepts = (UpdatableSet) cs._concepts.duplicate();
        _hashCode = cs._hashCode;
    }

    /**
     * Constructs a concept set from an enumeration of concepts.
     *
     * @param concepts the enumeration of concepts to include in the set
     */
    public ConceptSet(Enumeration<Concept> concepts) {
        while (concepts.hasMoreElements()) {
            addConcept(concepts.nextElement());
        }
    }

    /**
     * Checks if this concept set is empty.
     *
     * @return true if this concept set contains no concepts, false otherwise
     */
    public boolean isEmpty() {
        return _concepts.isEmpty();
    }

    /**
     * Returns the number of concepts in this set.
     *
     * @return the number of concepts in this set
     */
    public int size() {
        return _concepts.size();
    }

    /**
     * Returns an enumeration of all concepts in this set.
     *
     * @return an enumeration of all concepts in this set
     */
    public ConceptEnumeration elements() {
        return new ConceptEnumeration(_concepts.elements());
    }

    /**
     * Adds a concept to this set if it is not already included.
     *
     * @param newConcept the concept to add to this set
     */
    public void addConcept(Concept newConcept) {
        if (!_concepts.includes(newConcept)) {
            _concepts.include(newConcept);
            _hashCode += newConcept.hashCode();
        }
    }

    /**
     * Unites this concept set with another concept set, adding all concepts
     * from the other set to this set.
     *
     * @param that the concept set to unite with this set
     */
    public void unite(ConceptSet that) {
        _concepts.includeElements(that.elements());
        _hashCode = -1;
    }

    /**
     * Joins a new concept with the existing concept set, removing any concepts that
     * are more general than the new concept and checking for concept compatibility.
     *
     * <p>This method performs a lattice join operation from concept theory and is used
     * to maintain a concept set with minimal redundancy.</p>
     *
     * @param newConcept the concept to join with this set
     * @throws UnificationFailure if the new concept is incompatible with any concept in the set
     */
    public void joinConcept(Concept newConcept) throws UnificationFailure {
        UpdatableSet newConcepts = new HashedSet();
        ConceptEnumeration conceptEnum = elements();
        while (conceptEnum.hasMoreElements()) {
            Concept concept = conceptEnum.nextConcept();
            if (concept.isa(newConcept)) {
                return; // already something more special there
            }

            // only include concepts which are not more general than the new:
            if (!newConcept.isa(concept)) {
                // concepts are incomparable: test compatibility!
                if (concept.isNotA(newConcept)) {
                    throw new UnificationFailure(); // incompatible!
                }
                newConcepts.include(concept);
            }
        }


        // now that we have reduced the set of concepts, check
        // for unification with one of these concepts.
        //        concepts = new HashedSet();
        //        boolean addNew=true;
        //        Enumeration concEnum = newConcepts.elements();
        //        while (concEnum.hasMoreElements()) {
        //        Concept concept = (Concept)concEnum.nextElement();
        //        Concept uni=newConcept.unify(concept);
        //        if (uni==null) {
        //            concepts.include(concept);
        //        } else {
        //            addNew=false;
        //            concepts.include(uni);
        //        }
        //        }
        //        if (addNew)
        //        concepts.include(newConcept);
        newConcepts.include(newConcept);
        _concepts = newConcepts;
        _hashCode = -1;
    }

    /**
     * Performs a join operation on all concepts in this set.
     * This operation modifies the set to contain only the most specific concepts.
     *
     * @throws UnificationFailure if any concepts in the set are incompatible
     */
    public void join() throws UnificationFailure {
        ConceptSet newConcepts = new ConceptSet();
        newConcepts.joinConcepts(elements());
        _concepts = newConcepts._concepts;
    }

    /**
     * Joins all concepts from the provided enumeration with this concept set.
     *
     * @param conceptEnum an enumeration of concepts to join with this set
     * @throws UnificationFailure if any concept is incompatible with the existing concepts
     */
    public void joinConcepts(ConceptEnumeration conceptEnum) throws UnificationFailure {
        while (conceptEnum.hasMoreElements()) {
            joinConcept(conceptEnum.nextConcept());
        }
    }

    /**
     * Performs a meet operation with a new concept on this concept set.
     * This operation removes concepts that are more specific than the new concept
     * and adds the new concept to the set.
     *
     * @param newConcept the concept to meet with this set
     */
    public void meetConcept(Concept newConcept) {
        UpdatableSet newConcepts = new HashedSet();
        ConceptEnumeration conceptEnum = elements();
        while (conceptEnum.hasMoreElements()) {
            Concept concept = conceptEnum.nextConcept();
            if (newConcept.isa(concept)) {
                return; // already something more general there
            }

            // only include concepts which are not more special than the new:
            if (!concept.isa(newConcept)) {
                newConcepts.include(concept);
            }
        }
        newConcepts.include(newConcept);
        _concepts = newConcepts;
        _hashCode = -1;
    }

    /**
     * Performs a meet operation on all concepts in this set.
     * This operation modifies the set to contain only the most general concepts.
     */
    public void meet() {
        ConceptSet newConcepts = new ConceptSet();
        ConceptEnumeration conceptEnum = elements();
        while (conceptEnum.hasMoreElements()) {
            Concept concept = conceptEnum.nextConcept();
            newConcepts.meetConcept(concept);
        }
        _concepts = newConcepts._concepts;
    }

    //    public boolean equals(Object that) {
    //        if (that instanceof ConceptSet) {
    //        //  logger.debug("Comparing"+concepts+" and "+
    //        //             ((ConceptSet)that).concepts+": "+
    //        //             concepts.sameStructure(((ConceptSet)that).concepts));
    //        return ((ConceptSet)that).concepts.sameStructure(concepts);
    //        }
    //        return false;
    //    }
    @Override
    public boolean equals(Object that) {
        if (that instanceof ConceptSet) {
            return equalSet(((ConceptSet) that)._concepts, _concepts);
        }
        return false;
    }

    /**
     * Helper method to determine if two sets contain the same elements.
     *
     * @param a the first set to compare
     * @param b the second set to compare
     * @return true if both sets contain exactly the same elements
     */
    private static boolean equalSet(Set a, Set b) {
        if (a.size() != b.size()) {
            return false;
        }
        CollectionEnumeration aEnum = a.elements();
        for (int i = a.size(); i > 0; --i) {
            if (!b.includes(aEnum.nextElement())) {
                return false;
            }
        }
        return true;
    }

    @Override
    public int hashCode() {
        if (_hashCode == -1) {
            _hashCode = 0;
            ConceptEnumeration concenumeration = elements();
            while (concenumeration.hasMoreElements()) {
                _hashCode += concenumeration.nextConcept().hashCode();
            }
        }
        return _hashCode;
    }

    /** Return a String representation of this ConceptSet. */
    @Override
    public String toString() {
        StringBuffer output = new StringBuffer("{");
        ConceptEnumeration conceptEnum = new ConceptEnumeration(_concepts.elements());
        for (int i = 1; conceptEnum.hasMoreElements(); ++i) {
            if (i > 1) {
                output.append(',');
            }
            Concept concept = conceptEnum.nextConcept();
            output.append(concept.getName()); //+" ["+concept.getClass()+"]");
        }
        output.append('}');
        return output.toString();
    }
}