/*
 * 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.
 */

/*
 * $Id$
 */

#if !defined(XERCESC_INCLUDE_GUARD_CMSTATESET_HPP)
#define XERCESC_INCLUDE_GUARD_CMSTATESET_HPP

//  DESCRIPTION:
//
//  This class is a specialized bitset class for the content model code of
//  the validator. It assumes that its never called with two objects of
//  different bit counts, and that bit sets smaller than a threshold are far
//  and away the most common. So it can be a lot more optimized than a general
//  purpose utility bitset class
//

#include <xercesc/util/ArrayIndexOutOfBoundsException.hpp>
#include <xercesc/util/RuntimeException.hpp>
#include <xercesc/util/PlatformUtils.hpp>
#include <xercesc/framework/MemoryManager.hpp>
#include <string.h>

#if XERCES_HAVE_EMMINTRIN_H
#   include <emmintrin.h>
#endif

XERCES_CPP_NAMESPACE_BEGIN

class CMStateSetEnumerator;

// This value must be 4 in order to use the SSE2 instruction set
#define CMSTATE_CACHED_INT32_SIZE  4

// This value must be a multiple of 128 in order to use the SSE2 instruction set
#define CMSTATE_BITFIELD_CHUNK  1024
#define CMSTATE_BITFIELD_INT32_SIZE (1024 / 32)

struct CMDynamicBuffer
{
    //  fArraySize
    //      This indicates the number of elements of the fBitArray vector
    //
    //  fBitArray
    //      A vector of arrays of XMLInt32; each array is allocated on demand
    //      if a bit needs to be set in that range
    //
    //  fMemoryManager
    //      The memory manager used to allocate and deallocate memory
    //
    XMLSize_t       fArraySize;
    XMLInt32**      fBitArray;
    MemoryManager*  fMemoryManager;
};

class CMStateSet : public XMemory
{
public :
    // -----------------------------------------------------------------------
    //  Constructors and Destructor
    // -----------------------------------------------------------------------
    CMStateSet( const XMLSize_t bitCount
              , MemoryManager* const manager = XMLPlatformUtils::fgMemoryManager) :

        fBitCount(bitCount)
        , fDynamicBuffer(0)
    {
        //
        //  See if we need to allocate the byte array or whether we can live
        //  within the cached bit high performance scheme.
        //
        if (fBitCount > (CMSTATE_CACHED_INT32_SIZE * 32))
        {
            fDynamicBuffer = (CMDynamicBuffer*)manager->allocate(sizeof(CMDynamicBuffer));
            fDynamicBuffer->fMemoryManager = manager;
            // allocate an array of vectors, each one containing CMSTATE_BITFIELD_CHUNK bits
            fDynamicBuffer->fArraySize = fBitCount / CMSTATE_BITFIELD_CHUNK;
            if (fBitCount % CMSTATE_BITFIELD_CHUNK)
                fDynamicBuffer->fArraySize++;
            fDynamicBuffer->fBitArray = (XMLInt32**) fDynamicBuffer->fMemoryManager->allocate(fDynamicBuffer->fArraySize*sizeof(XMLInt32*));
            for(XMLSize_t index = 0; index < fDynamicBuffer->fArraySize; index++)
                fDynamicBuffer->fBitArray[index]=NULL;
        }
        else
        {
            for (XMLSize_t index = 0; index < CMSTATE_CACHED_INT32_SIZE; index++)
                fBits[index] = 0;
        }
    }

    CMStateSet(const CMStateSet& toCopy) :
        XMemory(toCopy)
      , fBitCount(toCopy.fBitCount)
      , fDynamicBuffer(0)
    {
        //
        //  See if we need to allocate the byte array or whether we can live
        //  within the cahced bit high performance scheme.
        //
        if (fBitCount > (CMSTATE_CACHED_INT32_SIZE * 32))
        {
            fDynamicBuffer = (CMDynamicBuffer*) toCopy.fDynamicBuffer->fMemoryManager->allocate(sizeof(CMDynamicBuffer));
            fDynamicBuffer->fMemoryManager = toCopy.fDynamicBuffer->fMemoryManager;
            fDynamicBuffer->fArraySize = fBitCount / CMSTATE_BITFIELD_CHUNK;
            if (fBitCount % CMSTATE_BITFIELD_CHUNK)
                fDynamicBuffer->fArraySize++;
            fDynamicBuffer->fBitArray = (XMLInt32**) fDynamicBuffer->fMemoryManager->allocate(fDynamicBuffer->fArraySize*sizeof(XMLInt32*));
            for(XMLSize_t index = 0; index < fDynamicBuffer->fArraySize; index++)
            {
                if(toCopy.fDynamicBuffer->fBitArray[index]!=NULL)
                {
                    allocateChunk(index);
                    memcpy((void *) fDynamicBuffer->fBitArray[index],
                           (const void *) toCopy.fDynamicBuffer->fBitArray[index],
                           CMSTATE_BITFIELD_INT32_SIZE * sizeof(XMLInt32));
                }
                else
                    fDynamicBuffer->fBitArray[index]=NULL;
            }
        }
        else
        {
            memcpy((void *) fBits,
                   (const void *) toCopy.fBits,
                   CMSTATE_CACHED_INT32_SIZE * sizeof(XMLInt32));
        }
    }

    ~CMStateSet()
    {
        if(fDynamicBuffer)
        {
            for(XMLSize_t index = 0; index < fDynamicBuffer->fArraySize; index++)
                if(fDynamicBuffer->fBitArray[index]!=NULL)
                    deallocateChunk(index);
            fDynamicBuffer->fMemoryManager->deallocate(fDynamicBuffer->fBitArray);
            fDynamicBuffer->fMemoryManager->deallocate(fDynamicBuffer);
        }
    }


