/*
 * 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 2019 Danny Robson <danny@nerdcruft.net>
 */

#pragma once

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

#include <array>
#include <utility>
#include <stdexcept>
#include <functional>

#include <cstddef>


namespace cruft::map {
    /// A flat map structure with a static capacity store and dynamic size.
    template <
        std::size_t SizeV,
        typename KeyT,
        typename ValueT,
        typename ComparatorT = std::equal_to<>
    >
    class fixed  {
    public:
        static constexpr auto elements = SizeV;

        using key_type = KeyT;
        using mapped_type = ValueT;
        using value_type = std::pair<KeyT,ValueT>;

        using iterator = value_type*;


        ///////////////////////////////////////////////////////////////////////
        fixed () = default;


        //---------------------------------------------------------------------
        fixed (std::initializer_list<value_type> keyvals)
        {
            if (keyvals.size () > capacity ())
                throw std::bad_alloc ();

            for (auto const &kv: keyvals) {
                auto const &[pos,success] = insert (kv);
                CHECK (success);
                (void)pos;
                (void)success;
            }
        }


        fixed (fixed&&) noexcept;
        fixed& operator= (fixed&&) noexcept;


        fixed (fixed const&);
        fixed& operator= (fixed const&);


        ~fixed () { clear (); }


        ///////////////////////////////////////////////////////////////////////
        mapped_type& at (KeyT const &key) &
        {
            ComparatorT cmp {};
            for (auto &i: *this)
                if (cmp (i.first, key))
                    return i.second;

            throw std::out_of_range ("Element out of range");
        }


        //---------------------------------------------------------------------
        mapped_type const& at (KeyT const &key) const&
        {
            ComparatorT cmp {};
            for (auto &i: *this)
                if (cmp (i.first, key))
                    return i.second;

            throw std::out_of_range ("Element out of range");
        }


        //---------------------------------------------------------------------
        std::pair<iterator,bool> insert (value_type const &keyval)
        {
            ComparatorT cmp {};
            for (auto &i: *this) {
                if (cmp (i.first, keyval.first)) {
                    return { &i, false };
                }
            }

            if (m_size >= capacity ())
                throw std::bad_alloc ();

            auto ptr = m_store.data + m_size;
            new (ptr) value_type (keyval);
            ++m_size;

            return { ptr, true };
        }


        //---------------------------------------------------------------------
        mapped_type const& operator[] (KeyT const &key) const
        {
            ComparatorT cmp {};
            for (auto &i: *this)
                if (cmp (i.first, key))
                    return i.second;
            throw std::out_of_range ("Invalid key");
        }


        //---------------------------------------------------------------------
        mapped_type& operator[] (KeyT const &key)
        {
            ComparatorT cmp {};
            for (auto &i: *this)
                if (cmp (i.first, key))
                    return i.second;

            if (m_size >= capacity ())
                throw std::bad_alloc ();

            auto ptr = m_store.data + m_size;
            new (ptr) value_type ({ key, {} });
            ++m_size;

            return ptr->second;
        }


        ///////////////////////////////////////////////////////////////////////
        void clear (void)
        {
            for (auto i: *this)
                i.~value_type ();
            m_size = 0;
        }


        //---------------------------------------------------------------------
        auto begin (void)& { return std::begin (m_store.data); }
        auto end   (void)& { return begin () + m_size; }

        auto begin (void) const& { return std::begin (m_store.data); }
        auto end   (void) const& { return begin () + m_size; }


        //---------------------------------------------------------------------
        auto size (void) const { return m_size; }
        static auto capacity (void) { return elements; }


    private:
        std::size_t m_size = 0;

        union storage {
             storage () {};
            ~storage () {};

            char defer;
            value_type data[elements];
        } m_store;
    };
}