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

#if !defined(ARENAALLOCATOR_INCLUDE_GUARD_1357924680)
#define ARENAALLOCATOR_INCLUDE_GUARD_1357924680



#include <algorithm>



#include <xalanc/Include/STLHelper.hpp>
#include <xalanc/Include/XalanList.hpp>



#include "ArenaBlock.hpp"



namespace XALAN_CPP_NAMESPACE {



template<class ObjectType,
#if defined(XALAN_NO_DEFAULT_TEMPLATE_ARGUMENTS)
         class ArenaBlockType>
#else
         class ArenaBlockType = ArenaBlock<ObjectType> >
#endif
class ArenaAllocator
{
public:

    typedef ArenaAllocator<ObjectType, ArenaBlockType>  ThisType;

    typedef XalanList<ArenaBlockType*>          ArenaBlockListType;

    typedef typename ArenaBlockType::size_type  size_type;

    /*
     * Construct an instance that will allocate blocks of the specified size.
     *
     * @param theBlockSize The block size.
     */
    ArenaAllocator(
                MemoryManager&  theManager,
                size_type           theBlockSize) :
        m_blockSize(theBlockSize),
        m_blocks(theManager)
    {
    }

    virtual
    ~ArenaAllocator()
    {
        reset();
    }

    MemoryManager&
    getMemoryManager()
    {
        return m_blocks.getMemoryManager();
    }

    const MemoryManager&
    getMemoryManager() const
    {
        return m_blocks.getMemoryManager();
    }

    /*
     * Get size of an ArenaBlock, that is, the number
     * of objects in each block.
     *
     * @return The size of the block
     */
    size_type
    getBlockSize() const
    {
        return m_blockSize;
    }

    /*
     * Set size of an ArenaBlock, that is, the number
     * of objects in each block.  Only affects blocks
     * allocated after the call.
     *
     * @param theSize The size of the block
     */
    void
    setBlockSize(size_type  theSize)
    {
        m_blockSize = theSize;
    }

    /*
     * Get the number of ArenaBlocks currently allocated.
     *
     * @return The number of blocks.
     */
    size_type
    getBlockCount() const
    {
        return (size_type)m_blocks.size();
    }

    /*
     * Allocate a block of the appropriate size for an
     * object.  Call commitAllocation() when after
     * the object is successfully constructed.
     *
     * @return A pointer to a block of memory
     */
    virtual ObjectType*
    allocateBlock()
    {
        if (m_blocks.empty() == true ||
            m_blocks.back()->blockAvailable() == false)
        {
            m_blocks.push_back(
                ArenaBlockType::create(
                    getMemoryManager(),
                    m_blockSize));
        }
        assert(
            m_blocks.empty() == false &&
            m_blocks.back() != 0 &&
            m_blocks.back()->blockAvailable() == true);

        return m_blocks.back()->allocateBlock();
    }

    /*
     * Commits the allocation of the previous
     * allocateBlock() call.
     *
     * @param theObject A pointer to a block of memory
     */
    virtual void
    commitAllocation(ObjectType*    theObject)
    {
        assert(
            m_blocks.empty() == false &&
            m_blocks.back()->ownsBlock(theObject) == true);

        m_blocks.back()->commitAllocation(theObject);

        assert(m_blocks.back()->ownsObject(theObject) == true);
    }

    virtual bool
    ownsObject(const ObjectType*    theObject) const
    {
        bool    fResult = false;

        typedef typename ArenaBlockListType::const_reverse_iterator     const_reverse_iterator;

        // Search back for a block that may have allocated the object...
        const const_reverse_iterator    theEnd = this->m_blocks.rend();

        const_reverse_iterator  i = this->m_blocks.rbegin();

        while(i != theEnd)
        {
            assert(*i != 0);

            if ((*i)->ownsObject(theObject) == true)
            {
                fResult = true;

                break;
            }
            else
            {
                ++i;
            }
        }

        return fResult;
    }

    virtual void
    reset()
    {
        std::for_each(
            m_blocks.begin(),
            m_blocks.end(),
            DeleteFunctor<ArenaBlockType>(m_blocks.getMemoryManager()));

        m_blocks.clear();
    }

protected:

    // data members...
    size_type           m_blockSize;

    ArenaBlockListType  m_blocks;

private:

    // Not defined...
    ArenaAllocator(const ArenaAllocator<ObjectType, ArenaBlockType>&);

    ArenaAllocator<ObjectType, ArenaBlockType>&
    operator=(const ArenaAllocator<ObjectType, ArenaBlockType>&);

    bool
    operator==(const ArenaAllocator<ObjectType, ArenaBlockType>&) const;
};



}



#endif  // !defined(ARENAALLOCATOR_INCLUDE_GUARD_1357924680)
