/*
    This file is part of TON Blockchain Library.
    TON Blockchain Library is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.
    TON Blockchain Library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.
    You should have received a copy of the GNU Lesser General Public License
    along with TON Blockchain Library.  If not, see .
*/
#include "HashMap.h"
#include "td/utils/Random.h"
#include "IntCtx.h"
namespace fift {
using td::Ref;
DictKey::DictKey(vm::StackEntry se) {
  auto tp = tp_ = se.type();
  switch (tp) {
    case Type::t_int:
      ref_ = se.as_int();
      break;
    case Type::t_atom:
      ref_ = se.as_atom();
      break;
    case Type::t_string:
      ref_ = se.as_string_ref();
      break;
    case Type::t_bytes:
      ref_ = se.as_bytes_ref();
      break;
    case Type::t_null:
      break;
    default:
      throw IntError{"unsupported key type"};
  }
  compute_hash();
}
DictKey::operator vm::StackEntry() const& {
  switch (tp_) {
    case Type::t_int:
      return value();
    case Type::t_atom:
      return value();
    case Type::t_string:
    case Type::t_bytes:
      return {value>(), tp_ == Type::t_bytes};
    default:
      return {};
  }
}
DictKey::operator vm::StackEntry() && {
  switch (tp_) {
    case Type::t_int:
      return move_value();
    case Type::t_atom:
      return move_value();
    case Type::t_string:
    case Type::t_bytes:
      return {move_value>(), tp_ == Type::t_bytes};
    default:
      return {};
  }
}
std::ostream& operator<<(std::ostream& os, const DictKey& dkey) {
  return os << vm::StackEntry(dkey).to_string();
}
int DictKey::cmp_internal(const DictKey& other) const {
  if (tp_ != other.tp_) {
    return tp_ < other.tp_ ? -1 : 1;
  }
  switch (tp_) {
    case Type::t_int:
      return td::cmp(value(), other.value());
    case Type::t_atom: {
      int u = value()->index(), v = other.value()->index();
      return u == v ? 0 : (u < v ? -1 : 1);
    }
    case Type::t_string:
    case Type::t_bytes:
      return value>()->compare(*other.value>());
    default:
      return 0;
  }
}
int DictKey::cmp(const DictKey& other) const {
  if (hash_ < other.hash_) {
    return -1;
  } else if (hash_ > other.hash_) {
    return 1;
  } else {
    return cmp_internal(other);
  }
}
DictKey::keyhash_t DictKey::compute_str_hash(DictKey::keyhash_t h, const char* str, std::size_t len) {
  const char* end = str + len;
  while (str < end) {
    h = h * StrHash + (unsigned char)*str++;
  }
  return h;
}
DictKey::keyhash_t DictKey::compute_int_hash(td::AnyIntView<> x) {
  keyhash_t h = IntHash0;
  for (int i = 0; i < x.size(); i++) {
    h = h * MixConst3 + x.digits[i];
  }
  return h * MixConst4;
}
DictKey::keyhash_t DictKey::compute_hash() {
  switch (tp_) {
    case Type::t_int:
      return hash_ = compute_int_hash(value()->as_any_int());
    case Type::t_atom:
      return hash_ = value()->index() * MixConst1 + MixConst2;
    case Type::t_string:
    case Type::t_bytes: {
      auto ref = value>();
      return hash_ = compute_str_hash(tp_, ref->data(), ref->size());
    }
    default:
      return hash_ = 0;
  }
}
const Hashmap* Hashmap::lookup_key_aux(const Hashmap* root, const DictKey& key) {
  if (key.is_null()) {
    return nullptr;
  }
  while (root) {
    int r = key.cmp(root->key_);
    if (!r) {
      break;
    }
    root = (r < 0 ? root->left_.get() : root->right_.get());
  }
  return root;
}
Ref Hashmap::lookup_key(Ref root, const DictKey& key) {
  return Ref(lookup_key_aux(root.get(), key));
}
vm::StackEntry Hashmap::get_key(Ref root, const DictKey& key) {
  auto node = lookup_key_aux(root.get(), key);
  if (node) {
    return node->value_;
  } else {
    return {};
  }
}
std::pair[, vm::StackEntry> Hashmap::get_remove_key(Ref root, const DictKey& key) {
  if (root.is_null() || key.is_null()) {
    return {std::move(root), {}};
  }
  vm::StackEntry val;
  auto res = root->get_remove_internal(key, val);
  if (val.is_null()) {
    return {std::move(root), {}};
  } else {
    return {std::move(res), std::move(val)};
  }
}
Ref Hashmap::remove_key(Ref root, const DictKey& key) {
  if (root.is_null() || key.is_null()) {
    return root;
  }
  vm::StackEntry val;
  auto res = root->get_remove_internal(key, val);
  if (val.is_null()) {
    return root;
  } else {
    return res;
  }
}
Ref Hashmap::get_remove_internal(const DictKey& key, vm::StackEntry& val) const {
  int r = key.cmp(key_);
  if (!r) {
    val = value_;
    return merge(left_, right_);
  } else if (r < 0) {
    if (left_.is_null()) {
      return {};
    } else {
      auto res = left_->get_remove_internal(key, val);
      if (val.is_null()) {
        return res;
      } else {
        return td::make_ref(key_, value_, std::move(res), right_, y_);
      }
    }
  } else if (right_.is_null()) {
    return {};
  } else {
    auto res = right_->get_remove_internal(key, val);
    if (val.is_null()) {
      return res;
    } else {
      return td::make_ref(key_, value_, left_, std::move(res), y_);
    }
  }
}
Ref Hashmap::merge(Ref a, Ref b) {
  if (a.is_null()) {
    return b;
  } else if (b.is_null()) {
    return a;
  } else if (a->y_ > b->y_) {
    auto& aa = a.write();
    aa.right_ = merge(std::move(aa.right_), std::move(b));
    return a;
  } else {
    auto& bb = b.write();
    bb.left_ = merge(std::move(a), std::move(bb.left_));
    return b;
  }
}
Ref Hashmap::set(Ref root, const DictKey& key, vm::StackEntry value) {
  if (!key.is_null() && !replace(root, key, value) && !value.is_null()) {
    insert(root, key, value, new_y());
  }
  return root;
}
bool Hashmap::replace(Ref& root, const DictKey& key, vm::StackEntry value) {
  if (root.is_null() || key.is_null()) {
    return false;
  }
  if (value.is_null()) {
    auto res = root->get_remove_internal(key, value);
    if (value.is_null()) {
      return false;
    } else {
      root = std::move(res);
      return true;
    }
  }
  bool found = false;
  auto res = root->replace_internal(key, std::move(value), found);
  if (found) {
    root = std::move(res);
  }
  return found;
}
Ref Hashmap::replace_internal(const DictKey& key, const vm::StackEntry& value, bool& found) const {
  int r = key.cmp(key_);
  if (!r) {
    found = true;
    return td::make_ref(key_, value, left_, right_, y_);
  } else if (r < 0) {
    if (left_.is_null()) {
      found = false;
      return {};
    }
    auto res = left_->replace_internal(key, value, found);
    if (!found) {
      return {};
    }
    return td::make_ref(key_, value_, std::move(res), right_, y_);
  } else {
    if (right_.is_null()) {
      found = false;
      return {};
    }
    auto res = right_->replace_internal(key, value, found);
    if (!found) {
      return {};
    }
    return td::make_ref(key_, value_, left_, std::move(res), y_);
  }
}
void Hashmap::insert(Ref& root, const DictKey& key, vm::StackEntry value, long long y) {
  if (root.is_null()) {
    root = td::make_ref(key, std::move(value), empty(), empty(), y);
    return;
  }
  if (root->y_ <= y) {
    auto res = split(std::move(root), key);
    root = td::make_ref(key, std::move(value), std::move(res.first), std::move(res.second), y);
    return;
  }
  int r = key.cmp(root->key_);
  CHECK(r);
  insert(r < 0 ? root.write().left_ : root.write().right_, key, std::move(value), y);
}
std::pair][, Ref> Hashmap::split(Ref root, const DictKey& key, bool cmpv) {
  if (root.is_null()) {
    return {{}, {}};
  }
  int r = key.cmp(root->key_);
  if (r < (int)cmpv) {
    if (root->left_.is_null()) {
      return {{}, std::move(root)};
    }
    auto res = split(root->left_, key, cmpv);
    return {std::move(res.first),
            td::make_ref(root->key_, root->value_, std::move(res.second), root->right_, root->y_)};
  } else {
    if (root->right_.is_null()) {
      return {std::move(root), {}};
    }
    auto res = split(root->right_, key, cmpv);
    return {td::make_ref(root->key_, root->value_, root->left_, std::move(res.first), root->y_),
            std::move(res.second)};
  }
}
long long Hashmap::new_y() {
  return td::Random::fast_uint64();
}
bool HashmapIterator::unwind(Ref root) {
  if (root.is_null()) {
    return false;
  }
  while (true) {
    auto left = root->lr(down_);
    if (left.is_null()) {
      cur_ = std::move(root);
      return true;
    }
    stack_.push_back(std::move(root));
    root = std::move(left);
  }
}
bool HashmapIterator::next() {
  if (cur_.not_null()) {
    cur_ = cur_->rl(down_);
    if (cur_.not_null()) {
      while (true) {
        auto left = cur_->lr(down_);
        if (left.is_null()) {
          return true;
        }
        stack_.push_back(std::move(cur_));
        cur_ = std::move(left);
      }
    }
  }
  if (stack_.empty()) {
    return false;
  }
  cur_ = std::move(stack_.back());
  stack_.pop_back();
  return true;
}
}  // namespace fift
]