/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed 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.
 */

#ifndef TNT_UTILS_STRUCTUREOFARRAYS_H
#define TNT_UTILS_STRUCTUREOFARRAYS_H

#include <array>        // note: this is safe, see how std::array is used below (inline / private)
#include <cstddef>
#include <functional>
#include <utility>

#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#include <utils/Allocator.h>
#include <utils/compiler.h>
#include <utils/EntityInstance.h>
#include <utils/Slice.h>

namespace utils {

template <typename Allocator, typename ... Elements>
class StructureOfArraysBase {
    // number of elements
    static constexpr const size_t kArrayCount = sizeof...(Elements);

public:
    using SoA = StructureOfArraysBase<Allocator, Elements ...>;

    // Type of the Nth array
    template<size_t N>
    using TypeAt = typename std::tuple_element<N, std::tuple<Elements...>>::type;

    // Number of arrays
    static constexpr size_t getArrayCount() noexcept { return kArrayCount; }

    // Size needed to store "size" array elements
    static size_t getNeededSize(size_t size) noexcept {
        return getOffset(kArrayCount - 1, size) + sizeof(TypeAt<kArrayCount - 1>) * size;
    }

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

    class Structure;
    template<typename T> class Iterator;
    using iterator = Iterator<StructureOfArraysBase*>;
    using const_iterator = Iterator<StructureOfArraysBase const*>;
    using size_type = size_t;
    using difference_type = ptrdiff_t;

    /*
     * An object that represents a reference to the type dereferenced by iterator.
     * In other words, it's the return type of iterator::operator*(), and since it
     * cannot be a C++ reference (&), it's an object that acts like it.
     */
    class StructureRef {
        friend class Structure;
        friend iterator;
        friend const_iterator;
        StructureOfArraysBase* const UTILS_RESTRICT soa;
        size_t const index;

        StructureRef(StructureOfArraysBase* soa, size_t index) : soa(soa), index(index) { }

        // assigns a value_type to a reference (i.e. assigns to what's pointed to by the reference)
        template<size_t ... Is>
        StructureRef& assign(Structure const& rhs, std::index_sequence<Is...>);

        // assigns a value_type to a reference (i.e. assigns to what's pointed to by the reference)
        template<size_t ... Is>
        StructureRef& assign(Structure&& rhs, std::index_sequence<Is...>) noexcept;

        // objects pointed to by reference can be swapped, so provide the special swap() function.
        friend void swap(StructureRef lhs, StructureRef rhs) {
            lhs.soa->swap(lhs.index, rhs.index);
        }

    public:
        // references can be created by copy-assignment only
        StructureRef(StructureRef const& rhs) noexcept : soa(rhs.soa), index(rhs.index) { }

        // copy the content of a reference to the content of this one
        StructureRef& operator=(StructureRef const& rhs);

        // move the content of a reference to the content of this one
        StructureRef& operator=(StructureRef&& rhs) noexcept;

        // copy a value_type to the content of this reference
        StructureRef& operator=(Structure const& rhs) {
            return assign(rhs, std::make_index_sequence<kArrayCount>());
        }

        // move a value_type to the content of this reference
        StructureRef& operator=(Structure&& rhs) noexcept {
            return assign(rhs, std::make_index_sequence<kArrayCount>());
        }

        // access the elements of this reference (i.e. the "fields" of the structure)
        template<size_t I> TypeAt<I> const& get() const { return soa->elementAt<I>(index); }
        template<size_t I> TypeAt<I>& get() { return soa->elementAt<I>(index); }
    };


    /*
     * The value_type of iterator. This is basically the "structure" of the SoA.
     * Internally we're using a tuple<> to store the data.
     * This object is not trivial to construct, as it copies an entry of the SoA.
     */
    class Structure {
        friend class StructureRef;
        friend iterator;
        friend const_iterator;
        using Type = std::tuple<typename std::decay<Elements>::type...>;
        Type elements;

        template<size_t ... Is>
        static Type init(StructureRef const& rhs, std::index_sequence<Is...>) {
            return Type{ rhs.soa->template elementAt<Is>(rhs.index)... };
        }

        template<size_t ... Is>
        static Type init(StructureRef&& rhs, std::index_sequence<Is...>) noexcept {
            return Type{ std::move(rhs.soa->template elementAt<Is>(rhs.index))... };
        }

    public:
        Structure(Structure const& rhs) = default;
        Structure(Structure&& rhs) noexcept = default;
        Structure& operator=(Structure const& rhs) = default;
        Structure& operator=(Structure&& rhs) noexcept = default;

