/*
 * Copyright 2010 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef GrAllocator_DEFINED
#define GrAllocator_DEFINED

#include "GrConfig.h"
#include "GrTypes.h"
#include "SkNoncopyable.h"
#include "SkTArray.h"
#include "SkTypes.h"
#include <new>

class GrAllocator : SkNoncopyable {
public:
    ~GrAllocator() { this->reset(); }

    /**
     * Create an allocator
     *
     * @param   itemSize        the size of each item to allocate
     * @param   itemsPerBlock   the number of items to allocate at once
     * @param   initialBlock    optional memory to use for the first block.
     *                          Must be at least itemSize*itemsPerBlock sized.
     *                          Caller is responsible for freeing this memory.
     */
    GrAllocator(size_t itemSize, int itemsPerBlock, void* initialBlock)
        : fItemSize(itemSize)
        , fItemsPerBlock(itemsPerBlock)
        , fOwnFirstBlock(nullptr == initialBlock)
        , fCount(0)
        , fInsertionIndexInBlock(0) {
        SkASSERT(itemsPerBlock > 0);
        fBlockSize = fItemSize * fItemsPerBlock;
        if (fOwnFirstBlock) {
            // This force us to allocate a new block on push_back().
            fInsertionIndexInBlock = fItemsPerBlock;
        } else {
            fBlocks.push_back() = initialBlock;
            fInsertionIndexInBlock = 0;
        }
    }

    /**
     * Adds an item and returns pointer to it.
     *
     * @return pointer to the added item.
     */
    void* push_back() {
        // we always have at least one block
        if (fItemsPerBlock == fInsertionIndexInBlock) {
            fBlocks.push_back() = sk_malloc_throw(fBlockSize);
            fInsertionIndexInBlock = 0;
        }
        void* ret = (char*)fBlocks.back() + fItemSize * fInsertionIndexInBlock;
        ++fCount;
        ++fInsertionIndexInBlock;
        return ret;
    }

    /**
     * Remove the last item, only call if count() != 0
     */
    void pop_back() {
        SkASSERT(fCount);
        SkASSERT(fInsertionIndexInBlock > 0);
        --fInsertionIndexInBlock;
        --fCount;
        if (0 == fInsertionIndexInBlock) {
            // Never delete the first block
            if (fBlocks.count() > 1) {
                sk_free(fBlocks.back());
                fBlocks.pop_back();
                fInsertionIndexInBlock = fItemsPerBlock;
            }
        }
    }

    /**
     * Removes all added items.
     */
    void reset() {
        int firstBlockToFree = fOwnFirstBlock ? 0 : 1;
        for (int i = firstBlockToFree; i < fBlocks.count(); ++i) {
            sk_free(fBlocks[i]);
        }
        if (fOwnFirstBlock) {
            fBlocks.reset();
            // This force us to allocate a new block on push_back().
            fInsertionIndexInBlock = fItemsPerBlock;
        } else {
            fBlocks.pop_back_n(fBlocks.count() - 1);
            fInsertionIndexInBlock = 0;
        }
        fCount = 0;
    }

    /**
     * Returns the item count.
     */
    int count() const {
        return fCount;
    }

    /**
     * Is the count 0?
     */
    bool empty() const { return 0 == fCount; }

    /**
     * Access first item, only call if count() != 0
     */
    void* front() {
        SkASSERT(fCount);
        SkASSERT(fInsertionIndexInBlock > 0);
        return (char*)(fBlocks.front());
    }

    /**
     * Access first item, only call if count() != 0
     */
    const void* front() const {
        SkASSERT(fCount);
        SkASSERT(fInsertionIndexInBlock > 0);
        return (const char*)(fBlocks.front());
    }

    /**
     * Access last item, only call if count() != 0
     */
    void* back() {
        SkASSERT(fCount);
        SkASSERT(fInsertionIndexInBlock > 0);
        return (char*)(fBlocks.back()) + (fInsertionIndexInBlock - 1) * fItemSize;
    }

    /**
     * Access last item, only call if count() != 0
     */
    const void* back() const {
        SkASSERT(fCount);
        SkASSERT(fInsertionIndexInBlock > 0);
        return (const char*)(fBlocks.back()) + (fInsertionIndexInBlock - 1) * fItemSize;
    }

    /**
     * Iterates through the allocator. This is faster than using operator[] when walking linearly
     * through the allocator.
     */
    class Iter {
    public:
        /**
         * Initializes the iterator. next() must be called before get().
         */
        Iter(const GrAllocator* allocator)
            : fAllocator(allocator)
            , fBlockIndex(-1)
            , fIndexInBlock(allocator->fItemsPerBlock - 1)
            , fItemIndex(-1) {}

        /**
         * Advances the iterator. Iteration is finished when next() returns false.
         */
        bool next() {
            ++fIndexInBlock;
            ++fItemIndex;
            if (fIndexInBlock == fAllocator->fItemsPerBlock) {
                ++fBlockIndex;
                fIndexInBlock = 0;
            }
            return fItemIndex < fAllocator->fCount;
        }

        /**
         * Gets the current iterator value. Call next() at least once before calling. Don't call
         * after next() returns false.
         */
        void* get() const {
            SkASSERT(fItemIndex >= 0 && fItemIndex < fAllocator->fCount);
            return (char*) fAllocator->fBlocks[fBlockIndex] + fIndexInBlock * fAllocator->fItemSize;
        }

    private:
        const GrAllocator* fAllocator;
        int                fBlockIndex;
        int                fIndexInBlock;
        int                fItemIndex;
    };

    /**
     * Access item by index.
     */
    void* operator[] (int i) {
        SkASSERT(i >= 0 && i < fCount);
        return (char*)fBlocks[i / fItemsPerBlock] +
               fItemSize * (i % fItemsPerBlock);
    }

    /**
     * Access item by index.
     */
    const void* operator[] (int i) const {
        SkASSERT(i >= 0 && i < fCount);
        return (const char*)fBlocks[i / fItemsPerBlock] +
               fItemSize * (i % fItemsPerBlock);
    }

