/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright 2017 Danny Robson <danny@nerdcruft.net>
 */

#pragma once

#include "../debug/assert.hpp"
#include "../iterator/zip.hpp"

#include <iterator>

#include <cstddef>


namespace cruft {
    /// An array-like object with capacity fixed at instantiation time, and a
    /// size which is variable at runtime.
    template <std::size_t CapacityV, typename ValueT>
    class darray {
    public:
        using value_type = ValueT;
        using size_type = std::size_t;
        static constexpr auto elements = CapacityV;

        using pointer = ValueT*;
        using const_pointer = ValueT const*;

        using iterator = pointer;
        using const_iterator = const_pointer;

        darray (): m_size (0) { ; }
        darray (darray const&) = default;
        darray (darray &&) noexcept (std::is_trivial_v<ValueT>) = default;

        darray& operator= (darray const&) noexcept (std::is_nothrow_copy_assignable_v<ValueT>) = default;
        darray& operator= (darray &&) noexcept (std::is_nothrow_move_assignable_v<ValueT>)= default;

        ~darray ()
        {
            for (auto &i: *this)
                i.~ValueT ();
        }

        darray (std::initializer_list<ValueT> init):
            m_size (init.size ())
        {
            CHECK_LE (init.size (), CapacityV);

            for (auto &&[idx, src]: cruft::iterator::izip (init))
                m_data.objects[idx] = std::move (src);
        }


        template <typename InputT>
        darray (InputT first, InputT last):
            darray ()
        {
            for ( ; first != last; ++first) {
                CHECK_LT (m_size, CapacityV);
                m_data.objects[m_size++] = *first;
            }
        }

        ValueT& operator[] (size_type idx)& noexcept
        {
            CHECK_LT (idx, m_size);
            return m_data.objects[idx];
        }

        ValueT const& operator[] (size_type idx) const& noexcept
        {
            CHECK_LT (idx, m_size);
            return m_data.objects[idx];
        }


        iterator begin (void)& { return m_data.objects + 0;      }
        iterator end   (void)& { return m_data.objects + m_size; }

        const_iterator begin (void) const& { return m_data.objects + 0;      }
        const_iterator end   (void) const& { return m_data.objects + m_size; }

        decltype(auto) cbegin (void) const& { return begin (); }
        decltype(auto) cend   (void) const& { return end   (); }

        constexpr value_type* data (void)& noexcept
        {
            return m_data.objects + 0;
        }

        constexpr value_type const* data (void) const& noexcept
        {
            return m_data.objects + 0;
        }

        size_type size (void) const noexcept { return m_size; }
        constexpr auto capacity (void) const noexcept { return CapacityV; }
        constexpr auto remain (void) const noexcept { return capacity () - size (); }

        constexpr void
        resize (size_type const count, value_type const &value)
        {
            if (count < m_size) {
                while (m_size > count) {
                    --m_size;
                    m_data.objects[m_size].~ValueT ();
                }
                return;
            }

            if (count > m_size) {
                for ( ; m_size < count; ++m_size)
                    new (m_data.objects + m_size) ValueT (value);
                return;
            }
        }

        constexpr void resize (size_type count) { return resize (count, value_type {}); }

        constexpr bool empty (void) const noexcept { return m_size == 0; }
        constexpr bool full (void) const noexcept { return m_size == CapacityV; }


        void erase (iterator pos)
        {
            CHECK_GE (pos, begin ());
            CHECK_LT (pos, end   ());

            for (auto cursor = pos + 1; cursor != end (); ++cursor)
                *(cursor - 1)  = std::move (*cursor);

            --m_size;
        }


        /// Insert one copy of `value' before `pos'
        iterator insert (iterator pos, ValueT const &val)
        {
            return insert (pos, 1u, val);
        }


        /// Insert `count' copies of `value' before `pos'.
        iterator insert (const_iterator pos, size_type count, ValueT const &val)
        {
            // Ensure we have enough space
            CHECK_GE (pos, begin ());
            CHECK_LT (pos, end   ());
            CHECK_LE (m_size, CapacityV - count);

            std::move_backward (pos, cend (), end () + count);
            m_size += count;

            auto dst = const_cast<iterator> (pos);
            std::fill_n (dst, count, val);

            return dst;
        }


        iterator push_back (value_type const &val)&
        {
            CHECK_LT (m_size, CapacityV);
            m_data.objects[m_size] = val;
            return m_data.objects + m_size++;
        }


        iterator push_back (value_type &&val)&
        {
            CHECK_LT (m_size, CapacityV);
            m_data.objects[m_size] = std::move (val);
            return m_data.objects + m_size++;
        }


    private:
        union alignas (ValueT) storage_t {
             storage_t () { };

            char store[sizeof (ValueT) * CapacityV];
            ValueT objects[CapacityV];
        } m_data;

        size_type m_size;
    };


    //-------------------------------------------------------------------------
    template <
        std::size_t SizeA, typename ValueA,
        std::size_t SizeB, typename ValueB
    >
    constexpr auto
    operator== (
        darray<SizeA,ValueA> const &a,
        darray<SizeB,ValueB> const &b
    ) {
        return std::equal (
            a.begin (), a.end (),
            b.begin (), b.end ()
        );
    }


    //-------------------------------------------------------------------------
    template <
        std::size_t SizeA, typename ValueA,
        std::size_t SizeB, typename ValueB
    >
    constexpr auto
    operator!= (
        darray<SizeA,ValueA> const &a,
        darray<SizeB,ValueB> const &b
    ) {
        return !(a == b);
    }
}