/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef _DECAF_UTIL_BITSET_H_
#define _DECAF_UTIL_BITSET_H_

#include <decaf/util/Config.h>

#include <string>

namespace decaf {
namespace util {

    /**
     * This class implements a vector of bits that grows as needed. Each component of
     * the bit set has a boolean value. The bits of a BitSet are indexed by nonnegative
     * integers. Individual indexed bits can be examined, set, or cleared. One BitSet
     * may be used to modify the contents of another BitSet through logical AND, logical
     * inclusive OR, and logical exclusive OR operations.
     *
     * By default, all bits in the set initially have the value false.
     *
     * Every bit set has a current size, which is the number of bits of space currently
     * in use by the bit set. Note that the size is related to the implementation of a
     * bit set, so it may change with implementation. The length of a bit set relates to
     * logical length of a bit set and is defined independently of implementation.
     *
     * A BitSet is not safe for multi-threaded use without external synchronization.
     *
     * @since 1.0
     */
    class DECAF_API BitSet {
    private:

        // The actual array of 64 bit bits elements
        unsigned long long* bits;
        int bitsSize;

        // Optimization, when the array is all zero there's no need to traverse
        bool needClear;

        // Lazily maintained actual array length and state.
        mutable int actualArrayLength;
        mutable bool isLengthActual;

    private:

        BitSet(unsigned long long* bits, int bitsSize, bool needClear, int actualArrayLength, bool isLengthActual);

    public:

        /**
         * Creates a new BitSet whose bits are all false.
         */
        BitSet();

        /**
         * Creates a bit set whose initial size is large enough to explicitly represent bits
         * with indices in the range 0 through bitCount-1. All bits are initially false.  If the
         * bitCount is not a multiple of 64 then the count is rounded to the next closest
         * multiple of 64.
         *
         * @param bitCount
         *      The number of bits this BitSet should hold.
         *
         * @throws NegativeArraySizeException if bitCount is negative.
         */
        BitSet(int bitCount);

        /**
         * Copy Constructor
         */
        BitSet(const BitSet& set);

        /**
         * Assignment
         */
        BitSet& operator= (const BitSet& set);

        virtual ~BitSet();

        /**
         * Boolean comparison operator ==
         *
         * @param other
         *      The other BitSet to compare to this one.
         */
        bool operator==(const BitSet& other) const {
            return this->equals(other);
        }

        /**
         * Boolean comparison operator !=
         *
         * @param other
         *      The other BitSet to compare to this one.
         */
        bool operator!=(const BitSet& other) const {
            return !this->equals(other);
        }

    public:

        /**
         * Performs a logical AND of this target bit set with the argument bit set. This bit
         * set is modified so that each bit in it has the value true if and only if it both
         * initially had the value true and the corresponding bit in the bit set argument
         * also had the value true.
         *
         * @param set
         *      The BitSet to perform this action against.
         */
        void AND(const BitSet& set);

        /**
         * Performs a logical OR of this bit set with the bit set argument. This bit set is
         * modified so that a bit in it has the value true if and only if it either already had
         * the value true or the corresponding bit in the bit set argument has the value true.
         *
         * @param set
         *      The BitSet to perform this action against.
         */
        void OR(const BitSet& set);

        /**
         * Clears all of the bits in this BitSet whose corresponding bit is set in the specified BitSet.
         *
         * @param set
         *      The BitSet to perform this action against.
         */
        void andNot(const BitSet& set);

        /**
         * Returns the number of bits set to true in this BitSet.
         *
         * @return the number of bits set to true in this BitSet.
         */
        int cardinality();

        /**
         * Sets all of the bits in this BitSet to false.
         */
        void clear();

        /**
         * Sets the bit specified by the index to false.
         *
         * @param index
         *      The index of the bit whose value is to be set to false
         *
         * @throws IndexOutOfBoundsException if the index value is negative.
         */
        void clear(int index);

        /**
         * Sets the bits from the specified fromIndex (inclusive) to the specified toIndex
         * (exclusive) to false.
         *
         * @param fromIndex
         *      The index (inclusive) to start setting bits to false.
         * @param toIndex
         *      The index (exclusive) to stop setting bits to false.
         *
         * @throws IndexOutOfBoundsException if fromIndex is negative, or toIndex is negative, or
         *                                   fromIndex is larger than toIndex.
         */
        void clear(int fromIndex, int toIndex);

        /**
         * Compares this object against the specified object. The result is true if and only if
         * is a Bitset object that has exactly the same set of bits set to true as this bit set.
         * That is, for every nonnegative int index k,
         *
         *   set.get(k) == this->get(k)
         *
         * must be true. The current sizes of the two bit sets are not compared.
         *
         * @return true if the sets are the same, false otherwise.
         */
        bool equals(const BitSet& set) const;

        /**
         * Sets the bit at the specified index to the complement of its current value.
         *
         * @param index
         *      The index of the bit whose value is to be set to its compliment.
         *
         * @throws IndexOutOfBoundsException if the index value is negative.
         */
        void flip(int index);

        /**
         * Sets each bit from the specified fromIndex (inclusive) to the specified toIndex
         * (exclusive) to the complement of its current value.
         *
         * @param fromIndex
         *      The index (inclusive) to start setting bits to its compliment.
         * @param toIndex
         *      The index (exclusive) to stop setting bits to its compliment.
         *
         * @throws IndexOutOfBoundsException if fromIndex is negative, or toIndex is negative, or
         *                                   fromIndex is larger than toIndex.
         */
        void flip(int fromIndex, int toIndex);