        // initialize and assign from a StructureRef
        Structure(StructureRef const& rhs)
                : elements(init(rhs, std::make_index_sequence<kArrayCount>())) {}
        Structure(StructureRef&& rhs) noexcept
                : elements(init(rhs, std::make_index_sequence<kArrayCount>())) {}
        Structure& operator=(StructureRef const& rhs) { return operator=(Structure(rhs)); }
        Structure& operator=(StructureRef&& rhs) noexcept { return operator=(Structure(rhs)); }

        // access the elements of this value_Type (i.e. the "fields" of the structure)
        template<size_t I> TypeAt<I> const& get() const { return std::get<I>(elements); }
        template<size_t I> TypeAt<I>& get() { return std::get<I>(elements); }
    };


    /*
     * An iterator to the SoA. This is only intended to be used with STL's algorithm, e.g.: sort().
     * Normally, SoA is not iterated globally, but rather an array at a time.
     * Iterating itself is not too costly, as well as dereferencing by reference. However,
     * dereferencing by value is.
     */
    template<typename CVQualifiedSOAPointer>
    class Iterator {
        friend class StructureOfArraysBase;
        CVQualifiedSOAPointer soa; // don't use restrict, can have aliases if multiple iterators are created
        size_t index;

        Iterator(CVQualifiedSOAPointer soa, size_t index) : soa(soa), index(index) {}

    public:
        using value_type = Structure;
        using reference = StructureRef;
        using pointer = StructureRef*;    // FIXME: this should be a StructurePtr type
        using difference_type = ptrdiff_t;
        using iterator_category = std::random_access_iterator_tag;

        Iterator(Iterator const& rhs) noexcept = default;
        Iterator& operator=(Iterator const& rhs) = default;

        reference operator*() const { return { soa, index }; }
        reference operator*() { return { soa, index }; }
        reference operator[](size_t n) { return *(*this + n); }

        template<size_t I> TypeAt<I> const& get() const { return soa->template elementAt<I>(index); }
        template<size_t I> TypeAt<I>& get() { return soa->template elementAt<I>(index); }

        Iterator& operator++() { ++index; return *this; }
        Iterator& operator--() { --index; return *this; }
        Iterator& operator+=(size_t n) { index += n; return *this; }
        Iterator& operator-=(size_t n) { index -= n; return *this; }
        Iterator operator+(size_t n) const { return { soa, index + n }; }
        Iterator operator-(size_t n) const { return { soa, index - n }; }
        difference_type operator-(Iterator const& rhs) const { return index - rhs.index; }
        bool operator==(Iterator const& rhs) const { return (index == rhs.index); }
        bool operator!=(Iterator const& rhs) const { return (index != rhs.index); }
        bool operator>=(Iterator const& rhs) const { return (index >= rhs.index); }
        bool operator> (Iterator const& rhs) const { return (index >  rhs.index); }
        bool operator<=(Iterator const& rhs) const { return (index <= rhs.index); }
        bool operator< (Iterator const& rhs) const { return (index <  rhs.index); }

        // Postfix operator needed by Microsoft STL.
        const Iterator operator++(int) { Iterator it(*this); index++; return it; }
        const Iterator operator--(int) { Iterator it(*this); index--; return it; }
    };

    iterator begin() noexcept { return { this, 0u }; }
    iterator end() noexcept { return { this, mSize }; }
    const_iterator begin() const noexcept { return { this, 0u }; }
    const_iterator end() const noexcept { return { this, mSize }; }

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

    StructureOfArraysBase() = default;

    explicit StructureOfArraysBase(size_t capacity) {
        setCapacity(capacity);
    }

    // not copyable for now
    StructureOfArraysBase(StructureOfArraysBase const& rhs) = delete;
    StructureOfArraysBase& operator=(StructureOfArraysBase const& rhs) = delete;

    // movability is trivial, so support it
    StructureOfArraysBase(StructureOfArraysBase&& rhs) noexcept {
        using std::swap;
        swap(mCapacity, rhs.mCapacity);
        swap(mSize, rhs.mSize);
        swap(mArrayOffset, rhs.mArrayOffset);
        swap(mAllocator, rhs.mAllocator);
    }

    StructureOfArraysBase& operator=(StructureOfArraysBase&& rhs) noexcept {
        if (this != &rhs) {
            using std::swap;
            swap(mCapacity, rhs.mCapacity);
            swap(mSize, rhs.mSize);
            swap(mArrayOffset, rhs.mArrayOffset);
            swap(mAllocator, rhs.mAllocator);
        }
        return *this;
    }

    ~StructureOfArraysBase() {
        destroy_each(0, mSize);
        mAllocator.free(mArrayOffset[0]);
    }

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

