////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2013 Saxonica Limited.
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package net.sf.saxon.z;


import net.sf.saxon.tree.util.FastStringBuffer;

import java.io.Serializable;

/**
 * Set of int values. This class is modelled on the java.net.Set interface, but it does
 * not implement this interface, because the set members are nint's rather than Objects.
 * <p/>
 * Not thread safe.
 * @author Dominique Devienne
 * @author Michael Kay: retrofitted to JDK 1.4, added iterator()
 */
public class IntHashSet extends AbstractIntSet implements IntSet, Serializable {

    private static final int NBIT = 30; // MAX_SIZE = 2^NBIT

    /**
     * The maximum number of elements this container can contain.
     */
    public static final int MAX_SIZE = 1 << NBIT; // maximum number of keys mapped

    /**
     * This set's NO-DATA-VALUE.
     */
    public final int ndv;

    // private

    //private double _factor; // 0.0 <= _factor <= 1.0 - changed by MHK to assume factor = 0.25
    private int _nmax; // 0 <= _nmax = 2^nbit <= 2^NBIT = MAX_SIZE
    private int _size; // 0 <= _size <= _nmax <= MAX_SIZE
    private int _nlo; // _nmax*_factor (_size<=_nlo, if possible)
    private int _nhi; //  MAX_SIZE*_factor (_size< _nhi, if possible)
    private int _shift; // _shift = 1 + NBIT - nbit (see function hash() below)
    private int _mask; // _mask = _nmax - 1
    private int[] _values; // array[_nmax] of values


    /**
     * Initializes a set with a capacity of 8 and a load factor of 0,25.
     */
    public IntHashSet() {
        this(8, Integer.MIN_VALUE);
    }

    /**
     * Initializes a set with the given capacity and a load factor of 0,25.
     * @param capacity the initial capacity.
     */
    public IntHashSet(int capacity) {
        this(capacity, Integer.MIN_VALUE);
    }

    /**
     * Initializes a set with a load factor of 0,25.
     * @param capacity    the initial capacity.
     * @param noDataValue the value to use for non-values.
     */
    public IntHashSet(int capacity, int noDataValue) {
        ndv = noDataValue;
        //_factor = 0.25;
        setCapacity(capacity);
    }

    public IntSet copy() {
        if (_size == 0) {
            return IntEmptySet.getInstance();
        } else {
            IntHashSet s = new IntHashSet(_size, ndv);
            s._nmax  = _nmax;
            s._size = _size;
            s._nlo = _nlo;
            s._nhi = _nhi;
            s._shift = _shift;
            s._size = _size;
            s._values = new int[_values.length];
            System.arraycopy(_values, 0, s._values, 0, _values.length);
            //s._values = Arrays.copyOf(_values, _values.length);
            return s;
        }
    }

    public IntSet mutableCopy() {
        return copy();
    }

    public void clear() {
        _size = 0;
        for (int i = 0; i < _nmax; ++i) {
            _values[i] = ndv;
        }
    }

    public int size() {
        return _size;
    }

    public boolean isEmpty() {
        return _size == 0;
    }

    public int[] getValues() {
        int index = 0;
        final int[] values = new int[_size];
        for (int _value : _values) {
            if (_value != ndv) {
                values[index++] = _value;
            }
        }
        return values;
    }


    public boolean contains(int value) {
        return (_values[indexOf(value)] != ndv);
    }


    public boolean remove(int value) {
        // Knuth, v. 3, 527, Algorithm R.
        int i = indexOf(value);
        if (_values[i] == ndv) {
            return false;
        }
        --_size;
        for (; ;) {
            _values[i] = ndv;
            int j = i;
            int r;
            do {
                i = (i - 1) & _mask;
                if (_values[i] == ndv) {
                    return true;
                }
                r = hash(_values[i]);
            } while ((i <= r && r < j) || (r < j && j < i) || (j < i && i <= r));
            _values[j] = _values[i];
        }
    }


    public boolean add(int value) {
        if (value == ndv) {
            throw new IllegalArgumentException("Can't add the 'no data' value");
        }
        int i = indexOf(value);
        if (_values[i] == ndv) {
            ++_size;
            _values[i] = value;

            // Check new size
            if (_size > MAX_SIZE) {
                throw new RuntimeException("Too many elements (> " + MAX_SIZE + ')');
            }
            if (_nlo < _size && _size <= _nhi) {
                setCapacity(_size);
            }
            return true;
        } else {
            return false; // leave set unchanged
        }
    }

    ///////////////////////////////////////////////////////////////////////////

