//
// MIT License
// Copyright (c) 2019 Jonathan R. Madsen
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED
// "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//
// ------------------------------------------------------------
// Tasking class header file
//
// Class Description:
//
// A class for fast allocation of objects to the heap through a pool of
// chunks organised as linked list. It's meant to be used by associating
// it to the object to be allocated and defining for it new and delete
// operators via MallocSingle() and FreeSingle() methods.
//      ---------------- TaskAllocator ----------------
//
// ------------------------------------------------------------

#pragma once

#include <cstddef>
#include <typeinfo>

#include "PTL/TaskAllocatorPool.hh"
#include "PTL/Threading.hh"

//--------------------------------------------------------------------------------------//

class TaskAllocatorBase
{
public:
    TaskAllocatorBase();
    virtual ~TaskAllocatorBase();
    virtual void        ResetStorage()                    = 0;
    virtual size_t      GetAllocatedSize() const          = 0;
    virtual int         GetNoPages() const                = 0;
    virtual size_t      GetPageSize() const               = 0;
    virtual void        IncreasePageSize(unsigned int sz) = 0;
    virtual const char* GetPoolType() const               = 0;
};

//--------------------------------------------------------------------------------------//

template <class Type>
class TaskAllocatorImpl : public TaskAllocatorBase
{
public:  // with description
    TaskAllocatorImpl();
    ~TaskAllocatorImpl();
    // Constructor & destructor

    inline Type* MallocSingle();
    inline void  FreeSingle(Type* anElement);
    // Malloc and Free methods to be used when overloading
    // new and delete operators in the client <Type> object

    inline void ResetStorage() override;
    // Returns allocated storage to the free store, resets allocator.
    // Note: contents in memory are lost using this call !

    inline size_t GetAllocatedSize() const override;
    // Returns the size of the total memory allocated
    inline int GetNoPages() const override;
    // Returns the total number of allocated pages
    inline size_t GetPageSize() const override;
    // Returns the current size of a page
    inline void IncreasePageSize(unsigned int sz) override;
    // Resets allocator and increases default page size of a given factor
    inline const char* GetPoolType() const override;
    // Returns the type_info Id of the allocated type in the pool

public:  // without description
    // This public section includes standard methods and types
    // required if the allocator is to be used as alternative
    // allocator for STL containers.
    // NOTE: the code below is a trivial implementation to make
    //       this class an STL compliant allocator.
    //       It is anyhow NOT recommended to use this class as
    //       alternative allocator for STL containers !

    typedef Type        value_type;
    typedef size_t      size_type;
    typedef ptrdiff_t   difference_type;
    typedef Type*       pointer;
    typedef const Type* const_pointer;
    typedef Type&       reference;
    typedef const Type& const_reference;

    template <class U>
    TaskAllocatorImpl(const TaskAllocatorImpl<U>& right) throw()
    : mem(right.mem)
    , tname(right.name())
    {
    }
    // Copy constructor

    pointer       address(reference r) const { return &r; }
    const_pointer address(const_reference r) const { return &r; }
    // Returns the address of values

    pointer allocate(size_type n, void* = 0)
    {
        // Allocates space for n elements of type Type, but does not initialise
        //
        Type* mem_alloc = 0;
        if(n == 1)
            mem_alloc = MallocSingle();
        else
            mem_alloc = static_cast<Type*>(::operator new(n * sizeof(Type)));
        return mem_alloc;
    }
    void deallocate(pointer p, size_type n)
    {
        // Deallocates n elements of type Type, but doesn't destroy
        //
        if(n == 1)
            FreeSingle(p);
        else
            ::operator delete((void*) p);
        return;
    }

    void construct(pointer p, const Type& val) { new((void*) p) Type(val); }
    // Initialises *p by val
    void destroy(pointer p) { p->~Type(); }
    // Destroy *p but doesn't deallocate

    size_type max_size() const throw()
    {
        // Returns the maximum number of elements that can be allocated
        //
        return 2147483647 / sizeof(Type);
    }