    // return the size the array
    size_t size() const noexcept {
        return mSize;
    }

    // return the capacity of the array
    size_t capacity() const noexcept {
        return mCapacity;
    }

    // set the capacity of the array. the capacity cannot be smaller than the current size,
    // the call is a no-op in that case.
    UTILS_NOINLINE
    void setCapacity(size_t capacity) {
        // allocate enough space for "capacity" elements of each array
        // capacity cannot change when optional storage is specified
        if (capacity >= mSize) {
            const size_t sizeNeeded = getNeededSize(capacity);
            void* buffer = mAllocator.alloc(sizeNeeded);

            // move all the items (one array at a time) from the old allocation to the new
            // this also update the array pointers
            move_each(buffer, capacity);

            // free the old buffer
            std::swap(buffer, mArrayOffset[0]);
            mAllocator.free(buffer);

            // and make sure to update the capacity
            mCapacity = capacity;
        }
    }

    void ensureCapacity(size_t needed) {
        if (UTILS_UNLIKELY(needed > mCapacity)) {
            // not enough space, increase the capacity
            const size_t capacity = (needed * 3 + 1) / 2;
            setCapacity(capacity);
        }
    }

    // grow or shrink the array to the given size. When growing, new elements are constructed
    // with their default constructor. when shrinking, discarded elements are destroyed.
    // If the arrays don't have enough capacity, the capacity is increased accordingly
    // (the capacity is set to 3/2 of the asked size).
    UTILS_NOINLINE
    void resize(size_t needed) {
        ensureCapacity(needed);
        resizeNoCheck(needed);
        if (needed <= mCapacity) {
            // TODO: see if we should shrink the arrays
        }
    }

    void clear() noexcept {
        resizeNoCheck(0);
    }


    inline void swap(size_t i, size_t j) noexcept {
        forEach([i, j](auto p) {
            using std::swap;
            swap(p[i], p[j]);
        });
    }

    // remove and destroy the last element of each array
    inline void pop_back() noexcept {
        if (mSize) {
            destroy_each(mSize - 1, mSize);
            mSize--;
        }
    }

    // create an element at the end of each array
    StructureOfArraysBase& push_back() noexcept {
        resize(mSize + 1);
        return *this;
    }

    StructureOfArraysBase& push_back(Elements const& ... args) noexcept {
        ensureCapacity(mSize + 1);
        return push_back_unsafe(args...);
    }

    StructureOfArraysBase& push_back(Elements&& ... args) noexcept {
        ensureCapacity(mSize + 1);
        return push_back_unsafe(std::forward<Elements>(args)...);
    }

    StructureOfArraysBase& push_back_unsafe(Elements const& ... args) noexcept {
        const size_t last = mSize++;
        size_t i = 0;
        int UTILS_UNUSED dummy[] = {
                (new(getArray<Elements>(i) + last)Elements(args), i++, 0)... };
        return *this;
    }

    StructureOfArraysBase& push_back_unsafe(Elements&& ... args) noexcept {
        const size_t last = mSize++;
        size_t i = 0;
        int UTILS_UNUSED dummy[] = {
                (new(getArray<Elements>(i) + last)Elements(std::forward<Elements>(args)), i++, 0)... };
        return *this;
    }

    template<typename F, typename ... ARGS>
    void forEach(F&& f, ARGS&& ... args) {
        size_t i = 0;
        int UTILS_UNUSED dummy[] = {
                (f(getArray<Elements>(i), std::forward<ARGS>(args)...), i++, 0)... };
    }

    // return a pointer to the first element of the ElementIndex]th array
    template<size_t ElementIndex>
    TypeAt<ElementIndex>* data() noexcept {
        return getArray<TypeAt<ElementIndex>>(ElementIndex);
    }

    template<size_t ElementIndex>
    TypeAt<ElementIndex> const* data() const noexcept {
        return getArray<TypeAt<ElementIndex>>(ElementIndex);
    }

    template<size_t ElementIndex>
    TypeAt<ElementIndex>* begin() noexcept {
        return getArray<TypeAt<ElementIndex>>(ElementIndex);
    }

    template<size_t ElementIndex>
    TypeAt<ElementIndex> const* begin() const noexcept {
        return getArray<TypeAt<ElementIndex>>(ElementIndex);
    }

    template<size_t ElementIndex>
    TypeAt<ElementIndex>* end() noexcept {
        return getArray<TypeAt<ElementIndex>>(ElementIndex) + size();
    }

    template<size_t ElementIndex>
    TypeAt<ElementIndex> const* end() const noexcept {
        return getArray<TypeAt<ElementIndex>>(ElementIndex) + size();
    }

