package java_cup;

import java.util.BitSet;

/** A set of terminals implemented as a bitset. 
 * @version last updated: 11/25/95
 * @author  Scott Hudson
 */
public class terminal_set {

    /*-----------------------------------------------------------*/
    /*--- Constructor(s) ----------------------------------------*/
    /*-----------------------------------------------------------*/

    /** Constructor for an empty set. */
    public terminal_set() {
        /* allocate the bitset at what is probably the right size */
        _elements = new BitSet(terminal.number());
    }

    /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

    /** Constructor for cloning from another set. 
     * @param other the set we are cloning from.
     */
    public terminal_set(terminal_set other) throws internal_error {
        not_null(other);
        _elements = (BitSet) other._elements.clone();
    }

    /*-----------------------------------------------------------*/
    /*--- (Access to) Static (Class) Variables ------------------*/
    /*-----------------------------------------------------------*/

    /** Constant for the empty set. */
    public static final terminal_set EMPTY = new terminal_set();

    /*-----------------------------------------------------------*/
    /*--- (Access to) Instance Variables ------------------------*/
    /*-----------------------------------------------------------*/

    /** Bitset to implement the actual set. */
    protected BitSet _elements;

    /*-----------------------------------------------------------*/
    /*--- General Methods ----------------------------------------*/
    /*-----------------------------------------------------------*/

    /** Helper function to test for a null object and throw an exception if
     *  one is found. 
     * @param obj the object we are testing.
     */
    protected void not_null(Object obj) throws internal_error {
        if (obj == null)
            throw new internal_error("Null object used in set operation");
    }

    /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

    /** Determine if the set is empty. */
    public boolean empty() {
        return equals(EMPTY);
    }

    /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

    /** Determine if the set contains a particular terminal. 
     * @param sym the terminal symbol we are looking for.
     */
    public boolean contains(terminal sym) throws internal_error {
        not_null(sym);
        return _elements.get(sym.index());
    }

    /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

    /** Given its index determine if the set contains a particular terminal. 
     * @param indx the index of the terminal in question.
     */
    public boolean contains(int indx) {
        return _elements.get(indx);
    }

    /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

    /** Determine if this set is an (improper) subset of another.
     * @param other the set we are testing against.
     */
    public boolean is_subset_of(terminal_set other) throws internal_error {
        not_null(other);

        /* make a copy of the other set */
        BitSet copy_other = (BitSet) other._elements.clone();

        /* and or in */
        copy_other.or(_elements);

        /* if it hasn't changed, we were a subset */
        return copy_other.equals(other._elements);
    }

    /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

    /** Determine if this set is an (improper) superset of another.
     * @param other the set we are testing against.
     */
    public boolean is_superset_of(terminal_set other) throws internal_error {
        not_null(other);
        return other.is_subset_of(this);
    }

    /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

    /** Add a single terminal to the set.  
     * @param sym the terminal being added.
     * @return true if this changes the set.
     */
    public boolean add(terminal sym) throws internal_error {
        boolean result;

        not_null(sym);

        /* see if we already have this */
        result = _elements.get(sym.index());

        /* if not we add it */
        if (!result) _elements.set(sym.index());

        return result;
    }

    /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

    /** Remove a terminal if it is in the set.
     * @param sym the terminal being removed.
     */
    public void remove(terminal sym) throws internal_error {
        not_null(sym);
        _elements.clear(sym.index());
    }

    /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

    /** Add (union) in a complete set.  
     * @param other the set being added.
     * @return true if this changes the set.
     */
    public boolean add(terminal_set other) throws internal_error {
        not_null(other);

        /* make a copy */
        BitSet copy = (BitSet) _elements.clone();

        /* or in the other set */
        _elements.or(other._elements);

        /* changed if we are not the same as the copy */
        return !_elements.equals(copy);
    }

    /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

    /** Determine if this set intersects another.
     * @param other the other set in question.
     */
    public boolean intersects(terminal_set other) throws internal_error {
        not_null(other);

        /* make a copy of the other set */
        BitSet copy = (BitSet) other._elements.clone();

        /* xor out our values */
        copy.xor(this._elements);

        /* see if its different */
        return !copy.equals(other._elements);
    }

    /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

    /** Equality comparison. */
    public boolean equals(terminal_set other) {
        if (other == null)
            return false;
        else return _elements.equals(other._elements);
    }

    /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

    /** Generic equality comparison. */
    @Override
    public boolean equals(Object other) {
        if (!(other instanceof terminal_set))
            return false;
        else return equals((terminal_set) other);
    }

    @Override
    public int hashCode() {
        return _elements.hashCode();
    }

    /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/

    /** Convert to string. */
    @Override
    public String toString() {
        String result;
        boolean comma_flag;

        result = "{";
        comma_flag = false;
        for (int t = 0; t < terminal.number(); t++) {
            if (_elements.get(t)) {
                if (comma_flag)
                    result += ", ";
                else comma_flag = true;

                result += terminal.find(t).name();
            }
        }
        result += "}";

        return result;
    }

    /*-----------------------------------------------------------*/

}
