/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */


#ifndef INCLUDE_ECXX_CTL_ARRAY_TPP
#error "this file cannot be included directly"
#endif
#undef INCLUDE_ECXX_CTL_ARRAY_TPP


#ifndef ECXX_CTL_ARRAY_TPP
#define ECXX_CTL_ARRAY_TPP


//
// A helper struct for array manipulations
//
template <typename ValueU, size_t OtherMaxCapacity>
struct ArrayHelper {
    typedef Array<ValueU, OtherMaxCapacity> ArrayType;
    typedef typename ArrayType::SizeType    SizeType;

    static inline void setArraySize(ArrayType& array, SizeType size) noexcept
    { array._size = size; }
};


//
// A simple array
//
template <typename ValueT, size_t MaxCapacity>
inline Array<ValueT, MaxCapacity>::Array() noexcept
: _size(0)
{}


template <typename ValueT, size_t MaxCapacity>
inline Array<ValueT, MaxCapacity>::Array(const Array& array) noexcept
: _size(0)
{ this->operator=(array); }


template <typename ValueT, size_t MaxCapacity>
inline Array<ValueT, MaxCapacity>::Array(Array&& array) noexcept
: _size(0)
{ this->operator=( eCxx::move(array) ); }


template <typename ValueT, size_t MaxCapacity>
inline Array<ValueT, MaxCapacity>::Array(InitializerList<ValueType> values) noexcept
: _size(0)
{ for(auto it = values.begin(); it != values.end(); ++it) pushBack(*it); }


template <typename ValueT, size_t MaxCapacity> template <size_t OtherMaxCapacity>
inline Array<ValueT, MaxCapacity>::Array(const Array<ValueType, OtherMaxCapacity>& array) noexcept
: _size(0)
{ this->operator=(array); }


template <typename ValueT, size_t MaxCapacity> template <size_t OtherMaxCapacity>
inline Array<ValueT, MaxCapacity>::Array(Array<ValueType, OtherMaxCapacity>&& array) noexcept
: _size(0)
{ this->operator=( eCxx::move(array) ); }


template <typename ValueT, size_t MaxCapacity> template <typename PGMMemCpyFunctorT>
inline Array<ValueT, MaxCapacity>::Array(PGMVoidPointer ptr, SizeType num) noexcept
: _size(0)
{
    typedef typename ValueType::ValueType PGMValueType;

          PGMValueType   dst;
    const PGMValueType*  src = reinterpret_cast<const PGMValueType*>(ptr.p);
    const SizeType       inc = 1;

    if(num > Capacity) num = Capacity;

    PGMMemCpyFunctorT functor;

    for(SizeType i = 0; i < num; ++i) {
        functor( &dst, src, sizeof(dst) );
        src += inc;

        pushBack( eCxx::move( ValueType(dst) ) );
    }
}


template <typename ValueT, size_t MaxCapacity> template <typename PGMMemCpyFunctorT>
inline Array<ValueT, MaxCapacity>::Array(PGMVoidPointerFar ptr, SizeType num) noexcept
: _size(0)
{
    typedef typename ValueType::ValueType PGMValueType;

#ifdef ECXX_PROGMEM_POINTER_FAR_TYPE_IS_UINT
          PGMValueType       dst;
          _PGMVoidPointerFar src = ptr.p;
    const SizeType           inc = sizeof(dst);
#else
          PGMValueType   dst;
    const PGMValueType*  src = reinterpret_cast<const PGMValueType*>(ptr.p);
    const SizeType       inc = 1;
#endif

    if(num > Capacity) num = Capacity;

    PGMMemCpyFunctorT functor;

    for(SizeType i = 0; i < num; ++i) {
        functor( &dst, src, sizeof(dst) );
        src += inc;

        pushBack( eCxx::move( ValueType(dst) ) );
    }
}


template <typename ValueT, size_t MaxCapacity>
inline Array<ValueT, MaxCapacity>::~Array() noexcept
{ clear(); }


template <typename ValueT, size_t MaxCapacity>
inline Array<ValueT, MaxCapacity>& Array<ValueT, MaxCapacity>::operator=(const Array& array) noexcept
{ return this->operator=<MaxCapacity>(array); }