    template<size_t ElementIndex>
    Slice<TypeAt<ElementIndex>> slice() noexcept {
        return { begin<ElementIndex>(), end<ElementIndex>() };
    }

    template<size_t ElementIndex>
    Slice<const TypeAt<ElementIndex>> slice() const noexcept {
        return { begin<ElementIndex>(), end<ElementIndex>() };
    }

    // return a reference to the index'th element of the ElementIndex'th array
    template<size_t ElementIndex>
    TypeAt<ElementIndex>& elementAt(size_t index) noexcept {
        return data<ElementIndex>()[index];
    }

    template<size_t ElementIndex>
    TypeAt<ElementIndex> const& elementAt(size_t index) const noexcept {
        return data<ElementIndex>()[index];
    }

    // return a reference to the last element of the ElementIndex'th array
    template<size_t ElementIndex>
    TypeAt<ElementIndex>& back() noexcept {
        return data<ElementIndex>()[size() - 1];
    }

    template<size_t ElementIndex>
    TypeAt<ElementIndex> const& back() const noexcept {
        return data<ElementIndex>()[size() - 1];
    }

    template <size_t E>
    struct Field {
        SoA& soa;
        EntityInstanceBase::Type i;
        using Type = typename SoA::template TypeAt<E>;

        UTILS_ALWAYS_INLINE Field& operator = (Field&& rhs) noexcept {
            soa.elementAt<E>(i) = soa.elementAt<E>(rhs.i);
            return *this;
        }

        // auto-conversion to the field's type
        UTILS_ALWAYS_INLINE operator Type&() noexcept {
            return soa.elementAt<E>(i);
        }
        UTILS_ALWAYS_INLINE operator Type const&() const noexcept {
            return soa.elementAt<E>(i);
        }
        // dereferencing the selected field
        UTILS_ALWAYS_INLINE Type& operator ->() noexcept {
            return soa.elementAt<E>(i);
        }
        UTILS_ALWAYS_INLINE Type const& operator ->() const noexcept {
            return soa.elementAt<E>(i);
        }
        // address-of the selected field
        UTILS_ALWAYS_INLINE Type* operator &() noexcept {
            return &soa.elementAt<E>(i);
        }
        UTILS_ALWAYS_INLINE Type const* operator &() const noexcept {
            return &soa.elementAt<E>(i);
        }
        // assignment to the field
        UTILS_ALWAYS_INLINE Type const& operator = (Type const& other) noexcept {
            return (soa.elementAt<E>(i) = other);
        }
        UTILS_ALWAYS_INLINE Type const& operator = (Type&& other) noexcept {
            return (soa.elementAt<E>(i) = other);
        }
        // comparisons
        UTILS_ALWAYS_INLINE bool operator==(Type const& other) const {
            return (soa.elementAt<E>(i) == other);
        }
        UTILS_ALWAYS_INLINE bool operator!=(Type const& other) const {
            return (soa.elementAt<E>(i) != other);
        }
        // calling the field
        template <typename ... ARGS>
        UTILS_ALWAYS_INLINE decltype(auto) operator()(ARGS&& ... args) noexcept {
            return soa.elementAt<E>(i)(std::forward<ARGS>(args)...);
        }
        template <typename ... ARGS>
        UTILS_ALWAYS_INLINE decltype(auto) operator()(ARGS&& ... args) const noexcept {
            return soa.elementAt<E>(i)(std::forward<ARGS>(args)...);
        }
    };

private:
    template<typename T>
    T const* getArray(size_t arrayIndex) const {
        return static_cast<T const*>(mArrayOffset[arrayIndex]);
    }

    template<typename T>
    T* getArray(size_t arrayIndex) {
        return static_cast<T*>(mArrayOffset[arrayIndex]);
    }

    inline void resizeNoCheck(size_t needed) noexcept {
        assert(mCapacity >= needed);
        if (needed < mSize) {
            // we shrink the arrays
            destroy_each(needed, mSize);
        } else if (needed > mSize) {
            // we grow the arrays
            construct_each(mSize, needed);
        }
        // record the new size of the arrays
        mSize = needed;
    }

    // this calculate the offset adjusted for all data alignment of a given array
    static inline size_t getOffset(size_t index, size_t capacity) noexcept {
        auto offsets = getOffsets(capacity);
        return offsets[index];
    }

