#pragma once
#include <cstdlib>
#include <utility>

//! Allocator Base class
//! subclasses must specify allocation and deallocation
class Allocator
{
    public:
        Allocator(void);
        Allocator(const Allocator&) = delete;
        virtual ~Allocator(void);

        virtual void *alloc_mem(size_t mem_size) = 0;
        virtual void dealloc_mem(void *memory) = 0;

        template <typename T, typename... Ts>
        T *alloc(Ts&&... ts)
        {
            void *data = alloc_mem(sizeof(T));
            if(!data)
                return nullptr;
            return new (data) T(std::forward<Ts>(ts)...);
        }

        template <typename T, typename... Ts>
        T *valloc(size_t len, Ts&&... ts)
        {
            T *data = (T*)alloc_mem(len*sizeof(T));
            if(!data)
                return nullptr;
            for(unsigned i=0; i<len; ++i)
                new ((void*)&data[i]) T(std::forward<Ts>(ts)...);

            return data;
        }

        template <typename T>
        void dealloc(T*&t)
        {
            if(t) {
                t->~T();
                dealloc_mem((void*)t);
                t = nullptr;
            }
        }

        //Destructor Free Version
        template <typename T>
        void devalloc(T*&t)
        {
            if(t) {
                dealloc_mem(t);
                t = nullptr;
            }
        }

        template <typename T>
        void devalloc(size_t elms, T*&t)
        {
            if(t) {
                for(size_t i=0; i<elms; ++i)
                    (t+i)->~T();

                dealloc_mem(t);
                t = nullptr;
            }
        }

    virtual void addMemory(void *, size_t mem_size) = 0;

    //Return true if the current pool cannot allocate n chunks of chunk_size
    virtual bool lowMemory(unsigned n, size_t chunk_size) const = 0;
    bool memFree(void *pool) const;

    //returns number of pools
    int memPools() const;

    int freePools() const;

    unsigned long long totalAlloced() const;

    struct AllocatorImpl *impl;
};

//! the allocator for normal use
class AllocatorClass : public Allocator
{
    public:
        void *alloc_mem(size_t mem_size);
        void dealloc_mem(void *memory);
        void addMemory(void *, size_t mem_size);
        bool lowMemory(unsigned n, size_t chunk_size) const;
        using Allocator::Allocator;
};
typedef AllocatorClass Alloc;

//! the dummy allocator, which does not allow any allocation
class DummyAllocator : public Allocator
{
    void not_allowed() const {
        throw "(de)allocation forbidden"; // TODO: std exception
    }
public:
    void *alloc_mem(size_t ) { return not_allowed(), nullptr; }
    void dealloc_mem(void* ) { not_allowed(); } // TODO: more functions?
    void addMemory(void *, size_t ) { not_allowed(); }
    bool lowMemory(unsigned , size_t ) const { return not_allowed(), true; }
    using Allocator::Allocator;
};

extern DummyAllocator DummyAlloc;

/**
 * General notes on Memory Allocation Within ZynAddSubFX
 * -----------------------------------------------------
 *
 *  - Parameter Objects Are never allocated within the realtime thread
 *  - Effects, notes and note subcomponents must be allocated with an allocator
 *  - 5M Chunks are used to give the allocator the memory it wants
 *  - If there are 3 chunks that are unused then 1 will be deallocated
 *  - The system will request more allocated space if 5x 1MB chunks cannot be
 *    allocated at any given time (this is likely huge overkill, but if this is
 *    satisfied, then a lot of note spamming would be needed to run out of
 *    space)
 *
 *   - Things will get a bit weird around the effects due to how pointer swaps
 *     occur
 *     * When a new Part instance is provided it may or may not come with some
 *       instrument effects
 *     * Merging blocks is an option, but one that is not going to likely be
 *       implmented too soon, thus all effects need to be reallocated when the
 *       pointer swap occurs
 *     * The old effect is extracted from the manager
 *     * A new one is constructed with a deep copy
 *     * The old one is returned to middleware for deallocation
 */