template <typename ValueT, size_t MaxCapacity>
inline Array<ValueT, MaxCapacity>& Array<ValueT, MaxCapacity>::operator=(Array&& array) noexcept
{ return this->operator=<MaxCapacity>( eCxx::move(array) ); }


template <typename ValueT, size_t MaxCapacity> template <size_t OtherMaxCapacity>
inline Array<ValueT, MaxCapacity>& Array<ValueT, MaxCapacity>::operator=(const Array<ValueType, OtherMaxCapacity>& array) noexcept
{
    // Clear this array
    clear();

    // Copy the values
    const SizeType num = (array.size() > Capacity) ? Capacity : array.size();

    for(SizeType i = 0; i < num; ++i) {
        pushBack( array[i] );
    }

    // Done
    return *this;
}


template <typename ValueT, size_t MaxCapacity> template <size_t OtherMaxCapacity>
inline Array<ValueT, MaxCapacity>& Array<ValueT, MaxCapacity>::operator=(Array<ValueType, OtherMaxCapacity>&& array) noexcept
{
    // Clear this array
    clear();

    // Move the values
    const SizeType num = (array.size() > Capacity) ? Capacity : array.size();

    for(SizeType i = 0; i < num; ++i) {
        pushBack( eCxx::move(array[i]) );
    }

    // Done if all values from the source array are moved
    if(array.size() <= num) return *this;

    // Otherwise, shift the values within the source array
    for(SizeType i = num; i < array.size(); ++i) {
        array[i - num] = eCxx::move(array[i]);
    }

    ArrayHelper<ValueType, OtherMaxCapacity>::setArraySize(array, array.size() - num);

    // Done
    return *this;
}


template <typename ValueT, size_t MaxCapacity>
inline typename Array<ValueT, MaxCapacity>::SizeType Array<ValueT, MaxCapacity>::capacity() const noexcept
{ return Capacity; }


template <typename ValueT, size_t MaxCapacity>
inline typename Array<ValueT, MaxCapacity>::SizeType Array<ValueT, MaxCapacity>::size() const noexcept
{ return _size; }


template <typename ValueT, size_t MaxCapacity>
inline bool Array<ValueT, MaxCapacity>::empty() const noexcept
{ return !_size; }


template <typename ValueT, size_t MaxCapacity>
inline void Array<ValueT, MaxCapacity>::clear() noexcept
{
    for(SizeType i = 0; i < _size; ++i) {
        _array(i)->~ValueType();
    }

    _size = 0;
}


template <typename ValueT, size_t MaxCapacity>
inline void Array<ValueT, MaxCapacity>::resize(SizeType size) noexcept
{
    // If the new size is zero, clear the array
    if(!size) {
        clear();
        return;
    }

    // If the new size is the same, do nothing
    if(size == _size) return;

    // Decrease the size
    if(size < _size) {
        for(SizeType i = size; i < _size; ++i) {
            _array(i)->~ValueType();
        }
        _size = size;
        return;
    }

    // Increase the size
    if(size > Capacity) size = Capacity;

    for(SizeType i = _size; i < size; ++i) {
        new(_array(i)) ValueType();
    }
    _size = size;
}


template <typename ValueT, size_t MaxCapacity>
inline void Array<ValueT, MaxCapacity>::pushBack(const ValueType& value) noexcept
{
    if(_size == Capacity) return;

    new(_array(_size)) ValueType(value);
    ++_size;
}


template <typename ValueT, size_t MaxCapacity>
inline void Array<ValueT, MaxCapacity>::pushBack(ValueType&& value) noexcept
{
    if(_size == Capacity) return;

    new(_array(_size)) ValueType( eCxx::move(value) );
    ++_size;
}


template <typename ValueT, size_t MaxCapacity>
inline void Array<ValueT, MaxCapacity>::popBack() noexcept
{
    if(!_size) return;

    _array(_size)->~ValueType();
    --_size;
}


template <typename ValueT, size_t MaxCapacity>
inline bool Array<ValueT, MaxCapacity>::operator==(const Array& array) const noexcept
{
    if( _size != array.size() ) return false;

    ConstPointer a = _array(0);
    ConstPointer b = array.data();
    SizeType     n = _size;

    while(n--) if(*a++ != *b++) return false;

    return true;
}


template <typename ValueT, size_t MaxCapacity>
inline bool Array<ValueT, MaxCapacity>::operator!=(const Array& array) const noexcept
{ return !operator==(array); }


template <typename ValueT, size_t MaxCapacity> template <typename PGMMemCpyFunctorT>
inline bool Array<ValueT, MaxCapacity>::equalsTo(PGMVoidPointer ptr, SizeType num) const noexcept
{
    typedef typename ValueType::ValueType PGMValueType;

    if(_size != num) return false;

          PGMValueType   tmp;
    const PGMValueType*  src = reinterpret_cast<const PGMValueType*>(ptr.p);
    const SizeType       inc = 1;

    ConstPointer ref = _array(0);

    if(num > Capacity) num = Capacity;

    PGMMemCpyFunctorT functor;

    for(SizeType i = 0; i < num; ++i) {
        functor( &tmp, src, sizeof(tmp) );
        src += inc;

        if( *ref++ != ValueType(tmp) ) return false;
    }

    return true;
}


template <typename ValueT, size_t MaxCapacity> template <typename PGMMemCpyFunctorT>
inline bool Array<ValueT, MaxCapacity>::equalsTo(PGMVoidPointerFar ptr, SizeType num) const noexcept
{
    typedef typename ValueType::ValueType PGMValueType;

    if(_size != num) return false;

#ifdef ECXX_PROGMEM_POINTER_TYPE_IS_UINT
          PGMValueType       tmp;
          _PGMVoidPointerFar src = ptr.p;
    const SizeType           inc = sizeof(tmp);
#else
          PGMValueType   tmp;
    const PGMValueType*  src = reinterpret_cast<const PGMValueType*>(ptr.p);
    const SizeType       inc = 1;
#endif

    ConstPointer ref = _array(0);

    if(num > Capacity) num = Capacity;

    PGMMemCpyFunctorT functor;

    for(SizeType i = 0; i < num; ++i) {
        functor( &tmp, src, sizeof(tmp) );
        src += inc;

        if( *ref++ != ValueType(tmp) ) return false;
    }

    return true;
}


template <typename ValueT, size_t MaxCapacity>
inline typename Array<ValueT, MaxCapacity>::ConstPointer Array<ValueT, MaxCapacity>::data() const noexcept
{ return _array(0); }


template <typename ValueT, size_t MaxCapacity>
inline typename Array<ValueT, MaxCapacity>::Reference Array<ValueT, MaxCapacity>::front() noexcept
{ return *_array(0); }


template <typename ValueT, size_t MaxCapacity>
inline typename Array<ValueT, MaxCapacity>::ConstReference Array<ValueT, MaxCapacity>::front() const noexcept
{ return *_array(0); }


template <typename ValueT, size_t MaxCapacity>
inline typename Array<ValueT, MaxCapacity>::Reference Array<ValueT, MaxCapacity>::back() noexcept
{ return *_array(_size - 1); }


template <typename ValueT, size_t MaxCapacity>
inline typename Array<ValueT, MaxCapacity>::ConstReference Array<ValueT, MaxCapacity>::back() const noexcept
{ return *_array(_size - 1); }


template <typename ValueT, size_t MaxCapacity>
inline typename Array<ValueT, MaxCapacity>::Reference Array<ValueT, MaxCapacity>::operator[](SizeType index) noexcept
{ return *_array(index); }


template <typename ValueT, size_t MaxCapacity>
inline typename Array<ValueT, MaxCapacity>::ConstReference Array<ValueT, MaxCapacity>::operator[](SizeType index) const noexcept
{ return *_array(index); }


template <typename ValueT, size_t MaxCapacity>
inline typename Array<ValueT, MaxCapacity>::Iterator Array<ValueT, MaxCapacity>::begin() noexcept
{ return _array(0); }


template <typename ValueT, size_t MaxCapacity>
inline typename Array<ValueT, MaxCapacity>::Iterator Array<ValueT, MaxCapacity>::end() noexcept
{ return _array(_size); }


template <typename ValueT, size_t MaxCapacity>
inline typename Array<ValueT, MaxCapacity>::ConstIterator Array<ValueT, MaxCapacity>::cbegin() const noexcept
{ return _array(0); }


template <typename ValueT, size_t MaxCapacity>
inline typename Array<ValueT, MaxCapacity>::ConstIterator Array<ValueT, MaxCapacity>::cend() const noexcept
{ return _array(_size); }


#endif
