/*
    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 "GenericAccount.h"
#include "block/block-auto.h"
#include "block/block-parse.h"
namespace ton {
namespace smc {
td::Ref pack_grams(td::uint64 amount) {
  vm::CellBuilder cb;
  block::tlb::t_Grams.store_integer_value(cb, td::BigInt256(amount));
  return vm::load_cell_slice_ref(cb.finalize());
}
bool unpack_grams(td::Ref cs, td::uint64& amount) {
  td::RefInt256 got;
  if (!block::tlb::t_Grams.as_integer_to(cs, got)) {
    return false;
  }
  if (!got->unsigned_fits_bits(63)) {
    return false;
  }
  auto x = got->to_long();
  if (x < 0) {
    return false;
  }
  amount = x;
  return true;
}
}  // namespace smc
td::Ref GenericAccount::get_init_state(const td::Ref& code,
                                                 const td::Ref& data) noexcept {
  return vm::CellBuilder()
      .store_zeroes(2)
      .store_ones(2)
      .store_zeroes(1)
      .store_ref(std::move(code))
      .store_ref(std::move(data))
      .finalize();
}
block::StdAddress GenericAccount::get_address(ton::WorkchainId workchain_id,
                                              const td::Ref& init_state) noexcept {
  return block::StdAddress(workchain_id, init_state->get_hash().bits(), true /*bounce*/);
}
void GenericAccount::store_int_message(vm::CellBuilder& cb, const block::StdAddress& dest_address, td::int64 gramms,
                                       td::Ref extra_currencies) {
  td::BigInt256 dest_addr;
  dest_addr.import_bits(dest_address.addr.as_bitslice());
  cb.store_zeroes(1)
      .store_ones(1)
      .store_long(dest_address.bounceable, 1)
      .store_zeroes(3)
      .store_ones(1)
      .store_zeroes(2)
      .store_long(dest_address.workchain, 8)
      .store_int256(dest_addr, 256);
  block::tlb::t_Grams.store_integer_value(cb, td::BigInt256(gramms));
  cb.store_maybe_ref(extra_currencies);
  cb.store_zeroes(8 + 64 + 32);
}
td::Ref GenericAccount::create_ext_message(const block::StdAddress& address, td::Ref new_state,
                                                     td::Ref body) noexcept {
  block::gen::Message::Record message;
  /*info*/ {
    block::gen::CommonMsgInfo::Record_ext_in_msg_info info;
    /* src */
    tlb::csr_pack(info.src, block::gen::MsgAddressExt::Record_addr_none{});
    /* dest */ {
      block::gen::MsgAddressInt::Record_addr_std dest;
      dest.anycast = vm::CellBuilder().store_zeroes(1).as_cellslice_ref();
      dest.workchain_id = address.workchain;
      dest.address = address.addr;
      tlb::csr_pack(info.dest, dest);
    }
    /* import_fee */ {
      vm::CellBuilder cb;
      block::tlb::t_Grams.store_integer_value(cb, td::BigInt256(0));
      info.import_fee = cb.as_cellslice_ref();
    }
    tlb::csr_pack(message.info, info);
  }
  /* init */ {
    if (new_state.not_null()) {
      // Just(Left(new_state))
      message.init = vm::CellBuilder()
                         .store_ones(1)
                         .store_zeroes(1)
                         .append_cellslice(vm::load_cell_slice(new_state))
                         .as_cellslice_ref();
    } else {
      message.init = vm::CellBuilder().store_zeroes(1).as_cellslice_ref();
      CHECK(message.init.not_null());
    }
  }
  /* body */ {
    message.body = vm::CellBuilder().store_zeroes(1).append_cellslice(vm::load_cell_slice_ref(body)).as_cellslice_ref();
  }
  td::Ref res;
  tlb::type_pack_cell(res, block::gen::t_Message_Any, message);
  if (res.is_null()) {
    /* body */ { message.body = vm::CellBuilder().store_ones(1).store_ref(std::move(body)).as_cellslice_ref(); }
    tlb::type_pack_cell(res, block::gen::t_Message_Any, message);
  }
  CHECK(res.not_null());
  return res;
}
td::Result GenericAccount::get_public_key(const SmartContract& sc) {
  auto answer = sc.run_get_method("get_public_key");
  if (!answer.success) {
    return td::Status::Error("get_public_key failed");
  }
  auto do_get_public_key = [&]() -> td::Result {
    auto key = answer.stack.write().pop_int_finite();
    td::SecureString bytes(32);
    if (!key->export_bytes(bytes.as_mutable_slice().ubegin(), bytes.size(), false)) {
      return td::Status::Error("get_public_key failed");
    }
    return td::Ed25519::PublicKey(std::move(bytes));
  };
  return TRY_VM(do_get_public_key());
}
td::Result GenericAccount::get_seqno(const SmartContract& sc) {
  return TRY_VM([&]() -> td::Result {
    auto answer = sc.run_get_method("seqno");
    if (!answer.success) {
      return td::Status::Error("seqno get method failed");
    }
    return static_cast(answer.stack.write().pop_long_range(std::numeric_limits::max()));
  }());
}
td::Result GenericAccount::get_wallet_id(const SmartContract& sc) {
  return TRY_VM([&]() -> td::Result {
    auto answer = sc.run_get_method("wallet_id");
    if (!answer.success) {
      return td::Status::Error("wallet_id get method failed");
    }
    return static_cast(answer.stack.write().pop_long_range(std::numeric_limits::max()));
  }());
}
}  // namespace ton