// Copyright 2025 Snowflake Inc.
// SPDX-License-Identifier: Apache-2.0
//
// 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.

#pragma once

#include <cassert>
#include <climits>
#include <cstdint>
#include <iterator>
#include <new>
#include <stdexcept>
#include <type_traits>
#include <utility>

template <class, bool>
class Int32MapIterator;

/*
 * A simple hash map with int32_t keys that's designed to be fast and compact:
 *   - Open addressing with triangular probing allows high load factors.
 *   - Iteration is very fast and cache-friendly to allow fast speculation.
 *   - int32_t should be all we need to store token and sequence IDs.
 */
template <class T>
class Int32Map {
public:

    Int32Map() = default;

    Int32Map(Int32Map&& o) noexcept
        : slots_(o.slots_), cap_(o.cap_), size_(o.size_), tombstones_(o.tombstones_) {
        o.slots_ = nullptr;
        o.cap_ = o.size_ = o.tombstones_ = 0;
    }

    Int32Map& operator=(Int32Map&& o) noexcept {
        if (this == &o) {
            return *this;
        }
        destroy_all_();
        delete[] slots_;
        slots_ = o.slots_;
        cap_ = o.cap_;
        size_ = o.size_;
        tombstones_ = o.tombstones_;
        o.slots_ = nullptr;
        o.cap_ = o.size_ = o.tombstones_ = 0;
        return *this;
    }

    Int32Map(const Int32Map&) = delete;

    Int32Map& operator=(const Int32Map&) = delete;

    ~Int32Map() {
        destroy_all_();
        delete[] slots_;
    }

    bool empty() const noexcept {
        return size_ == 0;
    }

    uint32_t size() const noexcept {
        return size_;
    }

    bool contains(int32_t key) const {
        if (key == KEY_EMPTY || key == KEY_TOMBSTONE) {
            throw std::invalid_argument("invalid key");
        }
        if (!slots_) {
            return false;
        }
        uint32_t idx;
        return probe_insert_or_find_(key, idx);
    }

    bool erase(int32_t key) {
        if (key == KEY_EMPTY || key == KEY_TOMBSTONE) {
            throw std::invalid_argument("invalid key");
        }
        if (!slots_) {
            return false;
        }
        uint32_t idx;
        if (!probe_insert_or_find_(key, idx)) {
            return false;
        }
        value_ptr_(slots_[idx])->~T();
        slots_[idx].key = KEY_TOMBSTONE;
        --size_;
        ++tombstones_;
        maybe_rehash_after_erase_();
        return true;
    }

    // Construct in-place if absent, otherwise return existing.
    template <class... Args>
    T& emplace(int32_t key, Args&&... args) {
        if (key == KEY_EMPTY || key == KEY_TOMBSTONE) {
            throw std::invalid_argument("invalid key");
        }

        // Allocate minimal table if needed.
        if (!slots_) {
            cap_ = MIN_CAPACITY;
            slots_ = new Slot[cap_];
            size_ = tombstones_ = 0;
        }

        // Probe once.
        uint32_t idx;
        if (probe_insert_or_find_(key, idx)) {
            return *value_ptr_(slots_[idx]);  // already present
        }

        // If we can reuse a tombstone, do it immediately without rehash.
        if (slots_[idx].key == KEY_TOMBSTONE) {
            --tombstones_;
            slots_[idx].key = key;
            ::new (static_cast<void*>(&slots_[idx].storage)) T(std::forward<Args>(args)...);
            ++size_;
            return *value_ptr_(slots_[idx]);
        }

        // We will use an EMPTY slot, which increases (size + tombstones).
        if (static_cast<uint64_t>(cap_) * MAX_LOAD_PCT <
                static_cast<uint64_t>(size_ + tombstones_ + 1) * 100) {
            // Will exceed max load factor after insert, we need to rehash.
            if (static_cast<uint64_t>(size_ + 1) * 100 <=
                    static_cast<uint64_t>(cap_) * MAX_LOAD_PCT) {
                // Load would fit without tombstones, prefer same-cap cleanup.
                rehash_(cap_);
            } else {
                // Grow capacity to the next power of 2.
                rehash_(cap_ * 2);
            }
            // Re-probe after rehash.
            bool found = probe_insert_or_find_(key, idx);
            assert(!found);  // Re-hashing should not change the key set.
        }

        assert(slots_[idx].key == KEY_EMPTY);  // Must have an empty slot now.

        slots_[idx].key = key;
        ::new (static_cast<void*>(&slots_[idx].storage)) T(std::forward<Args>(args)...);
        ++size_;
        return *value_ptr_(slots_[idx]);
    }