protected:
    /**
     * Set first block of memory to write into.  Must be called before any other methods.
     * This requires that you have passed nullptr in the constructor.
     *
     * @param   initialBlock    optional memory to use for the first block.
     *                          Must be at least itemSize*itemsPerBlock sized.
     *                          Caller is responsible for freeing this memory.
     */
    void setInitialBlock(void* initialBlock) {
        SkASSERT(0 == fCount);
        SkASSERT(0 == fBlocks.count());
        SkASSERT(fItemsPerBlock == fInsertionIndexInBlock);
        fOwnFirstBlock = false;
        fBlocks.push_back() = initialBlock;
        fInsertionIndexInBlock = 0;
    }

    // For access to above function.
    template <typename T> friend class GrTAllocator;

private:
    static const int NUM_INIT_BLOCK_PTRS = 8;

    SkSTArray<NUM_INIT_BLOCK_PTRS, void*, true>   fBlocks;
    size_t                                        fBlockSize;
    size_t                                        fItemSize;
    int                                           fItemsPerBlock;
    bool                                          fOwnFirstBlock;
    int                                           fCount;
    int                                           fInsertionIndexInBlock;

    typedef SkNoncopyable INHERITED;
};

template <typename T> class GrTAllocator;
template <typename T> void* operator new(size_t, GrTAllocator<T>*);