    // -----------------------------------------------------------------------
    //  Set manipulation methods
    // -----------------------------------------------------------------------
    void operator|=(const CMStateSet& setToOr)
    {
        if(fDynamicBuffer==0)
        {
#ifdef XERCES_HAVE_SSE2_INTRINSIC
            if(XMLPlatformUtils::fgSSE2ok)
            {
                __m128i xmm1 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(fBits));
                __m128i xmm2 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(setToOr.fBits));
                __m128i xmm3 = _mm_or_si128(xmm1, xmm2);     //  OR  4 32-bit words
                _mm_storeu_si128(reinterpret_cast<__m128i*>(fBits), xmm3);
            }
            else
#endif
            {
                for (XMLSize_t index = 0; index < CMSTATE_CACHED_INT32_SIZE; index++)
                    if(setToOr.fBits[index])
                    {
                        if(fBits[index])
                            fBits[index] |= setToOr.fBits[index];
                        else
                            fBits[index] = setToOr.fBits[index];
                    }
            }
        }
        else
        {
            for (XMLSize_t index = 0; index < fDynamicBuffer->fArraySize; index++)
            {
                XMLInt32 *& other = setToOr.fDynamicBuffer->fBitArray[index];
                if(other!=NULL)
                {
                    // if we haven't allocated the subvector yet, allocate it and copy
                    if(fDynamicBuffer->fBitArray[index]==NULL)
                    {
                        allocateChunk(index);
                        memcpy((void *) fDynamicBuffer->fBitArray[index],
                               (const void *) other,
                               CMSTATE_BITFIELD_INT32_SIZE * sizeof(XMLInt32));
                    }
                    else
                    {
                        // otherwise, merge them
                        XMLInt32*& mine = fDynamicBuffer->fBitArray[index];
#ifdef XERCES_HAVE_SSE2_INTRINSIC
                        if(XMLPlatformUtils::fgSSE2ok)
                        {
                            for(XMLSize_t subIndex = 0; subIndex < CMSTATE_BITFIELD_INT32_SIZE; subIndex+=4)
                            {
                               __m128i xmm1 = _mm_load_si128(reinterpret_cast<const __m128i*>(&other[subIndex]));
                               __m128i xmm2 = _mm_load_si128(reinterpret_cast<const __m128i*>(&mine[subIndex]));
                               __m128i xmm3 = _mm_or_si128(xmm1, xmm2);     //  OR  4 32-bit words
                               _mm_store_si128(reinterpret_cast<__m128i*>(&mine[subIndex]), xmm3);
                            }
                        }
                        else
#endif
                        {
                            for(XMLSize_t subIndex = 0; subIndex < CMSTATE_BITFIELD_INT32_SIZE; subIndex++)
                                if(setToOr.fDynamicBuffer->fBitArray[index][subIndex])
                                {
                                    if(fDynamicBuffer->fBitArray[index][subIndex])
                                        fDynamicBuffer->fBitArray[index][subIndex] |= setToOr.fDynamicBuffer->fBitArray[index][subIndex];
                                    else
                                        fDynamicBuffer->fBitArray[index][subIndex] = setToOr.fDynamicBuffer->fBitArray[index][subIndex];
                                }
                        }
                    }
                }
            }
        }
    }

    bool operator==(const CMStateSet& setToCompare) const
    {
        if (fBitCount != setToCompare.fBitCount)
            return false;

        if(fDynamicBuffer==0)
        {
            for (XMLSize_t index = 0; index < CMSTATE_CACHED_INT32_SIZE; index++)
            {
                if (fBits[index] != setToCompare.fBits[index])
                    return false;
            }
        }
        else
        {
            for (XMLSize_t index = 0; index < fDynamicBuffer->fArraySize; index++)
            {
                XMLInt32 *& other = setToCompare.fDynamicBuffer->fBitArray[index],
                         *& mine = fDynamicBuffer->fBitArray[index];
                if(mine==NULL && other==NULL)
                    continue;
                else if(mine==NULL || other==NULL) // the other should have been empty too
                    return false;
                else
                {
                    for(XMLSize_t subIndex = 0; subIndex < CMSTATE_BITFIELD_INT32_SIZE; subIndex++)
                        if(mine[subIndex]!=other[subIndex])
                            return false;
                }
            }
        }
        return true;
    }

    CMStateSet& operator=(const CMStateSet& srcSet)
    {
        if (this == &srcSet)
            return *this;

        // They have to be the same size
        if (fBitCount != srcSet.fBitCount)
        {
            if(fDynamicBuffer)
                ThrowXMLwithMemMgr(RuntimeException, XMLExcepts::Bitset_NotEqualSize, fDynamicBuffer->fMemoryManager);
            else
                ThrowXML(RuntimeException, XMLExcepts::Bitset_NotEqualSize);
        }

        if(fDynamicBuffer==0)
        {
            for (XMLSize_t index = 0; index < CMSTATE_CACHED_INT32_SIZE; index++)
                fBits[index] = srcSet.fBits[index];
        }
        else
        {
            for (XMLSize_t index = 0; index < fDynamicBuffer->fArraySize; index++)
                if(srcSet.fDynamicBuffer->fBitArray[index]==NULL)
                {
                    // delete this subentry
                    if(fDynamicBuffer->fBitArray[index]!=NULL)
                        deallocateChunk(index);
                }
                else
                {
                    // if we haven't allocated the subvector yet, allocate it and copy
                    if(fDynamicBuffer->fBitArray[index]==NULL)
                        allocateChunk(index);
                    memcpy((void *) fDynamicBuffer->fBitArray[index],
                           (const void *) srcSet.fDynamicBuffer->fBitArray[index],
                           CMSTATE_BITFIELD_INT32_SIZE * sizeof(XMLInt32));
                }
        }
        return *this;
    }

    XMLSize_t getBitCountInRange(XMLSize_t start, XMLSize_t end) const
    {
        XMLSize_t count = 0;
        end /= 32;
        if(fDynamicBuffer==0)
        {
            if(end > CMSTATE_CACHED_INT32_SIZE)
                end = CMSTATE_CACHED_INT32_SIZE;
            for (XMLSize_t index = start / 32; index < end; index++)
            {
                if (fBits[index] != 0)
                    for(int i=0;i<32;i++)
                    {
                        const XMLInt32 mask = 1UL << i;
                        if(fBits[index] & mask)
                            count++;
                    }
            }
        }
        else
        {
            if(end > fDynamicBuffer->fArraySize)
                end = fDynamicBuffer->fArraySize;
            for (XMLSize_t index = start / 32; index < end; index++)
            {
                if(fDynamicBuffer->fBitArray[index]==NULL)
                    continue;
                for(XMLSize_t subIndex=0;subIndex < CMSTATE_BITFIELD_INT32_SIZE; subIndex++)
                {
                    if (fDynamicBuffer->fBitArray[index][subIndex] != 0)
                        for(int i=0;i<32;i++)
                        {
                            const XMLInt32 mask = 1UL << i;
                            if(fDynamicBuffer->fBitArray[index][subIndex] & mask)
                                count++;
                        }
                }
            }
        }
        return count;
    }

    bool getBit(const XMLSize_t bitToGet) const
    {
        if (bitToGet >= fBitCount)
        {
            if(fDynamicBuffer)
                ThrowXMLwithMemMgr(ArrayIndexOutOfBoundsException, XMLExcepts::Bitset_BadIndex, fDynamicBuffer->fMemoryManager);
            else
                ThrowXML(ArrayIndexOutOfBoundsException, XMLExcepts::Bitset_BadIndex);
        }

        // And access the right bit and byte
        if(fDynamicBuffer==0)
        {
            const XMLInt32 mask = 1UL << (bitToGet % 32);
            const XMLSize_t byteOfs = bitToGet / 32;
            return (fBits[byteOfs]!=0 && (fBits[byteOfs] & mask) != 0);
        }
        else
        {
            const XMLSize_t vectorOfs = bitToGet / CMSTATE_BITFIELD_CHUNK;
            if(fDynamicBuffer->fBitArray[vectorOfs]==NULL)
                return false;
            const XMLInt32 mask = 1UL << (bitToGet % 32);
            const XMLSize_t byteOfs = (bitToGet % CMSTATE_BITFIELD_CHUNK) / 32;
            return (fDynamicBuffer->fBitArray[vectorOfs][byteOfs]!=0 && (fDynamicBuffer->fBitArray[vectorOfs][byteOfs] & mask) != 0);
        }
    }

    bool isEmpty() const
    {
        if(fDynamicBuffer==0)
        {
            for (XMLSize_t index = 0; index < CMSTATE_CACHED_INT32_SIZE; index++)
            {
                if (fBits[index] != 0)
                    return false;
            }
        }
        else
        {
            for (XMLSize_t index = 0; index < fDynamicBuffer->fArraySize; index++)
            {
                if(fDynamicBuffer->fBitArray[index]==NULL)
                    continue;
                for(XMLSize_t subIndex=0;subIndex < CMSTATE_BITFIELD_INT32_SIZE; subIndex++)
                {
                    if (fDynamicBuffer->fBitArray[index][subIndex] != 0)
                        return false;
                }
            }
        }
        return true;
    }

    void setBit(const XMLSize_t bitToSet)
    {
        if (bitToSet >= fBitCount)
        {
            if(fDynamicBuffer)
                ThrowXMLwithMemMgr(ArrayIndexOutOfBoundsException, XMLExcepts::Bitset_BadIndex, fDynamicBuffer->fMemoryManager);
            else
                ThrowXML(ArrayIndexOutOfBoundsException, XMLExcepts::Bitset_BadIndex);
        }

        const XMLInt32 mask = 1UL << (bitToSet % 32);

        // And access the right bit and byte
        if(fDynamicBuffer==0)
        {
            const XMLSize_t byteOfs = bitToSet / 32;
            fBits[byteOfs] &= ~mask;
            fBits[byteOfs] |= mask;
        }
        else
        {
            const XMLSize_t vectorOfs = bitToSet / CMSTATE_BITFIELD_CHUNK;
            if(fDynamicBuffer->fBitArray[vectorOfs]==NULL)
            {
                allocateChunk(vectorOfs);
                for(XMLSize_t index=0;index < CMSTATE_BITFIELD_INT32_SIZE; index++)
                    fDynamicBuffer->fBitArray[vectorOfs][index]=0;
            }
            const XMLSize_t byteOfs = (bitToSet % CMSTATE_BITFIELD_CHUNK) / 32;
            fDynamicBuffer->fBitArray[vectorOfs][byteOfs] &= ~mask;
            fDynamicBuffer->fBitArray[vectorOfs][byteOfs] |= mask;
        }
    }

    void zeroBits()
    {
        if(fDynamicBuffer==0)
        {
            for (XMLSize_t index = 0; index < CMSTATE_CACHED_INT32_SIZE; index++)
                fBits[index] = 0;
        }
        else
        {
            for (XMLSize_t index = 0; index < fDynamicBuffer->fArraySize; index++)
                // delete this subentry
                if(fDynamicBuffer->fBitArray[index]!=NULL)
                    deallocateChunk(index);
        }
    }

    XMLSize_t hashCode() const
    {
        XMLSize_t hash = 0;
        if(fDynamicBuffer==0)
        {
            for (XMLSize_t index = 0; index<CMSTATE_CACHED_INT32_SIZE; index++)
                hash = fBits[index] + hash * 31;
        }
        else
        {
            for (XMLSize_t index = 0; index<fDynamicBuffer->fArraySize; index++)
            {
                if(fDynamicBuffer->fBitArray[index]==NULL)
                    // simulates the iteration on the missing bits
                    for(XMLSize_t subIndex=0;subIndex < CMSTATE_BITFIELD_INT32_SIZE; subIndex++)
                        hash *= 31;
                else
                    for(XMLSize_t subIndex=0;subIndex < CMSTATE_BITFIELD_INT32_SIZE; subIndex++)
                        hash = fDynamicBuffer->fBitArray[index][subIndex] + hash * 31;
            }
        }
        return hash;
    }