    // Default-construct in-place if absent, otherwise return existing.
    T& operator[](int32_t key) {
        return emplace(key);
    }

    size_t memory_usage() const noexcept {
        return sizeof(*this) + sizeof(Slot) * cap_;
    }

    /* Iterators */

    friend class Int32MapIterator<T, false>;
    friend class Int32MapIterator<T, true>;

    using iterator = Int32MapIterator<T, false>;
    using const_iterator = Int32MapIterator<T, true>;

    iterator begin() {
        return iterator(this, 0);
    }

    iterator end() {
        return iterator(this, cap_);
    }

    const_iterator begin() const {
        return const_iterator(this, 0);
    }

    const_iterator end() const {
        return const_iterator(this, cap_);
    }

    const_iterator cbegin() const {
        return const_iterator(this, 0);
    }

    const_iterator cend() const {
        return const_iterator(this, cap_);
    }

    iterator find(int32_t key) {
        if (key == KEY_EMPTY || key == KEY_TOMBSTONE) {
            throw std::invalid_argument("invalid key");
        }
        if (!slots_) {
            return end();
        }
        uint32_t idx;
        if (!probe_insert_or_find_(key, idx)) {
            return end();
        }
        return iterator(this, idx);
    }

    const_iterator find(int32_t key) const {
        if (key == KEY_EMPTY || key == KEY_TOMBSTONE) {
            throw std::invalid_argument("invalid key");
        }
        if (!slots_) {
            return end();
        }
        uint32_t idx;
        if (!probe_insert_or_find_(key, idx)) {
            return end();
        }
        return const_iterator(this, idx);
    }

private:
    // Reserved key representing an empty slot.
    static constexpr int32_t KEY_EMPTY = INT32_MIN;

    // Reserved key representing a deleted slot (tombstone).
    static constexpr int32_t KEY_TOMBSTONE = INT32_MIN + 1;

    // Keep 2 * MIN_LOAD_PCT < MAX_LOAD_PCT with some buffer to avoid thrashing.
    static constexpr uint32_t MIN_LOAD_PCT = 25;
    static constexpr uint32_t MAX_LOAD_PCT = 75;

    // Capacity must be a power of 2 for triangular probing to cover all indices.
    static constexpr uint32_t MIN_CAPACITY = 2;

    struct Slot {
        int32_t key;
        typename std::aligned_storage<sizeof(T), alignof(T)>::type storage;
        Slot() noexcept : key(KEY_EMPTY) {}
    };

    // ----- Data (1 pointer + 3x uint32_t) -----
    Slot* slots_ = nullptr;
    uint32_t cap_ = 0;  // capacity, power of two (0 == unallocated)
    uint32_t size_ = 0;  // number of FILLED
    uint32_t tombstones_ = 0;  // number of TOMBSTONE

    // ----- Helpers -----
    static bool is_filled_(int32_t k) noexcept {
        return k != KEY_EMPTY && k != KEY_TOMBSTONE;
    }

    static uint32_t mix_hash_(int32_t key) noexcept {
        // 32-bit mix (Murmur-inspired)
        uint32_t x = static_cast<uint32_t>(key);
        x ^= x >> 16;
        x *= 0x7feb352dU;
        x ^= x >> 15;
        x *= 0x846ca68bU;
        x ^= x >> 16;
        return x;
    }

    static T* value_ptr_(Slot& s) noexcept {
        return std::launder(reinterpret_cast<T*>(&s.storage));
    }

    static const T* value_ptr_(const Slot& s) noexcept {
        return std::launder(reinterpret_cast<const T*>(&s.storage));
    }

    void destroy_all_() noexcept {
        if (!slots_) {
            return;
        }
        for (uint32_t i = 0; i < cap_; ++i) {
            if (is_filled_(slots_[i].key)) {
                value_ptr_(slots_[i])->~T();
                slots_[i].key = KEY_EMPTY;
            }
        }
        size_ = tombstones_ = 0;
    }

    void maybe_rehash_after_erase_() {
        if (!slots_) {
            return;
        }

        // If completely empty: free everything and return.
        if (size_ == 0) {
            delete[] slots_;
            slots_ = nullptr;
            cap_ = size_ = tombstones_ = 0;
            return;
        }

        // If too sparse, shrink by 1/2.
        if (static_cast<uint64_t>(size_) * 100 <
               static_cast<uint64_t>(cap_) * MIN_LOAD_PCT) {
            if (cap_ / 2 >= MIN_CAPACITY) {
                rehash_(cap_ / 2);
            }
        }
    }