        /**
         * Returns the value of the bit with the specified index. The value is true if the bit
         * with the given index is currently set in this BitSet; otherwise, the result is false.
         *
         * @param index
         *      The index of the bit in question.
         *
         * @return the value of the bit with the specified index.
         *
         * @throws IndexOutOfBoundsException if the index value is negative.
         */
        bool get(int index) const;

        /**
         * Returns a new BitSet composed of bits from this BitSet from fromIndex (inclusive) to
         * toIndex (exclusive).
         *
         * @param fromIndex
         *      The index (inclusive) to start at.
         * @param toIndex
         *      The index (exclusive) to stop at.
         *
         * @return a new BitSet containing the specified values.
         *
         * @throws IndexOutOfBoundsException if fromIndex is negative, or toIndex is negative, or
         *                                   fromIndex is larger than toIndex.
         */
        BitSet get(int fromIndex, int toIndex) const;

        /**
         * Returns true if the specified BitSet has any bits set to true that are also set to
         * true in this BitSet.
         *
         * @param set
         *      BitSet to intersect with.
         *
         * @return boolean indicating whether this BitSet intersects the specified BitSet.
         */
        bool intersects(const BitSet& set) const;

        /**
         * Returns true if this BitSet contains no bits that are set to true.
         *
         * @return true if the set is empty, false otherwise.
         */
        bool isEmpty() const;

        /**
         * Returns the "logical size" of this BitSet: the index of the highest set bit in the
         * BitSet plus one. Returns zero if the BitSet contains no set bits.
         *
         * @return the logical size of the BitSet.
         */
        int length() const;

        /**
         * Returns the index of the first bit that is set to false that occurs on or after the
         * specified starting index.
         *
         * @param index
         *      The index to start the search from (inclusive).
         *
         * @return the index of the next clear bit.
         *
         * @throws IndexOutOfBoundsException if the index value is negative.
         */
        int nextClearBit(int index) const;

        /**
         * Returns the index of the first bit that is set to true that occurs on or after the
         * specified starting index.
         *
         * @param index
         *      The index to start the search from (inclusive).
         *
         * @return the index of the next set bit.
         *
         * @throws IndexOutOfBoundsException if the index value is negative.
         */
        int nextSetBit(int index) const;

        /**
         * Sets the bit at the specified index to true.
         *
         * @param index
         *      The index to set to true.
         *
         * @throws IndexOutOfBoundsException if the index value is negative.
         */
        void set(int index);

        /**
         * Sets the bit at the specified index to the specified value.
         *
         * @param index
         *      The index to set.
         * @param value
         *      The value to assign to the given bit.
         *
         * @throws IndexOutOfBoundsException if the index value is negative.
         */
        void set(int index, bool value);

        /**
         * Sets the bits from the specified fromIndex (inclusive) to the specified toIndex
         * (exclusive) to true.
         *
         * @param fromIndex
         *      The index (inclusive) to start at.
         * @param toIndex
         *      The index (exclusive) to stop at.
         *
         * @throws IndexOutOfBoundsException if fromIndex is negative, or toIndex is negative, or
         *                                   fromIndex is larger than toIndex.
         */
        void set(int fromIndex, int toIndex);

        /**
         * Sets the bits from the specified fromIndex (inclusive) to the specified toIndex
         * (exclusive) to the value given.
         *
         * @param fromIndex
         *      The index (inclusive) to start at.
         * @param toIndex
         *      The index (exclusive) to stop at.
         * @param value
         *      The boolean value to assign to the target bits.
         *
         * @throws IndexOutOfBoundsException if fromIndex is negative, or toIndex is negative, or
         *                                   fromIndex is larger than toIndex.
         */
        void set(int fromIndex, int toIndex, bool value);

        /**
         * Returns the number of bits of space actually in use by this BitSet to represent bit
         * values. The maximum element in the set is the size - 1st element.
         *
         * @return the number of bits currently in this bit set.
         */
        int size() const;

        /**
         * Returns a string representation of this bit set. For every index for which this BitSet
         * contains a bit in the set state, the decimal representation of that index is included
         * in the result. Such indices are listed in order from lowest to highest, separated by
         * ", " (a comma and a space) and surrounded by braces, resulting in the usual mathematical
         * notation for a set of integers.
         *
         * @return string representation of the BitSet.
         */
        std::string toString() const;

        /**
         * Performs a logical XOR of this bit set with the bit set argument. This bit set is modified
         * so that a bit in it has the value true if and only if one of the following statements holds:
         *
         *   * The bit initially has the value true, and the corresponding bit in the argument has
         *     the value false.
         *   * The bit initially has the value false, and the corresponding bit in the argument has
         *     the value true.
         *
         * @param set
         *      The BitSet to use.
         */
        void XOR(const BitSet& set);

    private:

        /**
         * Increase the size of the internal array to accommodate length bits.
         * The new array max index will be a multiple of 64.
         *
         * @param length
         *      the index the new array needs to be able to access.
         */
        void ensureCapacity(int length);

        /**
         * Gets the actual length of the BitSet bit array which is maintained in a
         * sometimes lazy fashion to avoid excessive array traversals.  The actual
         * length is the number of non-zero array elements in the physical array
         * used to back this BitSet.
         *
         * @return the actual array length.
         */
        int getActualArrayLength() const;

    };

}}

#endif /* _DECAF_UTIL_BITSET_H_ */
