/***************************************************************************
*                                                                          *
*  Copyright (C) Golaem S.A.  All Rights Reserved.                         *
*                                                                          *
***************************************************************************/

#pragma once

#include "glmAssert.h"
#include "glmTypes.h"
#include "glmMemory.h"
#ifdef GLM_IS_COMPILER_GCC
#include <cstring>
#endif

namespace glm
{
//NOTE: only supports pushing back values, no insert or remove

template <typename value_type>
class PODArrayOfArray
{

  public:
    GLM_CLASS_STAT_ID( glm::MemoryStatIds::GolaemShared);

    typedef value_type* iterator;
    typedef const value_type* const_iterator;

    explicit PODArrayOfArray(const MemAllocStatId memAllocStatId = getDefaultPODArrayMemoryStatId());
    explicit PODArrayOfArray(size_t array_capacity, size_t item_capacity, const MemAllocStatId memAllocStatId = getDefaultPODArrayMemoryStatId());

    ~PODArrayOfArray();

    PODArrayOfArray(const PODArrayOfArray& other);
    PODArrayOfArray& operator=(const PODArrayOfArray& other);

    value_type& at(size_t array_index, size_t item_index_in_array);
    const value_type& at(size_t array_index, size_t item_index_in_array) const;

    iterator begin(size_t array_index);
    const_iterator begin(size_t array_index) const;
    iterator end(size_t array_index);
    const_iterator end(size_t array_index) const;

    /// The number of arrays.
    size_t size_arrays() const;

    /// The number of items in the array.
    size_t size_items(size_t array_index) const;

    /// The number of items in all arrays.
    size_t size_items() const;

    /// The reserved number of arrays.
    size_t capacity_arrays() const;

    /// The reserved number of items in the array.
    size_t capacity_items(size_t array_index) const;

    /// The reserved number of items for all arrays.
    size_t capacity_items() const;

    /// Makes sure that the array has the specified array capacity and item capacity.
    void reserve(size_t array_capacity, size_t item_capacity);

    /// Removes all items in the array (does not free memory).
    void clear();

    /// Pushes 'size' empty items as a new array with max 'capacity', returns new array index.
    size_t push_back_array(size_t size, size_t capacity);

    /// Pushes 'size' items as a new array with max 'capacity'.
    void push_back_array(size_t size, size_t capacity, const value_type* items);

    /// Pushes the item to the end of the array.
    void push_back_item(size_t array_index, const value_type& item);

    /// Pushes the items to the end of the array.
    void push_back_items(size_t array_index, size_t size, const value_type* items);

  private:
    void copy(const glm::PODArrayOfArray<value_type>& other);

    /// Mark the start of new array before pushing the item, and returns the new array index
    size_t start_new_array();

    static GLM_FORCE_INLINE MemAllocStatId getDefaultPODArrayMemoryStatId()
    {
        return glm::MemoryStatIds::GolaemShared;
    }

    typedef size_t array_offset_type;

    array_offset_type* allocateArraysOffset(size_t array_capacity);
    void deallocateArraysOffset(array_offset_type*& array_data_offset);

    value_type* allocate(size_t capacity);
    void deallocate(value_type*& data);

    void set_capacity(size_t new_array_capacity, size_t new_item_capacity);

    size_t _arraySize;
    size_t _arrayCapacity;
    array_offset_type* _arraysDataPushOffset;
    array_offset_type* _arraysDataOffset;

    size_t _itemSize;
    size_t _itemCapacity;
    value_type* _data;

