/*
    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-2020 Telegram Systems LLP
*/
#include 
#include 
#include 
#include "vm/boc.h"
#include "vm/boc-writers.h"
#include "vm/cells.h"
#include "vm/cellslice.h"
#include "td/utils/bits.h"
#include "td/utils/crypto.h"
#include "td/utils/format.h"
#include "td/utils/misc.h"
#include "td/utils/Slice-decl.h"
namespace vm {
using td::Ref;
td::Status CellSerializationInfo::init(td::Slice data, int ref_byte_size) {
  if (data.size() < 2) {
    return td::Status::Error(PSLICE() << "Not enough bytes " << td::tag("got", data.size())
                                      << td::tag("expected", "at least 2"));
  }
  TRY_STATUS(init(data.ubegin()[0], data.ubegin()[1], ref_byte_size));
  if (data.size() < end_offset) {
    return td::Status::Error(PSLICE() << "Not enough bytes " << td::tag("got", data.size())
                                      << td::tag("expected", end_offset));
  }
  return td::Status::OK();
}
td::Status CellSerializationInfo::init(td::uint8 d1, td::uint8 d2, int ref_byte_size) {
  refs_cnt = d1 & 7;
  level_mask = Cell::LevelMask(d1 >> 5);
  special = (d1 & 8) != 0;
  with_hashes = (d1 & 16) != 0;
  if (refs_cnt > 4) {
    if (refs_cnt != 7 || !with_hashes) {
      return td::Status::Error("Invalid first byte");
    }
    refs_cnt = 0;
    // ...
    // do not deserialize absent cells!
    return td::Status::Error("TODO: absent cells");
  }
  hashes_offset = 2;
  auto n = level_mask.get_hashes_count();
  depth_offset = hashes_offset + (with_hashes ? n * Cell::hash_bytes : 0);
  data_offset = depth_offset + (with_hashes ? n * Cell::depth_bytes : 0);
  data_len = (d2 >> 1) + (d2 & 1);
  data_with_bits = (d2 & 1) != 0;
  refs_offset = data_offset + data_len;
  end_offset = refs_offset + refs_cnt * ref_byte_size;
  return td::Status::OK();
}
td::Result CellSerializationInfo::get_bits(td::Slice cell) const {
  if (data_with_bits) {
    DCHECK(data_len != 0);
    int last = cell[data_offset + data_len - 1];
    if (!(last & 0x7f)) {
      return td::Status::Error("overlong encoding");
    }
    return td::narrow_cast((data_len - 1) * 8 + 7 - td::count_trailing_zeroes_non_zero32(last));
  } else {
    return td::narrow_cast(data_len * 8);
  }
}
// TODO: check usage when result is empty
td::Result[> CellSerializationInfo::create_data_cell(td::Slice cell_slice,
                                                                  td::Span][> refs) const {
  CellBuilder cb;
  TRY_RESULT(bits, get_bits(cell_slice));
  cb.store_bits(cell_slice.ubegin() + data_offset, bits);
  DCHECK(refs_cnt == (td::int64)refs.size());
  for (int k = 0; k < refs_cnt; k++) {
    cb.store_ref(std::move(refs[k]));
  }
  TRY_RESULT(res, cb.finalize_novm_nothrow(special));
  CHECK(!res.is_null());
  if (res->is_special() != special) {
    return td::Status::Error("is_special mismatch");
  }
  if (res->get_level_mask() != level_mask) {
    return td::Status::Error("level mask mismatch");
  }
  //return res;
  if (with_hashes) {
    auto hash_n = level_mask.get_hashes_count();
    if (res->get_hash().as_slice() !=
        cell_slice.substr(hashes_offset + Cell::hash_bytes * (hash_n - 1), Cell::hash_bytes)) {
      return td::Status::Error("representation hash mismatch");
    }
    if (res->get_depth() !=
        DataCell::load_depth(
            cell_slice.substr(depth_offset + Cell::depth_bytes * (hash_n - 1), Cell::depth_bytes).ubegin())) {
      return td::Status::Error("depth mismatch");
    }
    bool check_all_hashes = true;
    for (unsigned level_i = 0, hash_i = 0, level = level_mask.get_level(); check_all_hashes && level_i < level;
         level_i++) {
      if (!level_mask.is_significant(level_i)) {
        continue;
      }
      if (cell_slice.substr(hashes_offset + Cell::hash_bytes * hash_i, Cell::hash_bytes) !=
          res->get_hash(level_i).as_slice()) {
        // hash mismatch
        return td::Status::Error("lower hash mismatch");
      }
      if (res->get_depth(level_i) !=
          DataCell::load_depth(
              cell_slice.substr(depth_offset + Cell::depth_bytes * hash_i, Cell::depth_bytes).ubegin())) {
        return td::Status::Error("lower depth mismatch");
      }
      hash_i++;
    }
  }
  return res;
}
void BagOfCells::clear() {
  cells_clear();
  roots.clear();
  root_count = 0;
  serialized.clear();
}
int BagOfCells::set_roots(const std::vector>& new_roots) {
  clear();
  return add_roots(new_roots);
}
int BagOfCells::set_root(td::Ref new_root) {
  clear();
  return add_root(std::move(new_root));
}
int BagOfCells::add_roots(const std::vector>& add_roots) {
  int res = 0;
  for (td::Ref root : add_roots) {
    res += add_root(std::move(root));
  }
  return res;
}
int BagOfCells::add_root(td::Ref add_root) {
  if (add_root.is_null()) {
    return 0;
  }
  LOG_CHECK(add_root->get_virtualization() == 0) << "TODO: support serialization of virtualized cells";
  //const Cell::Hash& hash = add_root->get_hash();
  //for (const auto& root_info : roots) {
  //if (root_info.cell->get_hash() == hash) {
  //return 0;
  //}
  //}
  roots.emplace_back(std::move(add_root), -1);
  ++root_count;
  cells_clear();
  return 1;
}
// Changes in this function may require corresponding changes in crypto/vm/large-boc-serializer.cpp
td::Status BagOfCells::import_cells() {
  if (logger_ptr_) {
    logger_ptr_->start_stage("import_cells");
  }
  cells_clear();
  for (auto& root : roots) {
    auto res = import_cell(root.cell, 0);
    if (res.is_error()) {
      return res.move_as_error();
    }
    root.idx = res.move_as_ok();
  }
  //LOG(INFO) << "[cells: " << cell_count << ", refs: " << int_refs << ", bytes: " << data_bytes << "]";
  reorder_cells();
  //LOG(INFO) << "[cells: " << cell_count << ", refs: " << int_refs << ", bytes: " << data_bytes
  //<< ", internal hashes: " << int_hashes << ", top hashes: " << top_hashes << "]";
  CHECK(cell_count != 0);
  if (logger_ptr_) {
    logger_ptr_->finish_stage(PSLICE() << cell_count << " cells");
  }
  return td::Status::OK();
}
// Changes in this function may require corresponding changes in crypto/vm/large-boc-serializer.cpp
td::Result BagOfCells::import_cell(td::Ref cell, int depth) {
  if (depth > max_depth) {
    return td::Status::Error("error while importing a cell into a bag of cells: cell depth too large");
  }
  if (cell.is_null()) {
    return td::Status::Error("error while importing a cell into a bag of cells: cell is null");
  }
  if (logger_ptr_) {
    TRY_STATUS(logger_ptr_->on_cell_processed());
  }
  auto it = cells.find(cell->get_hash());
  if (it != cells.end()) {
    auto pos = it->second;
    cell_list_[pos].should_cache = true;
    return pos;
  }
  if (cell->get_virtualization() != 0) {
    return td::Status::Error(
        "error while importing a cell into a bag of cells: cell has non-zero virtualization level");
  }
  auto r_loaded_dc = cell->load_cell();
  if (r_loaded_dc.is_error()) {
    return td::Status::Error("error while importing a cell into a bag of cells: " +
                             r_loaded_dc.move_as_error().to_string());
  }
  auto loaded_dc = r_loaded_dc.move_as_ok();
  CellSlice cs(std::move(loaded_dc));
  std::array refs{-1};
  DCHECK(cs.size_refs() <= 4);
  unsigned sum_child_wt = 1;
  for (unsigned i = 0; i < cs.size_refs(); i++) {
    auto ref = import_cell(cs.prefetch_ref(i), depth + 1);
    if (ref.is_error()) {
      return ref.move_as_error();
    }
    refs[i] = ref.move_as_ok();
    sum_child_wt += cell_list_[refs[i]].wt;
    ++int_refs;
  }
  DCHECK(cell_list_.size() == static_cast(cell_count));
  auto dc = cs.move_as_loaded_cell().data_cell;
  auto res = cells.emplace(dc->get_hash(), cell_count);
  DCHECK(res.second);
  cell_list_.emplace_back(dc, dc->size_refs(), refs);
  CellInfo& dc_info = cell_list_.back();
  dc_info.hcnt = static_cast(dc->get_level_mask().get_hashes_count());
  dc_info.wt = static_cast(std::min(0xffU, sum_child_wt));
  dc_info.new_idx = -1;
  data_bytes += dc->get_serialized_size();
  return cell_count++;
}
// Changes in this function may require corresponding changes in crypto/vm/large-boc-serializer.cpp
void BagOfCells::reorder_cells() {
  int_hashes = 0;
  for (int i = cell_count - 1; i >= 0; --i) {
    CellInfo& dci = cell_list_[i];
    int s = dci.ref_num, c = s, sum = max_cell_whs - 1, mask = 0;
    for (int j = 0; j < s; ++j) {
      CellInfo& dcj = cell_list_[dci.ref_idx[j]];
      int limit = (max_cell_whs - 1 + j) / s;
      if (dcj.wt <= limit) {
        sum -= dcj.wt;
        --c;
        mask |= (1 << j);
      }
    }
    if (c) {
      for (int j = 0; j < s; ++j) {
        if (!(mask & (1 << j))) {
          CellInfo& dcj = cell_list_[dci.ref_idx[j]];
          int limit = sum++ / c;
          if (dcj.wt > limit) {
            dcj.wt = static_cast(limit);
          }
        }
      }
    }
  }
  for (int i = 0; i < cell_count; i++) {
    CellInfo& dci = cell_list_[i];
    int s = dci.ref_num, sum = 1;
    for (int j = 0; j < s; ++j) {
      sum += cell_list_[dci.ref_idx[j]].wt;
    }
    DCHECK(sum <= max_cell_whs);
    if (sum <= dci.wt) {
      dci.wt = static_cast(sum);
    } else {
      dci.wt = 0;
      int_hashes += dci.hcnt;
    }
  }
  top_hashes = 0;
  for (auto& root_info : roots) {
    auto& cell_info = cell_list_[root_info.idx];
    if (cell_info.is_root_cell) {
      cell_info.is_root_cell = true;
      if (cell_info.wt) {
        top_hashes += cell_info.hcnt;
      }
    }
  }
  if (cell_count > 0) {
    rv_idx = 0;
    cell_list_tmp.clear();
    cell_list_tmp.reserve(cell_count);
    for (const auto& root_info : roots) {
      revisit(root_info.idx, 0);
      revisit(root_info.idx, 1);
    }
    for (const auto& root_info : roots) {
      revisit(root_info.idx, 2);
    }
    for (auto& root_info : roots) {
      root_info.idx = cell_list_[root_info.idx].new_idx;
    }
    DCHECK(rv_idx == cell_count);
    //DCHECK(cell_list.back().new_idx == cell_count - 1);
    DCHECK(cell_list_.size() == cell_list_tmp.size());
    cell_list_ = std::move(cell_list_tmp);
    cell_list_tmp.clear();
  }
}
// force=0 : previsit (recursively until special cells are found; then visit them)
// force=1 : visit (allocate and process all children)
// force=2 : allocate (assign a new index; can be run only after visiting)
// Changes in this function may require corresponding changes in crypto/vm/large-boc-serializer.cpp
int BagOfCells::revisit(int cell_idx, int force) {
  DCHECK(cell_idx >= 0 && cell_idx < cell_count);
  CellInfo& dci = cell_list_[cell_idx];
  if (dci.new_idx >= 0) {
    return dci.new_idx;
  }
  if (!force) {
    // previsit
    if (dci.new_idx != -1) {
      // already previsited or visited
      return dci.new_idx;
    }
    int n = dci.ref_num;
    for (int j = n - 1; j >= 0; --j) {
      int child_idx = dci.ref_idx[j];
      // either previsit or visit child, depending on whether it is special
      revisit(dci.ref_idx[j], cell_list_[child_idx].is_special());
    }
    return dci.new_idx = -2;  // mark as previsited
  }
  if (force > 1) {
    // time to allocate
    auto i = dci.new_idx = rv_idx++;
    cell_list_tmp.emplace_back(std::move(dci));
    return i;
  }
  if (dci.new_idx == -3) {
    // already visited
    return dci.new_idx;
  }
  if (dci.is_special()) {
    // if current cell is special, previsit it first
    revisit(cell_idx, 0);
  }
  // visit children
  int n = dci.ref_num;
  for (int j = n - 1; j >= 0; --j) {
    revisit(dci.ref_idx[j], 1);
  }
  // allocate children
  for (int j = n - 1; j >= 0; --j) {
    dci.ref_idx[j] = revisit(dci.ref_idx[j], 2);
  }
  return dci.new_idx = -3;  // mark as visited (and all children processed)
}
// Changes in this function may require corresponding changes in crypto/vm/large-boc-serializer.cpp
td::uint64 BagOfCells::compute_sizes(int mode, int& r_size, int& o_size) {
  int rs = 0, os = 0;
  if (!root_count || !data_bytes) {
    r_size = o_size = 0;
    return 0;
  }
  while (cell_count >= (1LL << (rs << 3))) {
    rs++;
  }
  td::uint64 hashes =
      (((mode & Mode::WithTopHash) ? top_hashes : 0) + ((mode & Mode::WithIntHashes) ? int_hashes : 0)) *
      (Cell::hash_bytes + Cell::depth_bytes);
  td::uint64 data_bytes_adj = data_bytes + (unsigned long long)int_refs * rs + hashes;
  td::uint64 max_offset = (mode & Mode::WithCacheBits) ? data_bytes_adj * 2 : data_bytes_adj;
  while (max_offset >= (1ULL << (os << 3))) {
    os++;
  }
  if (rs > 4 || os > 8) {
    r_size = o_size = 0;
    return 0;
  }
  r_size = rs;
  o_size = os;
  return data_bytes_adj;
}
// Changes in this function may require corresponding changes in crypto/vm/large-boc-serializer.cpp
std::size_t BagOfCells::estimate_serialized_size(int mode) {
  if ((mode & Mode::WithCacheBits) && !(mode & Mode::WithIndex)) {
    info.invalidate();
    return 0;
  }
  auto data_bytes_adj = compute_sizes(mode, info.ref_byte_size, info.offset_byte_size);
  if (!data_bytes_adj) {
    info.invalidate();
    return 0;
  }
  info.valid = true;
  info.has_crc32c = mode & Mode::WithCRC32C;
  info.has_index = mode & Mode::WithIndex;
  info.has_cache_bits = mode & Mode::WithCacheBits;
  info.root_count = root_count;
  info.cell_count = cell_count;
  info.absent_count = dangle_count;
  int crc_size = info.has_crc32c ? 4 : 0;
  info.roots_offset = 4 + 1 + 1 + 3 * info.ref_byte_size + info.offset_byte_size;
  info.index_offset = info.roots_offset + info.root_count * info.ref_byte_size;
  info.data_offset = info.index_offset;
  if (info.has_index) {
    info.data_offset += (long long)cell_count * info.offset_byte_size;
  }
  info.magic = Info::boc_generic;
  info.data_size = data_bytes_adj;
  info.total_size = info.data_offset + data_bytes_adj + crc_size;
  auto res = td::narrow_cast_safe(info.total_size);
  if (res.is_error()) {
    return 0;
  }
  return res.ok();
}
td::Status BagOfCells::serialize(int mode) {
  std::size_t size_est = estimate_serialized_size(mode);
  if (!size_est) {
    serialized.clear();
    return td::Status::OK();
  }
  serialized.resize(size_est);
  TRY_RESULT(size, serialize_to(const_cast(serialized.data()), serialized.size(), mode));
  if (size != size_est) {
    serialized.clear();
    return td::Status::Error("serialization failed");
  }
  return td::Status::OK();
}
std::string BagOfCells::serialize_to_string(int mode) {
  std::size_t size_est = estimate_serialized_size(mode);
  if (!size_est) {
    return {};
  }
  std::string res;
  res.resize(size_est, 0);
  if (serialize_to(const_cast(reinterpret_cast(res.data())), res.size(), mode)
          .move_as_ok() == res.size()) {
    return res;
  } else {
    return {};
  }
}
td::Result BagOfCells::serialize_to_slice(int mode) {
  std::size_t size_est = estimate_serialized_size(mode);
  if (!size_est) {
    return td::Status::Error("no cells to serialize to this bag of cells");
  }
  td::BufferSlice res(size_est);
  TRY_RESULT(size, serialize_to(const_cast(reinterpret_cast(res.data())),
                                res.size(), mode));
  if (size == res.size()) {
    return std::move(res);
  } else {
    return td::Status::Error("error while serializing a bag of cells: actual serialized size differs from estimated");
  }
}
std::string BagOfCells::extract_string() const {
  return std::string{serialized.data(), serialized.data() + serialized.size()};
}
//serialized_boc#672fb0ac has_idx:(## 1) has_crc32c:(## 1)
//  has_cache_bits:(## 1) flags:(## 2) { flags = 0 }
//  size:(## 3) { size <= 4 }
//  off_bytes:(## 8) { off_bytes <= 8 }
//  cells:(##(size * 8))
//  roots:(##(size * 8))
//  absent:(##(size * 8)) { roots + absent <= cells }
//  tot_cells_size:(##(off_bytes * 8))
//  index:(cells * ##(off_bytes * 8))
//  cell_data:(tot_cells_size * [ uint8 ])
//  = BagOfCells;
// Changes in this function may require corresponding changes in crypto/vm/large-boc-serializer.cpp
template 
td::Result BagOfCells::serialize_to_impl(WriterT& writer, int mode) {
  auto store_ref = [&](unsigned long long value) { writer.store_uint(value, info.ref_byte_size); };
  auto store_offset = [&](unsigned long long value) { writer.store_uint(value, info.offset_byte_size); };
  writer.store_uint(info.magic, 4);
  td::uint8 byte{0};
  if (info.has_index) {
    byte |= 1 << 7;
  }
  if (info.has_crc32c) {
    byte |= 1 << 6;
  }
  if (info.has_cache_bits) {
    byte |= 1 << 5;
  }
  // 3, 4 - flags
  if (info.ref_byte_size < 1 || info.ref_byte_size > 7) {
    return 0;
  }
  byte |= static_cast(info.ref_byte_size);
  writer.store_uint(byte, 1);
  writer.store_uint(info.offset_byte_size, 1);
  store_ref(cell_count);
  store_ref(root_count);
  store_ref(0);
  store_offset(info.data_size);
  for (const auto& root_info : roots) {
    int k = cell_count - 1 - root_info.idx;
    DCHECK(k >= 0 && k < cell_count);
    store_ref(k);
  }
  DCHECK(writer.position() == info.index_offset);
  DCHECK((unsigned)cell_count == cell_list_.size());
  if (info.has_index) {
    std::size_t offs = 0;
    if (logger_ptr_) {
      logger_ptr_->start_stage("generate_index");
    }
    for (int i = cell_count - 1; i >= 0; --i) {
      const Ref& dc = cell_list_[i].dc_ref;
      bool with_hash = (mode & Mode::WithIntHashes) && !cell_list_[i].wt;
      if (cell_list_[i].is_root_cell && (mode & Mode::WithTopHash)) {
        with_hash = true;
      }
      offs += dc->get_serialized_size(with_hash) + dc->size_refs() * info.ref_byte_size;
      auto fixed_offset = offs;
      if (info.has_cache_bits) {
        fixed_offset = offs * 2 + cell_list_[i].should_cache;
      }
      store_offset(fixed_offset);
      if (logger_ptr_) {
        TRY_STATUS(logger_ptr_->on_cell_processed());
      }
    }
    if (logger_ptr_) {
      logger_ptr_->finish_stage("");
    }
    DCHECK(offs == info.data_size);
  }
  DCHECK(writer.position() == info.data_offset);
  size_t keep_position = writer.position();
  if (logger_ptr_) {
    logger_ptr_->start_stage("serialize");
  }
  for (int i = 0; i < cell_count; ++i) {
    const auto& dc_info = cell_list_[cell_count - 1 - i];
    const Ref& dc = dc_info.dc_ref;
    bool with_hash = (mode & Mode::WithIntHashes) && !dc_info.wt;
    if (dc_info.is_root_cell && (mode & Mode::WithTopHash)) {
      with_hash = true;
    }
    unsigned char buf[256];
    int s = dc->serialize(buf, 256, with_hash);
    writer.store_bytes(buf, s);
    DCHECK(dc->size_refs() == dc_info.ref_num);
    // std::cerr << (dc_info.is_special() ? '*' : ' ') << i << '<' << (int)dc_info.wt << ">:";
    for (unsigned j = 0; j < dc_info.ref_num; ++j) {
      int k = cell_count - 1 - dc_info.ref_idx[j];
      DCHECK(k > i && k < cell_count);
      store_ref(k);
      // std::cerr << ' ' << k;
    }
    // std::cerr << std::endl;
    if (logger_ptr_) {
      TRY_STATUS(logger_ptr_->on_cell_processed());
    }
  }
  writer.chk();
  DCHECK(writer.position() - keep_position == info.data_size);
  DCHECK(writer.remaining() == (info.has_crc32c ? 4 : 0));
  if (info.has_crc32c) {
    unsigned crc = writer.get_crc32();
    writer.store_uint(td::bswap32(crc), 4);
  }
  if (logger_ptr_) {
    logger_ptr_->finish_stage(PSLICE() << cell_count << " cells, " << writer.position() << " bytes");
  }
  DCHECK(writer.empty());
  return writer.position();
}
td::Result BagOfCells::serialize_to(unsigned char* buffer, std::size_t buff_size, int mode) {
  std::size_t size_est = estimate_serialized_size(mode);
  if (!size_est || size_est > buff_size) {
    return 0;
  }
  boc_writers::BufferWriter writer{buffer, buffer + size_est};
  return serialize_to_impl(writer, mode);
}
td::Status BagOfCells::serialize_to_file(td::FileFd& fd, int mode) {
  std::size_t size_est = estimate_serialized_size(mode);
  if (!size_est) {
    return td::Status::Error("no cells to serialize to this bag of cells");
  }
  boc_writers::FileWriter writer{fd, size_est};
  TRY_RESULT(s, serialize_to_impl(writer, mode));
  TRY_STATUS(writer.finalize());
  if (s != size_est) {
    return td::Status::Error("error while serializing a bag of cells: actual serialized size differs from estimated");
  }
  return td::Status::OK();
}
unsigned long long BagOfCells::Info::read_int(const unsigned char* ptr, unsigned bytes) {
  unsigned long long res = 0;
  while (bytes > 0) {
    res = (res << 8) + *ptr++;
    --bytes;
  }
  return res;
}
void BagOfCells::Info::write_int(unsigned char* ptr, unsigned long long value, int bytes) {
  ptr += bytes;
  while (bytes) {
    *--ptr = value & 0xff;
    value >>= 8;
    --bytes;
  }
  DCHECK(!bytes);
}
long long BagOfCells::Info::parse_serialized_header(const td::Slice& slice) {
  invalidate();
  int sz = static_cast(std::min(slice.size(), static_cast(0xffff)));
  if (sz < 4) {
    return -10;  // want at least 10 bytes
  }
  const unsigned char* ptr = slice.ubegin();
  magic = (unsigned)read_int(ptr, 4);
  has_crc32c = false;
  has_index = false;
  has_cache_bits = false;
  ref_byte_size = 0;
  offset_byte_size = 0;
  root_count = cell_count = absent_count = -1;
  index_offset = data_offset = data_size = total_size = 0;
  if (magic != boc_generic && magic != boc_idx && magic != boc_idx_crc32c) {
    magic = 0;
    return 0;
  }
  if (sz < 5) {
    return -10;
  }
  td::uint8 byte = ptr[4];
  if (magic == boc_generic) {
    has_index = (byte >> 7) % 2 == 1;
    has_crc32c = (byte >> 6) % 2 == 1;
    has_cache_bits = (byte >> 5) % 2 == 1;
  } else {
    has_index = true;
    has_crc32c = magic == boc_idx_crc32c;
  }
  if (has_cache_bits && !has_index) {
    return 0;
  }
  ref_byte_size = byte & 7;
  if (ref_byte_size > 4 || ref_byte_size < 1) {
    return 0;
  }
  if (sz < 6) {
    return -7 - 3 * ref_byte_size;
  }
  offset_byte_size = ptr[5];
  if (offset_byte_size > 8 || offset_byte_size < 1) {
    return 0;
  }
  roots_offset = 6 + 3 * ref_byte_size + offset_byte_size;
  ptr += 6;
  sz -= 6;
  if (sz < ref_byte_size) {
    return -static_cast(roots_offset);
  }
  cell_count = (int)read_ref(ptr);
  if (cell_count <= 0) {
    cell_count = -1;
    return 0;
  }
  if (sz < 2 * ref_byte_size) {
    return -static_cast(roots_offset);
  }
  root_count = (int)read_ref(ptr + ref_byte_size);
  if (root_count <= 0) {
    root_count = -1;
    return 0;
  }
  index_offset = roots_offset;
  if (magic == boc_generic) {
    index_offset += (long long)root_count * ref_byte_size;
    has_roots = true;
  } else {
    if (root_count != 1) {
      return 0;
    }
  }
  data_offset = index_offset;
  if (has_index) {
    data_offset += (long long)cell_count * offset_byte_size;
  }
  if (sz < 3 * ref_byte_size) {
    return -static_cast(roots_offset);
  }
  absent_count = (int)read_ref(ptr + 2 * ref_byte_size);
  if (absent_count < 0 || absent_count > cell_count) {
    return 0;
  }
  if (sz < 3 * ref_byte_size + offset_byte_size) {
    return -static_cast(roots_offset);
  }
  data_size = read_offset(ptr + 3 * ref_byte_size);
  if (data_size > ((unsigned long long)cell_count << 10)) {
    return 0;
  }
  if (data_size > (1ull << 40)) {
    return 0;  // bag of cells with more than 1TiB data is unlikely
  }
  if (data_size < cell_count * (2ull + ref_byte_size) - ref_byte_size) {
    return 0;  // invalid header, too many cells for this amount of data bytes
  }
  valid = true;
  total_size = data_offset + data_size + (has_crc32c ? 4 : 0);
  return total_size;
}
td::Result BagOfCells::get_cell_slice(int idx, td::Slice data) {
  unsigned long long offs = get_idx_entry(idx - 1);
  unsigned long long offs_end = get_idx_entry(idx);
  if (offs > offs_end || offs_end > data.size()) {
    return td::Status::Error(PSLICE() << "invalid index entry [" << offs << "; " << offs_end << "], "
                                      << td::tag("data.size()", data.size()));
  }
  return data.substr(offs, td::narrow_cast(offs_end - offs));
}
td::Result> BagOfCells::deserialize_cell(int idx, td::Slice cells_slice,
                                                               td::Span> cells_span,
                                                               std::vector* cell_should_cache) {
  TRY_RESULT(cell_slice, get_cell_slice(idx, cells_slice));
  std::array, 4> refs_buf;
  CellSerializationInfo cell_info;
  TRY_STATUS(cell_info.init(cell_slice, info.ref_byte_size));
  if (cell_info.end_offset != cell_slice.size()) {
    return td::Status::Error("unused space in cell serialization");
  }
  auto refs = td::MutableSpan>(refs_buf).substr(0, cell_info.refs_cnt);
  for (int k = 0; k < cell_info.refs_cnt; k++) {
    int ref_idx = (int)info.read_ref(cell_slice.ubegin() + cell_info.refs_offset + k * info.ref_byte_size);
    if (ref_idx <= idx) {
      return td::Status::Error(PSLICE() << "bag-of-cells error: reference #" << k << " of cell #" << idx
                                        << " is to cell #" << ref_idx << " with smaller index");
    }
    if (ref_idx >= cell_count) {
      return td::Status::Error(PSLICE() << "bag-of-cells error: reference #" << k << " of cell #" << idx
                                        << " is to non-existent cell #" << ref_idx << ", only " << cell_count
                                        << " cells are defined");
    }
    refs[k] = cells_span[cell_count - ref_idx - 1];
    if (cell_should_cache) {
      auto& cnt = (*cell_should_cache)[ref_idx];
      if (cnt < 2) {
        cnt++;
      }
    }
  }
  return cell_info.create_data_cell(cell_slice, refs);
}
td::Result BagOfCells::deserialize(const td::Slice& data, int max_roots) {
  clear();
  long long size_est = info.parse_serialized_header(data);
  //LOG(INFO) << "estimated size " << size_est << ", true size " << data.size();
  if (size_est == 0) {
    return td::Status::Error(PSLICE() << "cannot deserialize bag-of-cells: invalid header, error " << size_est);
  }
  if (size_est < 0) {
    //LOG(ERROR) << "cannot deserialize bag-of-cells: not enough bytes (" << data.size() << " present, " << -size_est
    //<< " required)";
    return size_est;
  }
  if (size_est > (long long)data.size()) {
    //LOG(ERROR) << "cannot deserialize bag-of-cells: not enough bytes (" << data.size() << " present, " << size_est
    //<< " required)";
    return -size_est;
  }
  //LOG(INFO) << "estimated size " << size_est << ", true size " << data.size();
  if (info.root_count > max_roots) {
    return td::Status::Error("Bag-of-cells has more root cells than expected");
  }
  if (info.has_crc32c) {
    unsigned crc_computed = td::crc32c(td::Slice{data.ubegin(), data.uend() - 4});
    unsigned crc_stored = td::as(data.uend() - 4);
    if (crc_computed != crc_stored) {
      return td::Status::Error(PSLICE() << "bag-of-cells CRC32C mismatch: expected " << td::format::as_hex(crc_computed)
                                        << ", found " << td::format::as_hex(crc_stored));
    }
  }
  cell_count = info.cell_count;
  std::vector cell_should_cache;
  if (info.has_cache_bits) {
    cell_should_cache.resize(cell_count, 0);
  }
  roots.clear();
  roots.resize(info.root_count);
  auto* roots_ptr = data.substr(info.roots_offset).ubegin();
  for (int i = 0; i < info.root_count; i++) {
    int idx = 0;
    if (info.has_roots) {
      idx = (int)info.read_ref(roots_ptr + i * info.ref_byte_size);
    }
    if (idx < 0 || idx >= info.cell_count) {
      return td::Status::Error(PSLICE() << "bag-of-cells invalid root index " << idx);
    }
    roots[i].idx = info.cell_count - idx - 1;
    if (info.has_cache_bits) {
      auto& cnt = cell_should_cache[idx];
      if (cnt < 2) {
        cnt++;
      }
    }
  }
  if (info.has_index) {
    index_ptr = data.substr(info.index_offset).ubegin();
    // TODO: should we validate index here
  } else {
    index_ptr = nullptr;
    unsigned long long cur = 0;
    custom_index.reserve(info.cell_count);
    auto cells_slice = data.substr(info.data_offset, info.data_size);
    for (int i = 0; i < info.cell_count; i++) {
      CellSerializationInfo cell_info;
      auto status = cell_info.init(cells_slice, info.ref_byte_size);
      if (status.is_error()) {
        return td::Status::Error(PSLICE()
                                 << "invalid bag-of-cells failed to deserialize cell #" << i << " " << status.error());
      }
      cells_slice = cells_slice.substr(cell_info.end_offset);
      cur += cell_info.end_offset;
      custom_index.push_back(cur);
    }
    if (!cells_slice.empty()) {
      return td::Status::Error(PSLICE() << "invalid bag-of-cells last cell #" << info.cell_count - 1 << ": end offset "
                                        << cur << " is different from total data size " << info.data_size);
    }
  }
  auto cells_slice = data.substr(info.data_offset, info.data_size);
  std::vector][> cell_list;
  cell_list.reserve(cell_count);
  std::array, 4> refs_buf;
  for (int i = 0; i < cell_count; i++) {
    // reconstruct cell with index cell_count - 1 - i
    int idx = cell_count - 1 - i;
    auto r_cell = deserialize_cell(idx, cells_slice, cell_list, info.has_cache_bits ? &cell_should_cache : nullptr);
    if (r_cell.is_error()) {
      return td::Status::Error(PSLICE() << "invalid bag-of-cells failed to deserialize cell #" << idx << " "
                                        << r_cell.error());
    }
    cell_list.push_back(r_cell.move_as_ok());
    DCHECK(cell_list.back().not_null());
  }
  if (info.has_cache_bits) {
    for (int idx = 0; idx < cell_count; idx++) {
      auto should_cache = cell_should_cache[idx] > 1;
      auto stored_should_cache = get_cache_entry(idx);
      if (should_cache != stored_should_cache) {
        return td::Status::Error(PSLICE() << "invalid bag-of-cells cell #" << idx << " has wrong cache flag "
                                          << stored_should_cache);
      }
    }
  }
  custom_index.clear();
  index_ptr = nullptr;
  root_count = info.root_count;
  dangle_count = info.absent_count;
  for (auto& root_info : roots) {
    root_info.cell = cell_list[root_info.idx];
  }
  cell_list.clear();
  return size_est;
}
unsigned long long BagOfCells::get_idx_entry(int index) {
  auto raw = get_idx_entry_raw(index);
  if (info.has_cache_bits) {
    raw /= 2;
  }
  return raw;
}
bool BagOfCells::get_cache_entry(int index) {
  if (!info.has_cache_bits) {
    return true;
  }
  if (!info.has_index) {
    return true;
  }
  auto raw = get_idx_entry_raw(index);
  return raw % 2 == 1;
}
unsigned long long BagOfCells::get_idx_entry_raw(int index) {
  if (index < 0) {
    return 0;
  }
  if (!info.has_index) {
    return custom_index.at(index);
  } else if (index < info.cell_count && index_ptr) {
    return info.read_offset(index_ptr + (long)index * info.offset_byte_size);
  } else {
    // throw ?
    return 0;
  }
}
/*
 * 
 *  Simple BoC serialization/deserialization functions
 * 
 */
td::Result][> std_boc_deserialize(td::Slice data, bool can_be_empty, bool allow_nonzero_level) {
  if (data.empty() && can_be_empty) {
    return Ref]| ();
  }
  BagOfCells boc;
  auto res = boc.deserialize(data, 1);
  if (res.is_error()) {
    return res.move_as_error();
  }
  if (boc.get_root_count() != 1) {
    return td::Status::Error("bag of cells is expected to have exactly one root");
  }
  auto root = boc.get_root_cell();
  if (root.is_null()) {
    return td::Status::Error("bag of cells has null root cell (?)");
  }
  if (!allow_nonzero_level && root->get_level() != 0) {
    return td::Status::Error("bag of cells has a root with non-zero level");
  }
  return std::move(root);
}
td::Result>> std_boc_deserialize_multi(td::Slice data, int max_roots) {
  if (data.empty()) {
    return std::vector [>{};
  }
  BagOfCells boc;
  auto res = boc.deserialize(data, max_roots);
  if (res.is_error()) {
    return res.move_as_error();
  }
  int n = boc.get_root_count();
  std::vector][> roots;
  for (int i = 0; i < n; i++) {
    auto root = boc.get_root_cell(i);
    if (root.is_null()) {
      return td::Status::Error("bag of cells has a null root cell (?)");
    }
    if (root->get_level() != 0) {
      return td::Status::Error("bag of cells has a root with non-zero level");
    }
    roots.emplace_back(std::move(root));
  }
  return std::move(roots);
}
td::Result std_boc_serialize(Ref]| root, int mode) {
  if (root.is_null()) {
    return td::Status::Error("cannot serialize a null cell reference into a bag of cells");
  }
  BagOfCells boc;
  boc.add_root(std::move(root));
  auto res = boc.import_cells();
  if (res.is_error()) {
    return res.move_as_error();
  }
  return boc.serialize_to_slice(mode);
}
td::Result std_boc_serialize_multi(std::vector [> roots, int mode) {
  if (roots.empty()) {
    return td::BufferSlice{};
  }
  BagOfCells boc;
  boc.add_roots(std::move(roots));
  auto res = boc.import_cells();
  if (res.is_error()) {
    return res.move_as_error();
  }
  return boc.serialize_to_slice(mode);
}
td::Status std_boc_serialize_to_file(Ref]| root, td::FileFd& fd, int mode,
                                     td::CancellationToken cancellation_token) {
  if (root.is_null()) {
    return td::Status::Error("cannot serialize a null cell reference into a bag of cells");
  }
  td::Timer timer;
  BagOfCellsLogger logger(std::move(cancellation_token));
  BagOfCells boc;
  boc.set_logger(&logger);
  boc.add_root(std::move(root));
  TRY_STATUS(boc.import_cells());
  TRY_STATUS(boc.serialize_to_file(fd, mode));
  LOG(ERROR) << "serialization took " << timer.elapsed() << "s";
  return td::Status::OK();
}
/*
 * 
 *  Cell storage statistics
 * 
 */
td::Result CellStorageStat::compute_used_storage(Ref cs_ref, bool kill_dup,
                                                                            unsigned skip_count_root) {
  clear();
  TRY_RESULT(res, add_used_storage(std::move(cs_ref), kill_dup, skip_count_root));
  clear_seen();
  return res;
}
td::Result CellStorageStat::compute_used_storage(const CellSlice& cs, bool kill_dup,
                                                                            unsigned skip_count_root) {
  clear();
  TRY_RESULT(res, add_used_storage(cs, kill_dup, skip_count_root));
  clear_seen();
  return res;
}
td::Result CellStorageStat::compute_used_storage(CellSlice&& cs, bool kill_dup,
                                                                            unsigned skip_count_root) {
  clear();
  TRY_RESULT(res, add_used_storage(std::move(cs), kill_dup, skip_count_root));
  clear_seen();
  return res;
}
td::Result CellStorageStat::compute_used_storage(Ref cell, bool kill_dup,
                                                                            unsigned skip_count_root) {
  clear();
  TRY_RESULT(res, add_used_storage(std::move(cell), kill_dup, skip_count_root));
  clear_seen();
  return res;
}
td::Result CellStorageStat::add_used_storage(Ref cs_ref, bool kill_dup,
                                                                        unsigned skip_count_root) {
  if (cs_ref->is_unique()) {
    return add_used_storage(std::move(cs_ref.unique_write()), kill_dup, skip_count_root);
  } else {
    return add_used_storage(*cs_ref, kill_dup, skip_count_root);
  }
}
td::Result CellStorageStat::add_used_storage(const CellSlice& cs, bool kill_dup,
                                                                        unsigned skip_count_root) {
  if (!(skip_count_root & 1)) {
    ++cells;
    if (cells > limit_cells) {
      return td::Status::Error("too many cells");
    }
  }
  if (!(skip_count_root & 2)) {
    bits += cs.size();
    if (bits > limit_bits) {
      return td::Status::Error("too many bits");
    }
  }
  CellInfo res;
  for (unsigned i = 0; i < cs.size_refs(); i++) {
    TRY_RESULT(child, add_used_storage(cs.prefetch_ref(i), kill_dup));
    res.max_merkle_depth = std::max(res.max_merkle_depth, child.max_merkle_depth);
  }
  if (cs.special_type() == CellTraits::SpecialType::MerkleProof ||
      cs.special_type() == CellTraits::SpecialType::MerkleUpdate) {
    ++res.max_merkle_depth;
  }
  return res;
}
td::Result CellStorageStat::add_used_storage(CellSlice&& cs, bool kill_dup,
                                                                        unsigned skip_count_root) {
  if (!(skip_count_root & 1)) {
    ++cells;
    if (cells > limit_cells) {
      return td::Status::Error("too many cells");
    }
  }
  if (!(skip_count_root & 2)) {
    bits += cs.size();
    if (bits > limit_bits) {
      return td::Status::Error("too many bits");
    }
  }
  CellInfo res;
  while (cs.size_refs()) {
    TRY_RESULT(child, add_used_storage(cs.fetch_ref(), kill_dup));
    res.max_merkle_depth = std::max(res.max_merkle_depth, child.max_merkle_depth);
  }
  if (cs.special_type() == CellTraits::SpecialType::MerkleProof ||
      cs.special_type() == CellTraits::SpecialType::MerkleUpdate) {
    ++res.max_merkle_depth;
  }
  return res;
}
td::Result CellStorageStat::add_used_storage(Ref cell, bool kill_dup,
                                                                        unsigned skip_count_root) {
  if (cell.is_null()) {
    return td::Status::Error("cell is null");
  }
  if (kill_dup) {
    auto ins = seen.emplace(cell->get_hash(), CellInfo{});
    if (!ins.second) {
      return ins.first->second;
    }
  }
  vm::CellSlice cs{vm::NoVm{}, std::move(cell)};
  return add_used_storage(std::move(cs), kill_dup, skip_count_root);
}
void NewCellStorageStat::add_cell(Ref | cell) {
  dfs(std::move(cell), true, false);
}
void NewCellStorageStat::add_proof(Ref | cell, const CellUsageTree* usage_tree) {
  CHECK(usage_tree);
  usage_tree_ = usage_tree;
  dfs(std::move(cell), false, true);
}
void NewCellStorageStat::add_cell_and_proof(Ref | cell, const CellUsageTree* usage_tree) {
  CHECK(usage_tree);
  usage_tree_ = usage_tree;
  dfs(std::move(cell), true, true);
}
NewCellStorageStat::Stat NewCellStorageStat::tentative_add_cell(Ref | cell) const {
  NewCellStorageStat stat;
  stat.parent_ = this;
  stat.add_cell(std::move(cell));
  return stat.get_stat();
}
NewCellStorageStat::Stat NewCellStorageStat::tentative_add_proof(Ref | cell,
                                                                 const CellUsageTree* usage_tree) const {
  NewCellStorageStat stat;
  stat.parent_ = this;
  stat.add_proof(std::move(cell), usage_tree);
  return stat.get_proof_stat();
}
void NewCellStorageStat::dfs(Ref | cell, bool need_stat, bool need_proof_stat) {
  if (cell.is_null()) {
    // FIXME: save error flag?
    return;
  }
  if (need_stat) {
    stat_.internal_refs++;
    if ((parent_ && parent_->seen_.count(cell->get_hash()) != 0) || !seen_.insert(cell->get_hash()).second) {
      need_stat = false;
    } else {
      stat_.cells++;
    }
  }
  if (need_proof_stat) {
    auto tree_node = cell->get_tree_node();
    if (!tree_node.empty() && tree_node.is_from_tree(usage_tree_)) {
      proof_stat_.external_refs++;
      need_proof_stat = false;
    } else {
      proof_stat_.internal_refs++;
      if ((parent_ && parent_->proof_seen_.count(cell->get_hash()) != 0) ||
          !proof_seen_.insert(cell->get_hash()).second) {
        need_proof_stat = false;
      } else {
        proof_stat_.cells++;
      }
    }
  }
  if (!need_proof_stat && !need_stat) {
    return;
  }
  vm::CellSlice cs{vm::NoVm{}, std::move(cell)};
  if (need_stat) {
    stat_.bits += cs.size();
  }
  if (need_proof_stat) {
    proof_stat_.bits += cs.size();
  }
  while (cs.size_refs()) {
    dfs(cs.fetch_ref(), need_stat, need_proof_stat);
  }
}
bool VmStorageStat::add_storage(Ref | cell) {
  if (cell.is_null() || !check_visited(cell)) {
    return true;
  }
  if (cells >= limit) {
    return false;
  }
  ++cells;
  bool special;
  auto cs = load_cell_slice_special(std::move(cell), special);
  return cs.is_valid() && add_storage(std::move(cs));
}
bool VmStorageStat::add_storage(const CellSlice& cs) {
  bits += cs.size();
  refs += cs.size_refs();
  for (unsigned i = 0; i < cs.size_refs(); i++) {
    if (!add_storage(cs.prefetch_ref(i))) {
      return false;
    }
  }
  return true;
}
static td::uint64 estimate_prunned_size() {
  return 41;
}
static td::uint64 estimate_serialized_size(const Ref& cell) {
  return cell->get_serialized_size() + cell->size_refs() * 3 + 3;
}
void ProofStorageStat::add_cell(const Ref& cell) {
  auto& status = cells_[cell->get_hash()];
  if (status == c_loaded) {
    return;
  }
  if (status == c_prunned) {
    proof_size_ -= estimate_prunned_size();
  }
  status = c_loaded;
  proof_size_ += estimate_serialized_size(cell);
  for (unsigned i = 0; i < cell->size_refs(); ++i) {
    auto& child_status = cells_[cell->get_ref(i)->get_hash()];
    if (child_status == c_none) {
      child_status = c_prunned;
      proof_size_ += estimate_prunned_size();
    }
  }
}
td::uint64 ProofStorageStat::estimate_proof_size() const {
  return proof_size_;
}
}  // namespace vm |  |  |  |  |  |  |  |  |  |