    static inline std::array<size_t, kArrayCount> getOffsets(size_t capacity) noexcept {
        // compute the required size of each array
        const size_t sizes[] = { (sizeof(Elements) * capacity)... };

        // we align each array to the same alignment guaranteed by malloc
        const size_t align = alignof(std::max_align_t);

        // hopefully most of this gets unrolled and inlined
        std::array<size_t, kArrayCount> offsets;
        offsets[0] = 0;
        #pragma unroll
        for (size_t i = 1; i < kArrayCount; i++) {
            size_t unalignment = sizes[i - 1] % align;
            size_t alignment = unalignment ? (align - unalignment) : 0;
            offsets[i] = offsets[i - 1] + (sizes[i - 1] + alignment);
        }
        return offsets;
    }

    void construct_each(size_t from, size_t to) noexcept {
        forEach([from, to](auto p) {
            using T = typename std::decay<decltype(*p)>::type;
            // note: scalar types like int/float get initialized to zero
            for (size_t i = from; i < to; i++) {
                new(p + i) T();
            }
        });
    }

    void destroy_each(size_t from, size_t to) noexcept {
        forEach([from, to](auto p) {
            using T = typename std::decay<decltype(*p)>::type;
            for (size_t i = from; i < to; i++) {
                p[i].~T();
            }
        });
    }

    void move_each(void* buffer, size_t capacity) noexcept {
        auto offsets = getOffsets(capacity);
        size_t index = 0;
        if (mSize) {
            auto size = mSize; // placate a compiler warning
            forEach([buffer, &index, &offsets, size](auto p) {
                using T = typename std::decay<decltype(*p)>::type;
                T* UTILS_RESTRICT b = static_cast<T*>(buffer);

                // go through each element and move them from the old array to the new
                // then destroy them from the old array
                T* UTILS_RESTRICT const arrayPointer =
                        reinterpret_cast<T*>(uintptr_t(b) + offsets[index]);

                // for trivial cases, just call memcpy()
                if (std::is_trivially_copyable<T>::value &&
                    std::is_trivially_destructible<T>::value) {
                    memcpy(arrayPointer, p, size * sizeof(T));
                } else {
                    for (size_t i = 0; i < size; i++) {
                        // we move an element by using the in-place move-constructor
                        new(arrayPointer + i) T(std::move(p[i]));
                        // and delete them by calling the destructor directly
                        p[i].~T();
                    }
                }
                index++;
            });
        }

        // update the pointers (the first offset will be filled later
        for (size_t i = 1; i < kArrayCount; i++) {
            mArrayOffset[i] = (char*)buffer + offsets[i];
        }
    }

    // capacity in array elements
    size_t mCapacity = 0;
    // size in array elements
    size_t mSize = 0;
    // N pointers to each arrays
    void *mArrayOffset[kArrayCount] = { nullptr };
    Allocator mAllocator;
};

template<typename Allocator, typename... Elements>
inline
typename StructureOfArraysBase<Allocator, Elements...>::StructureRef&
StructureOfArraysBase<Allocator, Elements...>::StructureRef::operator=(
        StructureOfArraysBase::StructureRef const& rhs) {
    return operator=(Structure(rhs));
}

template<typename Allocator, typename... Elements>
inline
typename StructureOfArraysBase<Allocator, Elements...>::StructureRef&
StructureOfArraysBase<Allocator, Elements...>::StructureRef::operator=(
        StructureOfArraysBase::StructureRef&& rhs) noexcept {
    return operator=(Structure(rhs));
}

template<typename Allocator, typename... Elements>
template<size_t... Is>
inline
typename StructureOfArraysBase<Allocator, Elements...>::StructureRef&
StructureOfArraysBase<Allocator, Elements...>::StructureRef::assign(
        StructureOfArraysBase::Structure const& rhs, std::index_sequence<Is...>) {
    // implements StructureRef& StructureRef::operator=(Structure const& rhs)
    auto UTILS_UNUSED l = { (soa->elementAt<Is>(index) = std::get<Is>(rhs.elements), 0)... };
    return *this;
}

template<typename Allocator, typename... Elements>
template<size_t... Is>
inline
typename StructureOfArraysBase<Allocator, Elements...>::StructureRef&
StructureOfArraysBase<Allocator, Elements...>::StructureRef::assign(
        StructureOfArraysBase::Structure&& rhs, std::index_sequence<Is...>) noexcept {
    // implements StructureRef& StructureRef::operator=(Structure&& rhs) noexcept
    auto UTILS_UNUSED l = {
            (soa->elementAt<Is>(index) = std::move(std::get<Is>(rhs.elements)), 0)... };
    return *this;
}

template <typename ... Elements>
using StructureOfArrays = StructureOfArraysBase<HeapArena<>, Elements ...>;

} // namespace utils

#endif // TNT_UTILS_STRUCTUREOFARRAYS_H