template <typename T> class GrTAllocator : SkNoncopyable {
public:
    virtual ~GrTAllocator() { this->reset(); }

    /**
     * Create an allocator
     *
     * @param   itemsPerBlock   the number of items to allocate at once
     */
    explicit GrTAllocator(int itemsPerBlock)
        : fAllocator(sizeof(T), itemsPerBlock, nullptr) {}

    /**
     * Adds an item and returns it.
     *
     * @return the added item.
     */
    T& push_back() {
        void* item = fAllocator.push_back();
        SkASSERT(item);
        new (item) T;
        return *(T*)item;
    }

    T& push_back(const T& t) {
        void* item = fAllocator.push_back();
        SkASSERT(item);
        new (item) T(t);
        return *(T*)item;
    }

    template <typename... Args> T& emplace_back(Args&&... args) {
        void* item = fAllocator.push_back();
        SkASSERT(item);
        new (item) T(std::forward<Args>(args)...);
        return *(T*)item;
    }

    /**
     * Remove the last item, only call if count() != 0
     */
    void pop_back() {
        this->back().~T();
        fAllocator.pop_back();
    }

    /**
     * Removes all added items.
     */
    void reset() {
        int c = fAllocator.count();
        for (int i = 0; i < c; ++i) {
            ((T*)fAllocator[i])->~T();
        }
        fAllocator.reset();
    }

    /**
     * Returns the item count.
     */
    int count() const {
        return fAllocator.count();
    }

    /**
     * Is the count 0?
     */
    bool empty() const { return fAllocator.empty(); }

    /**
     * Access first item, only call if count() != 0
     */
    T& front() {
        return *(T*)fAllocator.front();
    }

    /**
     * Access first item, only call if count() != 0
     */
    const T& front() const {
        return *(T*)fAllocator.front();
    }

    /**
     * Access last item, only call if count() != 0
     */
    T& back() {
        return *(T*)fAllocator.back();
    }

    /**
     * Access last item, only call if count() != 0
     */
    const T& back() const {
        return *(const T*)fAllocator.back();
    }

    /**
     * Iterates through the allocator. This is faster than using operator[] when walking linearly
     * through the allocator.
     */
    class Iter {
    public:
        /**
         * Initializes the iterator. next() must be called before get() or ops * and ->.
         */
        Iter(const GrTAllocator* allocator) : fImpl(&allocator->fAllocator) {}

        /**
         * Advances the iterator. Iteration is finished when next() returns false.
         */
        bool next() { return fImpl.next(); }

        /**
         * Gets the current iterator value. Call next() at least once before calling. Don't call
         * after next() returns false.
         */
        T* get() const { return (T*) fImpl.get(); }

        /**
         * Convenience operators. Same rules for calling apply as get().
         */
        T& operator*() const { return *this->get(); }
        T* operator->() const { return this->get(); }

    private:
        GrAllocator::Iter fImpl;
    };

    /**
     * Access item by index.
     */
    T& operator[] (int i) {
        return *(T*)(fAllocator[i]);
    }

    /**
     * Access item by index.
     */
    const T& operator[] (int i) const {
        return *(const T*)(fAllocator[i]);
    }

protected:
    /*
     * Set first block of memory to write into.  Must be called before any other methods.
     *
     * @param   initialBlock    optional memory to use for the first block.
     *                          Must be at least size(T)*itemsPerBlock sized.
     *                          Caller is responsible for freeing this memory.
     */
    void setInitialBlock(void* initialBlock) {
        fAllocator.setInitialBlock(initialBlock);
    }

private:
    friend void* operator new<T>(size_t, GrTAllocator*);

    GrAllocator fAllocator;
    typedef SkNoncopyable INHERITED;
};

template <int N, typename T> class GrSTAllocator : public GrTAllocator<T> {
private:
    typedef GrTAllocator<T> INHERITED;

public:
    GrSTAllocator() : INHERITED(N) {
        this->setInitialBlock(fStorage.get());
    }

private:
    SkAlignedSTStorage<N, T> fStorage;
};

template <typename T> void* operator new(size_t size, GrTAllocator<T>* allocator) {
    return allocator->fAllocator.push_back();
}

// Skia doesn't use C++ exceptions but it may be compiled with them enabled. Having an op delete
// to match the op new silences warnings about missing op delete when a constructor throws an
// exception.
template <typename T> void operator delete(void*, GrTAllocator<T>*) {
    SK_ABORT("Invalid Operation");
}

#define GrNEW_APPEND_TO_ALLOCATOR(allocator_ptr, type_name, args) \
    new (allocator_ptr) type_name args

#endif