    // Either finds existing (true, idx_out set) or returns best insert slot (false).
    // On "not found", idx_out is: first tombstone if any; else first empty.
    bool probe_insert_or_find_(int32_t key, uint32_t& idx_out) const {
        assert(slots_ && cap_ > 0 && "probe on uninitialized map");
        uint32_t idx  = mix_hash_(key) & (cap_ - 1);
        uint32_t step = 0;
        bool has_first_tomb = false;
        uint32_t first_tomb_idx = 0;
        for (uint32_t probes = 0; probes < cap_; ++probes) {
            int32_t k = slots_[idx].key;
            if (k == key) {
                idx_out = idx;
                return true;
            }
            if (k == KEY_EMPTY) {
                idx_out = has_first_tomb ? first_tomb_idx : idx;
                return false;
            }
            if (k == KEY_TOMBSTONE && !has_first_tomb) {
                first_tomb_idx = idx;
                has_first_tomb = true;
            }
            ++step;
            idx = (idx + step) & (cap_ - 1);  // triangular probing
        }
        if (!has_first_tomb) {
            // This should never happen if load factor is correctly maintained.
            throw std::runtime_error("Int32Map is full");
        }
        idx_out = first_tomb_idx;
        return false;
    }

    template <class U>
    static void place_new_(Slot* arr, uint32_t cap, int32_t key, U&& val) {
        uint32_t idx  = mix_hash_(key) & (cap - 1);
        uint32_t step = 0;
        for (uint32_t probes = 0; probes < cap; ++probes) {
            int32_t k = arr[idx].key;
            if (k == KEY_EMPTY || k == KEY_TOMBSTONE) {
                arr[idx].key = key;
                ::new (static_cast<void*>(&arr[idx].storage)) T(std::forward<U>(val));
                return;
            }
            ++step;
            idx = (idx + step) & (cap - 1);
        }
        assert(false && "rehash placement failed");
    }

    void rehash_(uint32_t new_cap) {
        assert((new_cap & (new_cap - 1)) == 0 && new_cap >= MIN_CAPACITY);
        Slot* fresh = new Slot[new_cap];  // keys default to KEY_EMPTY

        if (slots_) {
            for (uint32_t i = 0; i < cap_; ++i) {
                auto& s = slots_[i];
                if (!is_filled_(s.key)) {
                    continue;
                }
                int32_t k = s.key;
                T* v  = value_ptr_(s);
                place_new_(fresh, new_cap, k, std::move(*v));
                v->~T();
                s.key = KEY_EMPTY;
            }
            delete[] slots_;
        }

        slots_ = fresh;
        cap_ = new_cap;
        tombstones_ = 0;  // cleaned
        // size_ unchanged
    }
};


template <class T, bool IsConst>
class Int32MapIterator {
public:
    using map_type = std::conditional_t<IsConst, const Int32Map<T>, Int32Map<T>>;
    using ref_type = std::conditional_t<IsConst, const T&, T&>;
    using value_type = std::pair<int32_t, ref_type>;
    using difference_type = std::ptrdiff_t;
    using iterator_category = std::forward_iterator_tag;

    struct arrow_proxy {
        value_type value;
        const value_type* operator->() const {
            return &value;
        }
    };

    Int32MapIterator() : m_(nullptr), i_(0) {}
    Int32MapIterator(map_type* m, uint32_t i) : m_(m), i_(i) { advance_(); }

    value_type operator*() const {
        auto& s = m_->slots_[i_];
        return { s.key, *m_->value_ptr_(s) };
    }

    arrow_proxy operator->() const {
        auto& s = m_->slots_[i_];
        return arrow_proxy{ { s.key, *m_->value_ptr_(s) } };
    }

    Int32MapIterator& operator++() {
        ++i_;
        advance_();
        return *this;
    }

    template<bool C>
    bool operator==(const Int32MapIterator<T, C>& o) const {
        return m_ == o.m_ && i_ == o.i_;
    }

    template<bool C>
    bool operator!=(const Int32MapIterator<T, C>& o) const {
        return !(*this == o);
    }

private:
    void advance_() {
        uint32_t c = m_ ? m_->cap_ : 0u;
        while (m_ && i_ < c && !m_->is_filled_(m_->slots_[i_].key)) {
            ++i_;
        }
    }
    map_type* m_;
    uint32_t i_;
};