    template <class U>
    struct rebind
    {
        typedef TaskAllocatorImpl<U> other;
    };
    // Rebind allocator to type U

    TaskAllocatorPool mem;
    // Pool of elements of sizeof(Type)

private:
    const char* tname;
    // Type name identifier
};

//--------------------------------------------------------------------------------------//
//
//      Inherit from this class, e.g. MyClass : public TaskAllocator<MyClass>
//
//--------------------------------------------------------------------------------------//

template <typename Type>
class TaskAllocator : public TaskAllocatorImpl<Type>
{
public:
    typedef Type                    value_type;
    typedef size_t                  size_type;
    typedef ptrdiff_t               difference_type;
    typedef Type*                   pointer;
    typedef const Type*             const_pointer;
    typedef Type&                   reference;
    typedef const Type&             const_reference;
    typedef TaskAllocatorImpl<Type> allocator_type;

public:
    // define the new operator
    void* operator new(size_type)
    {
        return static_cast<void*>(get_allocator()->MallocSingle());
    }
    // define the delete operator
    void operator delete(void* ptr)
    {
        get_allocator()->FreeSingle(static_cast<pointer>(ptr));
    }

private:
    // currently disabled due to memory leak found via -fsanitize=leak
    // static function to get allocator
    static allocator_type* get_allocator()
    {
        typedef std::unique_ptr<allocator_type> allocator_ptr;
        ThreadLocalStatic allocator_ptr _allocator = allocator_ptr(new allocator_type);
        return _allocator.get();
    }
};

//--------------------------------------------------------------------------------------//
// Inline implementation

template <class Type>
TaskAllocatorImpl<Type>::TaskAllocatorImpl()
: mem(sizeof(Type))
, tname(typeid(Type).name())
{
}

// ************************************************************
// TaskAllocatorImpl destructor
// ************************************************************
//
template <class Type>
TaskAllocatorImpl<Type>::~TaskAllocatorImpl()
{
}

// ************************************************************
// MallocSingle
// ************************************************************
//
template <class Type>
Type*
TaskAllocatorImpl<Type>::MallocSingle()
{
    return static_cast<Type*>(mem.Alloc());
}

// ************************************************************
// FreeSingle
// ************************************************************
//
template <class Type>
void
TaskAllocatorImpl<Type>::FreeSingle(Type* anElement)
{
    mem.Free(anElement);
    return;
}

// ************************************************************
// ResetStorage
// ************************************************************
//
template <class Type>
void
TaskAllocatorImpl<Type>::ResetStorage()
{
    // Clear all allocated storage and return it to the free store
    //
    mem.Reset();
    return;
}

// ************************************************************
// GetAllocatedSize
// ************************************************************
//
template <class Type>
size_t
TaskAllocatorImpl<Type>::GetAllocatedSize() const
{
    return mem.Size();
}

// ************************************************************
// GetNoPages
// ************************************************************
//
template <class Type>
int
TaskAllocatorImpl<Type>::GetNoPages() const
{
    return mem.GetNoPages();
}

// ************************************************************
// GetPageSize
// ************************************************************
//
template <class Type>
size_t
TaskAllocatorImpl<Type>::GetPageSize() const
{
    return mem.GetPageSize();
}

// ************************************************************
// IncreasePageSize
// ************************************************************
//
template <class Type>
void
TaskAllocatorImpl<Type>::IncreasePageSize(unsigned int sz)
{
    ResetStorage();
    mem.GrowPageSize(sz);
}

// ************************************************************
// GetPoolType
// ************************************************************
//
template <class Type>
const char*
TaskAllocatorImpl<Type>::GetPoolType() const
{
    return tname;
}

// ************************************************************
// operator==
// ************************************************************
//
template <class T1, class T2>
bool
operator==(const TaskAllocatorImpl<T1>&, const TaskAllocatorImpl<T2>&) throw()
{
    return true;
}

// ************************************************************
// operator!=
// ************************************************************
//
template <class T1, class T2>
bool
operator!=(const TaskAllocatorImpl<T1>&, const TaskAllocatorImpl<T2>&) throw()
{
    return false;
}