private :
    // -----------------------------------------------------------------------
    //  Unimplemented constructors and operators
    // -----------------------------------------------------------------------
    CMStateSet();

    // -----------------------------------------------------------------------
    // Helpers
    // -----------------------------------------------------------------------
    void allocateChunk(const XMLSize_t index)
    {
#ifdef XERCES_HAVE_SSE2_INTRINSIC
        if(XMLPlatformUtils::fgSSE2ok)
            fDynamicBuffer->fBitArray[index]=(XMLInt32*)_mm_malloc(CMSTATE_BITFIELD_INT32_SIZE * sizeof(XMLInt32), 16);
        else
#endif
            fDynamicBuffer->fBitArray[index]=(XMLInt32*)fDynamicBuffer->fMemoryManager->allocate(CMSTATE_BITFIELD_INT32_SIZE * sizeof(XMLInt32));
    }

    void deallocateChunk(const XMLSize_t index)
    {
#ifdef XERCES_HAVE_SSE2_INTRINSIC
        if(XMLPlatformUtils::fgSSE2ok)
            _mm_free(fDynamicBuffer->fBitArray[index]);
        else
#endif
            fDynamicBuffer->fMemoryManager->deallocate(fDynamicBuffer->fBitArray[index]);
        fDynamicBuffer->fBitArray[index]=NULL;
    }

    // -----------------------------------------------------------------------
    //  Private data members
    //
    //  fBitCount
    //      The count of bits that the outside world wants to support,
    //      so its the max bit index plus one.
    //
    //  fBits
    //      When the bit count is less than a threshold (very common), these hold the bits.
    //      Otherwise, the fDynamicBuffer member holds htem.
    //
    //  fDynamicBuffer
    //      If the bit count is greater than the threshold, then we allocate this structure to
    //      store the bits, the length, and the memory manager to allocate/deallocate
    //      the memory
    //
    // -----------------------------------------------------------------------
    XMLSize_t        fBitCount;
    XMLInt32         fBits[CMSTATE_CACHED_INT32_SIZE];
    CMDynamicBuffer* fDynamicBuffer;

    friend class CMStateSetEnumerator;
};