    private int hash(int key) {
        // Knuth, v. 3, 509-510. Randomize the 31 low-order bits of c*key
        // and return the highest nbits (where nbits <= 30) bits of these.
        // The constant c = 1327217885 approximates 2^31 * (sqrt(5)-1)/2.
        return ((1327217885 * key) >> _shift) & _mask;
    }

    /**
     * Gets the index of the value, if it exists, or the index at which
     * this value would be added if it does not exist yet.
     */
    private int indexOf(int value) {
        int i = hash(value);
        while (_values[i] != ndv) {
            if (_values[i] == value) {
                return i;
            }
            i = (i - 1) & _mask;
        }
        return i;
    }

    private void setCapacity(int capacity) {
        // Changed MHK in 8.9 to use a constant factor of 0.25, thus avoiding floating point arithmetic
        if (capacity < _size) {
            capacity = _size;
        }
        //double factor = 0.25;
        int nbit, nmax;
        for (nbit = 1, nmax = 2; nmax < capacity * 4 && nmax < MAX_SIZE; ++nbit, nmax *= 2) {
            ;
        }
        int nold = _nmax;
        if (nmax == nold) {
            return;
        }

        _nmax = nmax;
        _nlo = (int)(nmax / 4);
        _nhi = (int)(MAX_SIZE / 4);
        _shift = 1 + NBIT - nbit;
        _mask = nmax - 1;

        _size = 0;
        int[] values = _values;
        _values = new int[nmax];
        java.util.Arrays.fill(_values, ndv); // empty all values
        if (values != null) {
            for (int i = 0; i < nold; ++i) {
                int value = values[i];
                if (value != ndv) {
                    // Don't use add, because the capacity is necessarily large enough,
                    // and the value is necessarily unique (since in this set already)!
                    //add(values[i]);
                    ++_size;
                    _values[indexOf(value)] = value;
                }
            }
        }
    }

    /**
     * Get an iterator over the values
     */

    public IntIterator iterator() {
        return new IntHashSetIterator();
    }


    /**
     * Test if one set has overlapping membership with another set
     */

    public static boolean containsSome(IntSet one, IntSet two) {
        if (two instanceof IntEmptySet) {
            return false;
        }
        if (two instanceof IntUniversalSet) {
            return !one.isEmpty();
        }
        if (two instanceof IntComplementSet) {
            return !((IntComplementSet)two).getExclusions().containsAll(one);
        }
        IntIterator it = two.iterator();
        while (it.hasNext()) {
            if (one.contains(it.next())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Test whether this set has exactly the same members as another set
     */

    public boolean equals(Object other) {
        if (other instanceof IntSet) {
            IntHashSet s = (IntHashSet)other;
            return (size() == s.size() && containsAll(s));
        } else {
            return false;
        }
    }

    /**
     * Construct a hash key that supports the equals() test
     */

    public int hashCode() {
        // Note, hashcodes are the same as those used by IntArraySet
        int h = 936247625;
        IntIterator it = iterator();
        while (it.hasNext()) {
            h += it.next();
        }
        return h;
    }

    /**
     * Diagnostic output
     */

    public void diagnosticDump() {
        System.err.println("Contents of IntHashSet");
        FastStringBuffer sb = new FastStringBuffer(100);
        for (int i = 0; i < _values.length; i++) {
            if (i % 10 == 0) {
                System.err.println(sb.toString());
                sb.setLength(0);
            }
            if (_values[i] == ndv) {
                sb.append("*, ");
            } else {
                sb.append(_values[i] + ", ");
            }
        }
        System.err.println(sb.toString());
        sb.setLength(0);
        System.err.println("size: " + _size);
        System.err.println("ndv: " + ndv);
        System.err.println("nlo: " + _nlo);
        System.err.println("nhi: " + _nhi);
        System.err.println("nmax: " + _nmax);
        System.err.println("shift: " + _shift);
        System.err.println("mask: " + _mask);
        System.err.println("Result of iterator:");
        IntIterator iter = iterator();
        int i = 0;
        while (iter.hasNext()) {
            if (i++ % 10 == 0) {
                System.err.println(sb.toString());
                sb.setLength(0);
            }
            sb.append(iter.next() + ", ");
        }
        System.err.println(sb.toString());
        System.err.println("=====================");
    }

    /**
     * Iterator class
     * @author Saxonica Limited
     */

    private class IntHashSetIterator implements IntIterator, Serializable {

        private int i = 0;

        public IntHashSetIterator() {
            i = 0;
        }

        public boolean hasNext() {
            while (i < _values.length) {
                if (_values[i] != ndv) {
                    return true;
                } else {
                    i++;
                }
            }
            return false;
        }

        public int next() {
            return _values[i++];
        }
    }

}

