#pragma once

#include <concepts> // std::unsigned_integral
#include <cstddef>  // std::size_t
#include <iterator> // std::distance
#include <utility>  // std::pair
#include <vector>   // Underlying arrays

namespace kuiper
{

namespace containers
{

    template<typename T>
        requires std::unsigned_integral<T>
    class sparse_set {
      public:
        using sparse_container_t = std::vector<T>;
        using dense_container_t  = std::vector<T>;

      public:
        // Capacity

        /// Reserve a number of slots in the underlying containers
        void reserve(std::size_t size) {
            if (size > m_capacity) {
                m_sparse.resize(size);
                m_dense.resize(size);
                m_capacity = size;
            }

            return;
        }

        // Modifiers

        /// Add an element to the set, returns `(dense index, inserted)` pair
        std::pair<std::size_t, bool> insert(const T& value) {
            // 1. Append to next slot in the dense array
            // 2. Record dense array index (current m_size) in sparse array
            // 3. Increment number of elements

            if (!contains(value)) {
                reserve(value + 1); // no-op if there's already enough space

                m_dense[m_size] = value;
                m_sparse[value] = m_size;
                ++m_size;

                return std::make_pair(std::distance(m_dense.cbegin(), m_dense.cbegin() + m_sparse[value]), true);
            } else {
                // Value already exists in set, return the index, but not inserted flag
                return std::make_pair(std::distance(m_dense.cbegin(), m_dense.cbegin() + m_sparse[value]), false);
            }
        }

        /// Remove an element from the set
        void erase(const T& value) {
            // 1. Swap element to be removed to the back of the dense array
            // 2. Update the dense array index for the value which was swapped
            // 3. Decrement number of elements

            if (contains(value)) {
                m_dense[m_sparse[value]]      = m_dense[m_size - 1];
                // m_dense[m_size - 1]           = m_dense[m_sparse[value]];
                m_sparse[m_dense[m_size - 1]] = m_sparse[value];
                --m_size;
            }

            return;
        }

        /// Clear all elements from the set
        void clear() {
            m_size = 0;
            return;
        }

        // Lookup

        /// Check if a value is in the set
        bool contains(const T& value) const {
            // First condition will short-circuit if `value` is out-of-range
            return !(value >= m_capacity) && (m_sparse[value] < m_size) && (m_dense[m_sparse[value]] == value);
        }

        // Observers

        /// Access to the underlying sparse container
        const sparse_container_t& sparse() const noexcept {
            return m_sparse;
        }

        /// Access to the underlying dense container
        const dense_container_t& dense() const noexcept {
            return m_dense;
        }

      private:
        sparse_container_t m_sparse {};
        dense_container_t  m_dense {};
        std::size_t        m_size     = 0; // Number of elements, also the next index into m_dense
        std::size_t        m_capacity = 0; // Number of elements + 1
    };

} // namespace containers

} // namespace kuiper