class CMStateSetEnumerator : public XMemory
{
public:
    CMStateSetEnumerator(const CMStateSet* toEnum, XMLSize_t start = 0) :
      fToEnum(toEnum),
      fIndexCount((XMLSize_t)-1),
      fLastValue(0)
    {
        // if a starting bit is specified, place fIndexCount at the beginning of the previous 32 bit area
        // so the findNext moves to the one where 'start' is located
        if(start > 32)
            fIndexCount = (start/32 - 1) * 32;
        findNext();
        // if we found data, and fIndexCount is still pointing to the area where 'start' is located, erase the bits before 'start'
        if(hasMoreElements() && fIndexCount < start)
        {
            for(XMLSize_t i=0;i< (start - fIndexCount);i++)
            {
                XMLInt32 mask=1UL << i;
                if(fLastValue & mask)
                    fLastValue &= ~mask;
            }
            // in case the 32 bit area contained only bits before 'start', advance
            if(fLastValue==0)
                findNext();
        }
    }

    bool hasMoreElements()
    {
        return fLastValue!=0;
    }

    unsigned int nextElement()
    {
        for(int i=0;i<32;i++)
        {
            XMLInt32 mask=1UL << i;
            if(fLastValue & mask)
            {
                fLastValue &= ~mask;
                unsigned int retVal=(unsigned int)fIndexCount+i;
                if(fLastValue==0)
                    findNext();
                return retVal;
            }
        }
        return 0;
    }

private:
    void findNext()
    {
        if(fToEnum->fDynamicBuffer==0)
        {
            XMLSize_t nOffset=((fIndexCount==(XMLSize_t)-1)?0:(fIndexCount/32)+1);
            for(XMLSize_t index=nOffset;index<CMSTATE_CACHED_INT32_SIZE;index++)
            {
                if(fToEnum->fBits[index]!=0)
                {
                    fIndexCount=index*32;
                    fLastValue=fToEnum->fBits[index];
                    return;
                }
            }
        }
        else
        {
            XMLSize_t nOffset=((fIndexCount==(XMLSize_t)-1)?0:(fIndexCount/CMSTATE_BITFIELD_CHUNK));
            XMLSize_t nSubOffset=((fIndexCount==(XMLSize_t)-1)?0:((fIndexCount % CMSTATE_BITFIELD_CHUNK) /32)+1);
            for (XMLSize_t index = nOffset; index<fToEnum->fDynamicBuffer->fArraySize; index++)
            {
                if(fToEnum->fDynamicBuffer->fBitArray[index]!=NULL)
                {
                    for(XMLSize_t subIndex=nSubOffset;subIndex < CMSTATE_BITFIELD_INT32_SIZE; subIndex++)
                        if(fToEnum->fDynamicBuffer->fBitArray[index][subIndex]!=0)
                        {
                            fIndexCount=index*CMSTATE_BITFIELD_CHUNK + subIndex*32;
                            fLastValue=fToEnum->fDynamicBuffer->fBitArray[index][subIndex];
                            return;
                        }
                }
                nSubOffset = 0; // next chunks will be processed from the beginning
            }
        }
    }

    const CMStateSet*   fToEnum;
    XMLSize_t           fIndexCount;
    XMLInt32            fLastValue;
};

XERCES_CPP_NAMESPACE_END

#endif