    MemAllocStatId _memAllocStatId;
};

//-----------------------------------------------------------------------------
template <typename value_type>
inline value_type* PODArrayOfArray<value_type>::allocate(size_t capacity)
{
	void* newValue = GLM_ALLOC(sizeof(value_type) * capacity, newValue, _memAllocStatId);
    return static_cast<value_type*>(newValue);
}

//-----------------------------------------------------------------------------
template <typename value_type>
inline void PODArrayOfArray<value_type>::deallocate(value_type*& data)
{
    GLM_FREE( data,  _memAllocStatId);
}

//-----------------------------------------------------------------------------
template <typename value_type>
inline typename PODArrayOfArray<value_type>::array_offset_type* PODArrayOfArray<value_type>::allocateArraysOffset(size_t array_capacity)
{
    //MemAllocStatId memAllocStatId = getDefaultPODArrayMemoryStatId();
    array_offset_type* arrayData = static_cast<array_offset_type*>(GLM_ALLOC_ALIGNED_NOPROFILING( sizeof(array_offset_type) * array_capacity,  GLM_ALIGNOF(array_offset_type),  memAllocStatId));
    //memset(arrayData, 0, sizeof(array_offset_type) * array_capacity);//NOTE: now done in clear()

    return arrayData;
}

//-----------------------------------------------------------------------------
template <typename value_type>
inline void PODArrayOfArray<value_type>::deallocateArraysOffset(typename PODArrayOfArray<value_type>::array_offset_type*& array_data_offset)
{
    GLM_FREE_NOPROFILING( array_data_offset,  _memAllocStatId);
}

//-----------------------------------------------------------------------------
template <typename value_type>
GLM_FORCE_INLINE PODArrayOfArray<value_type>::PODArrayOfArray(const MemAllocStatId memAllocStatId)
    : _arraySize(0)
    , _arrayCapacity(0)
    , _arraysDataPushOffset(NULL)
    , _arraysDataOffset(NULL)
    , _itemSize(0)
    , _itemCapacity(0)
    , _data(NULL)
	, _memAllocStatId(memAllocStatId)
{
    static_assert(std::is_pod<value_type>::value, "PODArrayOfArray requires a POD type");
}

//-----------------------------------------------------------------------------
template <typename value_type>
GLM_FORCE_INLINE PODArrayOfArray<value_type>::PODArrayOfArray(size_t array_capacity, size_t item_capacity, const MemAllocStatId memAllocStatId)
    : _arraySize(0)
    , _arrayCapacity(0)
    , _arraysDataPushOffset(NULL)
    , _arraysDataOffset(NULL)
    , _itemSize(0)
    , _itemCapacity(0)
    , _data(NULL)
	, _memAllocStatId(memAllocStatId)
{
    static_assert(std::is_pod<value_type>::value, "PODArrayOfArray requires a POD type");
    reserve(array_capacity, item_capacity);
}

//-----------------------------------------------------------------------------
template <typename value_type>
inline PODArrayOfArray<value_type>::~PODArrayOfArray()
{
    _itemSize = 0;
    if (_itemCapacity != 0)
    {
        _itemCapacity = 0;

        GLM_DEBUG_ASSERT(_data != NULL);
        deallocate(_data);
        _data = NULL;
    }
    GLM_DEBUG_ASSERT(_data == NULL);

    _arraySize = 0;
    if (_arrayCapacity != 0)
    {
        _arrayCapacity = 0;

        GLM_DEBUG_ASSERT(_arraysDataOffset != NULL);
        deallocateArraysOffset(_arraysDataOffset);
        _arraysDataOffset = NULL;

        GLM_DEBUG_ASSERT(_arraysDataPushOffset != NULL);
        deallocateArraysOffset(_arraysDataPushOffset);
        _arraysDataPushOffset = NULL;
    }
    GLM_DEBUG_ASSERT(_arraysDataPushOffset == NULL);
    GLM_DEBUG_ASSERT(_arraysDataOffset == NULL);
}

//-----------------------------------------------------------------------------
template <typename value_type>
void PODArrayOfArray<value_type>::copy(const glm::PODArrayOfArray<value_type>& other)
{
    reserve(other.capacity_arrays(), other.capacity_items());

    const size_t arrayCount = other.size_arrays();
    for (size_t arrayIndex = 0; arrayIndex < arrayCount; ++arrayIndex)
    {
        const size_t itemCapacity = other.capacity_items(arrayIndex);
        push_back_array(0, itemCapacity);
        GLM_DEBUG_ASSERT(size_arrays() == arrayIndex + 1);
        GLM_DEBUG_ASSERT(size_items(arrayIndex) == 0);
        GLM_DEBUG_ASSERT(capacity_items(arrayIndex) == itemCapacity);

        const size_t itemCount = other.size_items(arrayIndex);
        if (itemCount > 0)
        {
            push_back_items(arrayIndex, itemCount, other.begin(arrayIndex));
            GLM_DEBUG_ASSERT(size_items(arrayIndex) == itemCount);
        }
    }
}

//-----------------------------------------------------------------------------
template <typename value_type>
PODArrayOfArray<value_type>::PODArrayOfArray(const PODArrayOfArray<value_type>& other)
    : _arraySize(0)
    , _arrayCapacity(0)
    , _arraysDataPushOffset(NULL)
    , _arraysDataOffset(NULL)
    , _itemSize(0)
    , _itemCapacity(0)
    , _data(NULL)
	, _memAllocStatId(other._memAllocStatId)
{
    static_assert(std::is_pod<value_type>::value, "PODArrayOfArray requires a POD type");
    copy(other);
}

//-----------------------------------------------------------------------------
template <typename value_type>
PODArrayOfArray<value_type>& PODArrayOfArray<value_type>::operator=(const PODArrayOfArray<value_type>& other)
{
    if (this != &other)
    {
        clear();
        copy(other);
    }
    return *this;
}

//-----------------------------------------------------------------------------
template <typename value_type>
inline size_t PODArrayOfArray<value_type>::size_arrays() const
{
    return _arraySize;
}
//-----------------------------------------------------------------------------
template <typename value_type>
inline size_t PODArrayOfArray<value_type>::size_items(size_t array_index) const
{
    GLM_DEBUG_ASSERT(array_index < _arrayCapacity);
    GLM_DEBUG_ASSERT(array_index < _arraySize || _arraysDataOffset[array_index] == 0);
    size_t endArrayDataOffset = _arraysDataPushOffset[array_index];
    size_t startArrayDataOffset = _arraysDataOffset[array_index];

    return endArrayDataOffset - startArrayDataOffset;
}
//-----------------------------------------------------------------------------
template <typename value_type>
inline size_t PODArrayOfArray<value_type>::size_items() const
{
    return _itemSize;
}
//-----------------------------------------------------------------------------
template <typename value_type>
inline size_t PODArrayOfArray<value_type>::capacity_arrays() const
{
    return _arrayCapacity;
}
//-----------------------------------------------------------------------------
template <typename value_type>
inline size_t PODArrayOfArray<value_type>::capacity_items(size_t array_index) const
{
    GLM_DEBUG_ASSERT(array_index < _arrayCapacity);
    return _arraysDataOffset[array_index + 1] - _arraysDataOffset[array_index];
}
//-----------------------------------------------------------------------------
template <typename value_type>
inline size_t PODArrayOfArray<value_type>::capacity_items() const
{
    return _itemCapacity;
}

//-----------------------------------------------------------------------------
template <typename value_type>
inline value_type& PODArrayOfArray<value_type>::at(size_t array_index, size_t item_index_in_array)
{
    GLM_DEBUG_ASSERT(array_index < _arraySize);
    GLM_DEBUG_ASSERT(item_index_in_array < size_items(array_index));
    return _data[_arraysDataOffset[array_index] + item_index_in_array];
}

//-----------------------------------------------------------------------------
template <typename value_type>
inline const value_type& PODArrayOfArray<value_type>::at(size_t array_index, size_t item_index_in_array) const
{
    GLM_DEBUG_ASSERT(array_index < _arraySize);
    GLM_DEBUG_ASSERT(item_index_in_array < size_items(array_index));
    return _data[_arraysDataOffset[array_index] + item_index_in_array];
}

//-----------------------------------------------------------------------------
template <typename value_type>
inline value_type* PODArrayOfArray<value_type>::begin(size_t array_index)
{
    GLM_DEBUG_ASSERT(array_index < _arraySize);
    return _data + _arraysDataOffset[array_index];
}
//-----------------------------------------------------------------------------
template <typename value_type>
inline const value_type* PODArrayOfArray<value_type>::begin(size_t array_index) const
{
    GLM_DEBUG_ASSERT(array_index < _arraySize);
    return _data + _arraysDataOffset[array_index];
}
//-----------------------------------------------------------------------------
template <typename value_type>
inline value_type* PODArrayOfArray<value_type>::end(size_t array_index)
{
    GLM_DEBUG_ASSERT(array_index < _arraySize);
    return _data + _arraysDataOffset[array_index + 1];
}
//-----------------------------------------------------------------------------
template <typename value_type>
inline const value_type* PODArrayOfArray<value_type>::end(size_t array_index) const
{
    GLM_DEBUG_ASSERT(array_index < _arraySize);
    return _data + _arraysDataOffset[array_index + 1];
}

//-----------------------------------------------------------------------------
template <typename value_type>
inline void PODArrayOfArray<value_type>::reserve(size_t array_capacity, size_t item_capacity)
{
    GLM_DEBUG_ASSERT_MSG(_arraySize == 0 && _itemSize == 0, "Can't reserve PODArrayOfArrayStatic once it is NOT empty.");

    if (_arraySize == 0 && _itemSize == 0)
    {
        set_capacity(array_capacity, item_capacity);
    }
}

//-----------------------------------------------------------------------------
template <typename value_type>
void PODArrayOfArray<value_type>::set_capacity(size_t new_array_capacity, size_t new_item_capacity)
{
    GLM_DEBUG_ASSERT(_arraySize == 0 && _itemSize == 0);
    GLM_DEBUG_ASSERT(new_array_capacity != _arrayCapacity || new_item_capacity != _itemCapacity);

    array_offset_type* new_arrays_offset = NULL;
    array_offset_type* new_arrays_push_offset = NULL;
    if (new_array_capacity > 0)
    {
        new_arrays_push_offset = allocateArraysOffset(new_array_capacity);
        new_arrays_offset = allocateArraysOffset(new_array_capacity + 1); //HACK: storing end of array data offset in last item to speed up array capacity computation
        new_arrays_offset[new_array_capacity] = 0;
    }
    deallocateArraysOffset(_arraysDataOffset);
    deallocateArraysOffset(_arraysDataPushOffset);
    _arraysDataPushOffset = new_arrays_push_offset;
    _arraysDataOffset = new_arrays_offset;
    _arrayCapacity = new_array_capacity;

    value_type* new_data = 0;
    if (new_item_capacity > 0)
    {
        new_data = allocate(new_item_capacity);
    }
    deallocate(_data);
    _data = new_data;
    _itemCapacity = new_item_capacity;

    clear();

    GLM_DEBUG_ASSERT(_arraySize == 0);
    GLM_DEBUG_ASSERT(_itemSize == 0);
}

//-----------------------------------------------------------------------------
template <typename value_type>
inline void PODArrayOfArray<value_type>::clear()
{
    if (_arrayCapacity > 0)
    {
        GLM_DEBUG_ASSERT(_arraysDataPushOffset != NULL);
        memset(_arraysDataPushOffset, 0, sizeof(array_offset_type) * _arrayCapacity);
        GLM_DEBUG_ASSERT(_arraysDataOffset != NULL);
        memset(_arraysDataOffset, 0, sizeof(array_offset_type) * _arrayCapacity);
        _arraysDataOffset[0] = _itemCapacity;
    }

    _arraySize = 0;
    _itemSize = 0;
}

//-----------------------------------------------------------------------------
template <typename value_type>
size_t PODArrayOfArray<value_type>::start_new_array()
{
    const size_t newArrayIndex = _arraySize;
    GLM_DEBUG_ASSERT(_arraysDataOffset[newArrayIndex] == _itemCapacity);
    GLM_DEBUG_ASSERT(_arraysDataPushOffset[newArrayIndex] == 0);
    GLM_DEBUG_ASSERT(_arraySize < _arrayCapacity);
    _arraysDataOffset[newArrayIndex] = _itemSize;
    _arraysDataPushOffset[newArrayIndex] = _itemSize;
    ++_arraySize;

    const size_t nextArrayIndex = _arraySize;
    _arraysDataOffset[nextArrayIndex] = _itemCapacity;

    return newArrayIndex;
}

//-----------------------------------------------------------------------------
template <typename value_type>
inline void PODArrayOfArray<value_type>::push_back_item(size_t array_index, const value_type& item)
{
    GLM_DEBUG_ASSERT(size_items(array_index) + 1 <= capacity_items(array_index));
    _data[_arraysDataPushOffset[array_index]++] = item;
}

//-----------------------------------------------------------------------------
template <typename value_type>
inline void PODArrayOfArray<value_type>::push_back_items(size_t array_index, size_t size, const value_type* items)
{
    GLM_DEBUG_ASSERT(size_items(array_index) + size <= capacity_items(array_index));

    GLM_DEBUG_ASSERT(items != NULL);
    memcpy(_data + _arraysDataPushOffset[array_index], items, sizeof(value_type) * size);

    _arraysDataPushOffset[array_index] += size;
}

//-----------------------------------------------------------------------------
template <typename value_type>
inline size_t PODArrayOfArray<value_type>::push_back_array(size_t size, size_t capacity)
{
    const size_t newArrayIndex = start_new_array();

    GLM_DEBUG_ASSERT(size_items(newArrayIndex) + size <= capacity_items(newArrayIndex));
    _arraysDataPushOffset[newArrayIndex] += size;

    GLM_DEBUG_ASSERT(_itemSize + capacity <= _itemCapacity);
    _itemSize += capacity;

    return newArrayIndex;
}

//-----------------------------------------------------------------------------
template <typename value_type>
inline void PODArrayOfArray<value_type>::push_back_array(size_t size, size_t capacity, const value_type* items)
{
    const size_t newArrayIndex = push_back_array(0, capacity);

    if (size > 0)
    {
        push_back_items(newArrayIndex, size, items);
    }
}
} // namespace glm
