/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 <cstddef>
#include <iterator>
#include <list>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

namespace paimon {

template <typename K, typename V>
class LinkedHashMap {
 public:
    using IteratorType = typename std::list<std::pair<K, V>>::iterator;
    using ConstIteratorType = typename std::list<std::pair<K, V>>::const_iterator;

    LinkedHashMap() = default;
    LinkedHashMap(const LinkedHashMap& other) {
        order_ = other.order_;
        map_.clear();
        for (auto iter = order_.begin(); iter != order_.end(); ++iter) {
            map_[iter->first] = iter;
        }
    }

    LinkedHashMap& operator=(const LinkedHashMap& other) {
        order_ = other.order_;
        map_.clear();
        for (auto iter = order_.begin(); iter != order_.end(); ++iter) {
            map_[iter->first] = iter;
        }
        return *this;
    }

    bool operator==(const LinkedHashMap& other) const {
        if (this == &other) {
            return true;
        }
        return map_.size() == other.map_.size() && order_ == other.order_;
    }

    bool operator!=(const LinkedHashMap& other) const {
        return !(*this == other);
    }

    size_t size() const {
        return order_.size();
    }

    bool empty() const {
        return order_.empty();
    }

    IteratorType erase(const K& key) {
        auto map_iter = map_.find(key);
        if (map_iter == map_.end()) {
            return order_.end();
        }
        IteratorType order_iter = map_iter->second;
        map_.erase(map_iter);
        return order_.erase(order_iter);
    }

    IteratorType insert(const K& key, const V& value) {
        auto iter = map_.find(key);
        if (iter != map_.end()) {
            return iter->second;
        }
        return unsafe_insert(key, value);
    }

    IteratorType insert_or_assign(const K& key, const V& value) {
        auto iter = map_.find(key);
        if (iter != map_.end()) {
            // if key exists, update its value
            iter->second->second = value;
            return iter->second;
        }
        return unsafe_insert(key, value);
    }

    ConstIteratorType find(const K& key) const {
        auto iter = map_.find(key);
        if (iter != map_.end()) {
            return iter->second;
        }
        return order_.end();
    }

    ConstIteratorType begin() const {
        return order_.begin();
    }

    ConstIteratorType end() const {
        return order_.end();
    }

    V& operator[](const K& key) {
        auto iter = map_.find(key);
        if (iter != map_.end()) {
            // if key exists, return its value reference
            return iter->second->second;
        }
        return unsafe_insert(key, V())->second;
    }

    std::unordered_set<K> key_set() const {
        std::unordered_set<K> key_set;
        for (const auto& [key, _] : order_) {
            key_set.insert(key);
        }
        return key_set;
    }

    std::vector<K> key_vec() const {
        std::vector<K> key_vec;
        key_vec.reserve(order_.size());
        for (const auto& [key, _] : order_) {
            key_vec.push_back(key);
        }
        return key_vec;
    }

 private:
    IteratorType unsafe_insert(const K& key, const V& value) {
        auto new_iter = order_.insert(order_.end(), {key, value});
        map_[key] = new_iter;
        return new_iter;
    }

    // map_ {key, iterator in order}
    std::unordered_map<K, IteratorType> map_;
    std::list<std::pair<K, V>> order_;
};

}  // namespace paimon
