/*
    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 .
    Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/cells/MerkleUpdate.h"
#include "vm/cells/MerkleProof.h"
#include "td/utils/HashMap.h"
#include "td/utils/HashSet.h"
namespace vm {
namespace detail {
class MerkleUpdateApply {
 public:
  Ref| apply(Ref | from, Ref | update_from, Ref | update_to, td::uint32 from_level,
                  td::uint32 to_level) {
    if (from_level != from->get_level()) {
      return {};
    }
    dfs_both(from, update_from, from_level);
    return dfs(update_to, to_level);
  }
 private:
  using Key = std::pair;
  td::HashMap> known_cells_;
  td::HashMap> ready_cells_;
  void dfs_both(Ref | original, Ref | update_from, int merkle_depth) {
    CellSlice cs_update_from(NoVm(), update_from);
    known_cells_.emplace(original->get_hash(merkle_depth), original);
    if (cs_update_from.special_type() == Cell::SpecialType::PrunnedBranch) {
      return;
    }
    int child_merkle_depth = cs_update_from.child_merkle_depth(merkle_depth);
    CellSlice cs_original(NoVm(), original);
    for (unsigned i = 0; i < cs_original.size_refs(); i++) {
      dfs_both(cs_original.prefetch_ref(i), cs_update_from.prefetch_ref(i), child_merkle_depth);
    }
  }
  Ref | dfs(Ref | cell, int merkle_depth) {
    CellSlice cs(NoVm(), cell);
    if (cs.special_type() == Cell::SpecialType::PrunnedBranch) {
      if ((int)cell->get_level() == merkle_depth + 1) {
        auto it = known_cells_.find(cell->get_hash(merkle_depth));
        if (it != known_cells_.end()) {
          return it->second;
        }
        return {};
      }
      return cell;
    }
    Key key{cell->get_hash(), merkle_depth};
    {
      auto it = ready_cells_.find(key);
      if (it != ready_cells_.end()) {
        return it->second;
      }
    }
    int child_merkle_depth = cs.child_merkle_depth(merkle_depth);
    CellBuilder cb;
    cb.store_bits(cs.fetch_bits(cs.size()));
    for (unsigned i = 0; i < cs.size_refs(); i++) {
      auto ref = dfs(cs.prefetch_ref(i), child_merkle_depth);
      if (ref.is_null()) {
        return {};
      }
      cb.store_ref(std::move(ref));
    }
    auto res = cb.finalize(cs.is_special());
    ready_cells_.emplace(key, res);
    return res;
  }
};
class MerkleUpdateValidator {
 public:
  td::Status validate(Ref | update_from, Ref | update_to, td::uint32 from_level, td::uint32 to_level) {
    dfs_from(update_from, from_level);
    return dfs_to(update_to, to_level);
  }
 private:
  td::HashSet known_cells_;
  using Key = std::pair;
  td::HashSet visited_from_;
  td::HashSet visited_to_;
  void dfs_from(Ref | cell, int merkle_depth) {
    if (!visited_from_.emplace(cell->get_hash(), merkle_depth).second) {
      return;
    }
    CellSlice cs(NoVm(), cell);
    known_cells_.insert(cell->get_hash(merkle_depth));
    if (cs.special_type() == Cell::SpecialType::PrunnedBranch) {
      return;
    }
    int child_merkle_depth = cs.child_merkle_depth(merkle_depth);
    for (unsigned i = 0; i < cs.size_refs(); i++) {
      dfs_from(cs.prefetch_ref(i), child_merkle_depth);
    }
  }
  td::Status dfs_to(Ref | cell, int merkle_depth) {
    if (!visited_to_.emplace(cell->get_hash(), merkle_depth).second) {
      return td::Status::OK();
    }
    CellSlice cs(NoVm(), cell);
    if (cs.special_type() == Cell::SpecialType::PrunnedBranch) {
      if ((int)cell->get_level() == merkle_depth + 1) {
        if (known_cells_.count(cell->get_hash(merkle_depth)) == 0) {
          return td::Status::Error(PSLICE()
                                   << "Unknown prunned cell (validate): " << cell->get_hash(merkle_depth).to_hex());
        }
      }
      return td::Status::OK();
    }
    int child_merkle_depth = cs.child_merkle_depth(merkle_depth);
    for (unsigned i = 0; i < cs.size_refs(); i++) {
      TRY_STATUS(dfs_to(cs.prefetch_ref(i), child_merkle_depth));
    }
    return td::Status::OK();
  }
};
}  // namespace detail
td::Status MerkleUpdate::may_apply(Ref | from, Ref | update) {
  if (update->get_level() != 0 || from->get_level() != 0) {
    return td::Status::Error("Level of update of from is not zero");
  }
  CellSlice cs(NoVm(), std::move(update));
  if (cs.special_type() != Cell::SpecialType::MerkleUpdate) {
    return td::Status::Error("Update cell is not a MerkeUpdate");
  }
  auto update_from = cs.fetch_ref();
  if (from->get_hash(0) != update_from->get_hash(0)) {
    return td::Status::Error("Hash mismatch");
  }
  return td::Status::OK();
}
Ref | MerkleUpdate::apply(Ref | from, Ref | update) {
  if (update->get_level() != 0 || from->get_level() != 0) {
    return {};
  }
  CellSlice cs(NoVm(), std::move(update));
  if (cs.special_type() != Cell::SpecialType::MerkleUpdate) {
    return {};
  }
  auto update_from = cs.fetch_ref();
  auto update_to = cs.fetch_ref();
  return apply_raw(std::move(from), std::move(update_from), std::move(update_to), 0, 0);
}
Ref | MerkleUpdate::apply_raw(Ref | from, Ref | update_from, Ref | update_to, td::uint32 from_level,
                                  td::uint32 to_level) {
  if (from->get_hash(from_level) != update_from->get_hash(from_level)) {
    LOG(DEBUG) << "invalid Merkle update: expected old value hash = " << update_from->get_hash(from_level).to_hex()
               << ", applied to value with hash = " << from->get_hash(from_level).to_hex();
    return {};
  }
  return detail::MerkleUpdateApply().apply(from, std::move(update_from), std::move(update_to), from_level, to_level);
}
std::pair [, Ref]| > MerkleUpdate::generate_raw(Ref | from, Ref | to, CellUsageTree *usage_tree) {
  // create Merkle update cell->new_cell
  auto update_to = MerkleProof::generate_raw(to, [tree = usage_tree](const Ref | &cell) {
    auto loaded_cell = cell->load_cell().move_as_ok();  // FIXME
    if (loaded_cell.data_cell->size_refs() == 0) {
      return false;
    }
    return !loaded_cell.tree_node.empty() && loaded_cell.tree_node.mark_path(tree);
  });
  usage_tree->set_use_mark_for_is_loaded(true);
  auto update_from = MerkleProof::generate_raw(from, usage_tree);
  return {std::move(update_from), std::move(update_to)};
}
td::Status MerkleUpdate::validate_raw(Ref | update_from, Ref | update_to, td::uint32 from_level,
                                      td::uint32 to_level) {
  return detail::MerkleUpdateValidator().validate(std::move(update_from), std::move(update_to), from_level, to_level);
}
td::Status MerkleUpdate::validate(Ref | update) {
  if (update->get_level() != 0) {
    return td::Status::Error("nonzero level");
  }
  CellSlice cs(NoVm(), std::move(update));
  if (cs.special_type() != Cell::SpecialType::MerkleUpdate) {
    return td::Status::Error("not a MerkleUpdate cell");
  }
  auto update_from = cs.fetch_ref();
  auto update_to = cs.fetch_ref();
  return validate_raw(std::move(update_from), std::move(update_to), 0, 0);
}
Ref | MerkleUpdate::generate(Ref | from, Ref | to, CellUsageTree *usage_tree) {
  auto from_level = from->get_level();
  auto to_level = to->get_level();
  if (from_level != 0 || to_level != 0) {
    return {};
  }
  auto res = generate_raw(std::move(from), std::move(to), usage_tree);
  return CellBuilder::create_merkle_update(res.first, res.second);
}
namespace detail {
class MerkleCombine {
 public:
  MerkleCombine(Ref | AB, Ref | CD) : AB_(std::move(AB)), CD_(std::move(CD)) {
  }
  td::Result [> run() {
    TRY_RESULT(AB, unpack_update(std::move(AB_)));
    TRY_RESULT(CD, unpack_update(std::move(CD_)));
    std::tie(A_, B_) = AB;
    std::tie(C_, D_) = CD;
    if (B_->get_hash(0) != C_->get_hash(0)) {
      return td::Status::Error("Impossible to combine merkle updates");
    }
    auto log = [](td::Slice name, auto cell) {
      CellSlice cs(NoVm(), cell);
      LOG(ERROR) << name << " " << cell->get_level();
      cs.print_rec(std::cerr);
    };
    if (0) {
      log("A", A_);
      log("B", B_);
      log("C", C_);
      log("D", D_);
    }
    // We have four bags of cells. A, B, C and D.
    // X = Virtualize(A), A is subtree (merkle proof) of X
    // Y = Virtualize(B) = Virtualize(C), B and C are  subrees of Y
    // Z = Virtualize(D), D is subtree of Z
    //
    // Prunned cells bounded by merkle proof P are essentially cells which are impossible to load during traversal of Virtualize(P)
    //
    // We want to create new_A and new_D
    // Virtualize(new_A) = X
    // Virtualize(new_D) = Z
    // All prunned branches bounded by new_D must be in new_A
    // i.e. if we have all cells reachable in Virtualize(new_A) we may construct Z from them (and from new_D)
    //
    // Main idea is following
    // 1. Create maximum subtrees of X and Z with all cells in A, B, C and D
    // Max(V) - such maximum subtree
    //
    // 2. Max(A) and Max(D) should be merkle update already. But win want to minimize it
    // So we cut all branches of Max(D) which are in maxA
    // When we cut branch q in Max(D) we mark some path to q in Max(A)
    // Then we cut all branches of Max(A) which are not marked.
    //
    // How to create Max(A)?
    // We just store all cells reachable from A, B, C and D in big cache.
    // It we reach bounded prunned cell during traversion we may continue traversial with a cell from the cache.
    //
    //
    // 1. load_cells(root) - caches all cell reachable in Virtualize(root);
    visited_.clear();
    load_cells(A_, 0);
    visited_.clear();
    load_cells(B_, 0);
    visited_.clear();
    load_cells(C_, 0);
    visited_.clear();
    load_cells(D_, 0);
    // 2. mark_A(A) - Traverse Max(A), but uses all cached cells from step 1. Mark all visited cells
    A_usage_tree_ = std::make_shared();
    mark_A(A_, 0, A_usage_tree_->root_id());
    // 3. create_D(D) - create new_D. Traverse Max(D), and stop at marked cells. Mark path in A to marked cells
    auto new_D = create_D(D_, 0, 0);
    if (new_D.is_null()) {
      return td::Status::Error("Failed to combine updates. One of them is probably an invalid update");
    }
    // 4. create_A(A) - create new_A. Traverse Max(A), and stop at cells not marked at step 3.
    auto new_A = create_A(A_, 0, 0);
    if (0) {
      log("NewD", new_D);
    }
    return CellBuilder::create_merkle_update(new_A, new_D);
  }
 private:
  Ref]| AB_, CD_;
  Ref | A_, B_, C_, D_;
  std::shared_ptr A_usage_tree_;
  struct Info {
    Ref | cell_;
    Ref | prunned_cells_[Cell::max_level];  // Cache prunned cells with different levels to reuse them
    CellUsageTree::NodeId A_node_id{0};
    Ref | get_prunned_cell(int depth) {
      if (depth < Cell::max_level) {
        return prunned_cells_[depth];
      }
      return {};
    }
    Ref | get_any_cell() const {
      if (cell_.not_null()) {
        return cell_;
      }
      for (auto &cell : prunned_cells_) {
        if (cell.not_null()) {
          return cell;
        }
      }
      UNREACHABLE();
    }
  };
  using Key = std::pair;
  td::HashMap cells_;
  td::HashMap> create_A_res_;
  td::HashMap> create_D_res_;
  td::HashSet visited_;
  void load_cells(Ref | cell, int merkle_depth) {
    if (!visited_.emplace(cell->get_hash(), merkle_depth).second) {
      return;
    }
    auto &info = cells_[cell->get_hash(merkle_depth)];
    CellSlice cs(NoVm(), cell);
    // check if prunned cell is bounded
    if (cs.special_type() == Cell::SpecialType::PrunnedBranch && static_cast(cell->get_level()) > merkle_depth) {
      info.prunned_cells_[cell->get_level() - 1] = std::move(cell);
      return;
    }
    info.cell_ = std::move(cell);
    auto child_merkle_depth = cs.child_merkle_depth(merkle_depth);
    for (size_t i = 0, size = cs.size_refs(); i < size; i++) {
      load_cells(cs.fetch_ref(), child_merkle_depth);
    }
  }
  void mark_A(Ref | cell, int merkle_depth, CellUsageTree::NodeId node_id) {
    CHECK(node_id != 0);
    // cell in cache may be virtualized with different level
    // so we make merkle_depth as small as possible
    merkle_depth = cell->get_level_mask().apply(merkle_depth).get_level();
    auto &info = cells_[cell->get_hash(merkle_depth)];
    if (info.A_node_id != 0) {
      return;
    }
    info.A_node_id = node_id;
    if (info.cell_.is_null()) {
      return;
    }
    CellSlice cs(NoVm(), info.cell_);
    auto child_merkle_depth = cs.child_merkle_depth(merkle_depth);
    for (int i = 0, size = cs.size_refs(); i < size; i++) {
      mark_A(cs.fetch_ref(), child_merkle_depth, A_usage_tree_->create_child(node_id, i));
    }
  }
  Ref | create_D(Ref | cell, int merkle_depth, int d_merkle_depth) {
    merkle_depth = cell->get_level_mask().apply(merkle_depth).get_level();
    auto key = Key(cell->get_hash(merkle_depth), d_merkle_depth);
    auto it = create_D_res_.find(key);
    if (it != create_D_res_.end()) {
      return it->second;
    }
    auto res = do_create_D(std::move(cell), merkle_depth, d_merkle_depth);
    if (res.is_null()) {
      return {};
    }
    create_D_res_.emplace(key, res);
    return res;
  }
  Ref | do_create_D(Ref | cell, int merkle_depth, int d_merkle_depth) {
    auto &info = cells_[cell->get_hash(merkle_depth)];
    if (info.A_node_id != 0) {
      A_usage_tree_->mark_path(info.A_node_id);
      Ref | res = info.get_prunned_cell(d_merkle_depth);
      if (res.is_null()) {
        res = CellBuilder::create_pruned_branch(info.get_any_cell(), d_merkle_depth + 1, merkle_depth);
      }
      return res;
    }
    if (info.cell_.is_null()) {
      return {};
    }
    CellSlice cs(NoVm(), info.cell_);
    if (cs.size_refs() == 0) {
      return info.cell_;
    }
    auto child_merkle_depth = cs.child_merkle_depth(merkle_depth);
    auto child_d_merkle_depth = cs.child_merkle_depth(d_merkle_depth);
    CellBuilder cb;
    cb.store_bits(cs.fetch_bits(cs.size()));
    for (unsigned i = 0; i < cs.size_refs(); i++) {
      auto ref = create_D(cs.prefetch_ref(i), child_merkle_depth, child_d_merkle_depth);
      if (ref.is_null()) {
        return {};
      }
      cb.store_ref(std::move(ref));
    }
    return cb.finalize(cs.is_special());
  }
  Ref | create_A(Ref | cell, int merkle_depth, int a_merkle_depth) {
    merkle_depth = cell->get_level_mask().apply(merkle_depth).get_level();
    auto key = Key(cell->get_hash(merkle_depth), a_merkle_depth);
    auto it = create_A_res_.find(key);
    if (it != create_A_res_.end()) {
      return it->second;
    }
    auto res = do_create_A(std::move(cell), merkle_depth, a_merkle_depth);
    create_A_res_.emplace(key, res);
    return res;
  }
  Ref | do_create_A(Ref | cell, int merkle_depth, int a_merkle_depth) {
    auto &info = cells_[cell->get_hash(merkle_depth)];
    CHECK(info.A_node_id != 0);
    if (!A_usage_tree_->has_mark(info.A_node_id)) {
      Ref | res = info.get_prunned_cell(a_merkle_depth);
      if (res.is_null()) {
        res = CellBuilder::create_pruned_branch(info.get_any_cell(), a_merkle_depth + 1, merkle_depth);
      }
      return res;
    }
    CHECK(info.cell_.not_null());
    CellSlice cs(NoVm(), info.cell_);
    CHECK(cs.size_refs() != 0);
    if (cs.size_refs() == 0) {
      return info.cell_;
    }
    auto child_merkle_depth = cs.child_merkle_depth(merkle_depth);
    auto child_a_merkle_depth = cs.child_merkle_depth(a_merkle_depth);
    CellBuilder cb;
    cb.store_bits(cs.fetch_bits(cs.size()));
    for (unsigned i = 0; i < cs.size_refs(); i++) {
      cb.store_ref(create_A(cs.prefetch_ref(i), child_merkle_depth, child_a_merkle_depth));
    }
    return cb.finalize(cs.is_special());
  }
  td::Result, Ref | >> unpack_update(Ref | update) const {
    if (update->get_level() != 0) {
      return td::Status::Error("level is not zero");
    }
    CellSlice cs(NoVm(), std::move(update));
    if (cs.special_type() != Cell::SpecialType::MerkleUpdate) {
      return td::Status::Error("Not a Merkle Update cell");
    }
    auto update_from = cs.fetch_ref();
    auto update_to = cs.fetch_ref();
    return std::make_pair(std::move(update_from), std::move(update_to));
  }
};
}  // namespace detail
Ref | MerkleUpdate::combine(Ref | ab, Ref | bc) {
  detail::MerkleCombine combine(ab, bc);
  auto res = combine.run();
  if (res.is_error()) {
    return {};
  }
  return res.move_as_ok();
}
}  // namespace vm |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |