/*
 * Copyright 2020 The Android Open Source Project
 *
 * 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 <ftl/initializer_list.h>
#include <ftl/optional.h>
#include <ftl/small_vector.h>

#include <algorithm>
#include <functional>
#include <type_traits>
#include <utility>

namespace android::ftl {

// Associative container with unique, unordered keys. Unlike std::unordered_map, key-value pairs are
// stored in contiguous storage for cache efficiency. The map is allocated statically until its size
// exceeds N, at which point mappings are relocated to dynamic memory. The try_emplace operation has
// a non-standard analogue try_replace that destructively emplaces. The API also defines an in-place
// counterpart to insert_or_assign: emplace_or_replace. Lookup is done not via a subscript operator,
// but immutable getters that can optionally transform the value.
//
// SmallMap<K, V, 0> unconditionally allocates on the heap.
//
// Example usage:
//
//   ftl::SmallMap<int, std::string, 3> map;
//   assert(map.empty());
//   assert(!map.dynamic());
//
//   map = ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?');
//   assert(map.size() == 3u);
//   assert(!map.dynamic());
//
//   assert(map.contains(123));
//   assert(map.get(42).transform([](const std::string& s) { return s.size(); }) == 3u);
//
//   const auto opt = map.get(-1);
//   assert(opt);
//
//   std::string& ref = *opt;
//   assert(ref.empty());
//   ref = "xyz";
//
//   map.emplace_or_replace(0, "vanilla", 2u, 3u);
//   assert(map.dynamic());
//
//   assert(map == SmallMap(ftl::init::map(-1, "xyz"sv)(0, "nil"sv)(42, "???"sv)(123, "abc"sv)));
//
template <typename K, typename V, std::size_t N, typename KeyEqual = std::equal_to<K>>
class SmallMap final {
  using Map = SmallVector<std::pair<const K, V>, N>;

  template <typename, typename, std::size_t, typename>
  friend class SmallMap;

 public:
  using key_type = K;
  using mapped_type = V;

  using value_type = typename Map::value_type;
  using size_type = typename Map::size_type;
  using difference_type = typename Map::difference_type;

  using reference = typename Map::reference;
  using iterator = typename Map::iterator;

  using const_reference = typename Map::const_reference;
  using const_iterator = typename Map::const_iterator;

  // Creates an empty map.
  SmallMap() = default;

  // Constructs at most N key-value pairs in place by forwarding per-pair constructor arguments.
  // The template arguments K, V, and N are inferred using the deduction guide defined below.
  // The syntax for listing pairs is as follows:
  //
  //   ftl::SmallMap map = ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?');
  //   static_assert(std::is_same_v<decltype(map), ftl::SmallMap<int, std::string, 3>>);
  //
  // The types of the key and value are deduced if the first pair contains exactly two arguments:
  //
  //   ftl::SmallMap map = ftl::init::map(0, 'a')(1, 'b')(2, 'c');
  //   static_assert(std::is_same_v<decltype(map), ftl::SmallMap<int, char, 3>>);
  //
  template <typename U, std::size_t... Sizes, typename... Types>
  SmallMap(InitializerList<U, std::index_sequence<Sizes...>, Types...>&& list)
      : map_(std::move(list)) {
    deduplicate();
  }

  // Copies or moves key-value pairs from a convertible map.
  template <typename Q, typename W, std::size_t M, typename E>
  SmallMap(SmallMap<Q, W, M, E> other) : map_(std::move(other.map_)) {}

  static constexpr size_type static_capacity() { return N; }

  size_type max_size() const { return map_.max_size(); }
  size_type size() const { return map_.size(); }
  bool empty() const { return map_.empty(); }

  // Returns whether the map is backed by static or dynamic storage.
  bool dynamic() const {
    if constexpr (static_capacity() > 0) {
      return map_.dynamic();
    } else {
      return true;
    }
  }

  iterator begin() { return map_.begin(); }
  const_iterator begin() const { return cbegin(); }
  const_iterator cbegin() const { return map_.cbegin(); }

  iterator end() { return map_.end(); }
  const_iterator end() const { return cend(); }
  const_iterator cend() const { return map_.cend(); }

  // Returns whether a mapping exists for the given key.
  bool contains(const key_type& key) const { return get(key).has_value(); }

  // Returns a reference to the value for the given key, or std::nullopt if the key was not found.
  //
  //   ftl::SmallMap map = ftl::init::map('a', 'A')('b', 'B')('c', 'C');
  //
  //   const auto opt = map.get('c');
  //   assert(opt == 'C');
  //
  //   char d = 'd';
  //   const auto ref = map.get('d').value_or(std::ref(d));
  //   ref.get() = 'D';
  //   assert(d == 'D');
  //
  auto get(const key_type& key) const -> Optional<std::reference_wrapper<const mapped_type>> {
    for (const auto& [k, v] : *this) {
      if (KeyEqual{}(k, key)) {
        return std::cref(v);
      }
    }
    return {};
  }

  auto get(const key_type& key) -> Optional<std::reference_wrapper<mapped_type>> {
    for (auto& [k, v] : *this) {
      if (KeyEqual{}(k, key)) {
        return std::ref(v);
      }
    }
    return {};
  }

  // Returns an iterator to an existing mapping for the given key, or the end() iterator otherwise.
  const_iterator find(const key_type& key) const { return const_cast<SmallMap&>(*this).find(key); }
  iterator find(const key_type& key) { return find(key, begin()); }

  // Inserts a mapping unless it exists. Returns an iterator to the inserted or existing mapping,
  // and whether the mapping was inserted.
  //
  // On emplace, if the map reaches its static or dynamic capacity, then all iterators are
  // invalidated. Otherwise, only the end() iterator is invalidated.
  //
  template <typename... Args>
  std::pair<iterator, bool> try_emplace(const key_type& key, Args&&... args) {
    if (const auto it = find(key); it != end()) {
      return {it, false};
    }

    decltype(auto) ref_or_it =
        map_.emplace_back(std::piecewise_construct, std::forward_as_tuple(key),
                          std::forward_as_tuple(std::forward<Args>(args)...));

    if constexpr (static_capacity() > 0) {
      return {&ref_or_it, true};
    } else {
      return {ref_or_it, true};
    }
  }

  // Replaces a mapping if it exists, and returns an iterator to it. Returns the end() iterator
  // otherwise.
  //
  // The value is replaced via move constructor, so type V does not need to define copy/move
  // assignment, e.g. its data members may be const.
  //
  // The arguments may directly or indirectly refer to the mapping being replaced.
  //
  // Iterators to the replaced mapping point to its replacement, and others remain valid.
  //
  template <typename... Args>
  iterator try_replace(const key_type& key, Args&&... args) {
    const auto it = find(key);
    if (it == end()) return it;
    map_.replace(it, std::piecewise_construct, std::forward_as_tuple(key),
                 std::forward_as_tuple(std::forward<Args>(args)...));
    return it;
  }

  // In-place counterpart of std::unordered_map's insert_or_assign. Returns true on emplace, or
  // false on replace.
  //
  // The value is emplaced and replaced via move constructor, so type V does not need to define
  // copy/move assignment, e.g. its data members may be const.
  //
  // On emplace, if the map reaches its static or dynamic capacity, then all iterators are
  // invalidated. Otherwise, only the end() iterator is invalidated. On replace, iterators
  // to the replaced mapping point to its replacement, and others remain valid.
  //
  template <typename... Args>
  std::pair<iterator, bool> emplace_or_replace(const key_type& key, Args&&... args) {
    const auto [it, ok] = try_emplace(key, std::forward<Args>(args)...);
    if (ok) return {it, ok};
    map_.replace(it, std::piecewise_construct, std::forward_as_tuple(key),
                 std::forward_as_tuple(std::forward<Args>(args)...));
    return {it, ok};
  }

  // Removes a mapping if it exists, and returns whether it did.
  //
  // The last() and end() iterators, as well as those to the erased mapping, are invalidated.
  //
  bool erase(const key_type& key) { return erase(key, begin()); }

  // Removes all mappings.
  //
  // All iterators are invalidated.
  //
  void clear() { map_.clear(); }

 private:
  iterator find(const key_type& key, iterator first) {
    return std::find_if(first, end(),
                        [&key](const auto& pair) { return KeyEqual{}(pair.first, key); });
  }

  bool erase(const key_type& key, iterator first) {
    const auto it = find(key, first);
    if (it == end()) return false;
    map_.unstable_erase(it);
    return true;
  }

  void deduplicate() {
    for (auto it = begin(); it != end();) {
      if (const auto key = it->first; ++it != end()) {
        while (erase(key, it));
      }
    }
  }

  Map map_;
};

// Deduction guide for in-place constructor.
template <typename K, typename V, typename E, std::size_t... Sizes, typename... Types>
SmallMap(InitializerList<KeyValue<K, V, E>, std::index_sequence<Sizes...>, Types...>&&)
    -> SmallMap<K, V, sizeof...(Sizes), E>;

// Returns whether the key-value pairs of two maps are equal.
template <typename K, typename V, std::size_t N, typename Q, typename W, std::size_t M, typename E>
bool operator==(const SmallMap<K, V, N, E>& lhs, const SmallMap<Q, W, M, E>& rhs) {
  if (lhs.size() != rhs.size()) return false;

  for (const auto& [k, v] : lhs) {
    const auto& lv = v;
    if (!rhs.get(k).transform([&lv](const W& rv) { return lv == rv; }).value_or(false)) {
      return false;
    }
  }

  return true;
}

// TODO: Remove in C++20.
template <typename K, typename V, std::size_t N, typename Q, typename W, std::size_t M, typename E>
inline bool operator!=(const SmallMap<K, V, N, E>& lhs, const SmallMap<Q, W, M, E>& rhs) {
  return !(lhs == rhs);
}

}  // namespace android::ftl
