/*
    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 .
*/
#pragma once
#include "ton/ton-types.h"
#include "crypto/vm/cellslice.h"
#include "block/block-parse.h"
#include "td/actor/actor.h"
#include "tonlib/tonlib/TonlibClientWrapper.h"
#include 
#include "keyring/keyring.h"
namespace ton {
struct ContractAddress {
  WorkchainId wc = workchainIdNotYet;
  td::Bits256 addr = td::Bits256::zero();
  ContractAddress() = default;
  ContractAddress(WorkchainId wc, td::Bits256 addr) : wc(wc), addr(addr) {
  }
  std::string to_string() const {
    return PSTRING() << wc << ":" << addr.to_hex();
  }
  td::Ref to_cellslice() const {
    return block::tlb::t_MsgAddressInt.pack_std_address(wc, addr);
  }
  static td::Result parse(td::Slice s) {
    TRY_RESULT(x, block::StdAddress::parse(s));
    return ContractAddress(x.workchain, x.addr);
  }
  bool operator==(const ContractAddress& other) const {
    return wc == other.wc && addr == other.addr;
  }
  bool operator!=(const ContractAddress& other) const {
    return !(*this == other);
  }
  bool operator<(const ContractAddress& other) const {
    return wc == other.wc ? addr < other.addr : wc < other.wc;
  }
};
void run_get_method(ContractAddress address, td::actor::ActorId client, std::string method,
                    std::vector> args,
                    td::Promise>> promise);
void check_contract_exists(ContractAddress address, td::actor::ActorId client,
                           td::Promise promise);
void get_contract_balance(ContractAddress address, td::actor::ActorId client,
                          td::Promise promise);
class FabricContractWrapper : public td::actor::Actor {
 public:
  class Callback {
   public:
    virtual ~Callback() = default;
    virtual void on_transaction(tl_object_ptr transaction) = 0;
  };
  explicit FabricContractWrapper(ContractAddress address, td::actor::ActorId client,
                                 td::actor::ActorId keyring, td::unique_ptr callback,
                                 td::uint64 last_processed_lt);
  void start_up() override;
  void alarm() override;
  void run_get_method(std::string method, std::vector> args,
                      td::Promise>> promise);
  void send_internal_message(ContractAddress dest, td::RefInt256 coins, vm::CellSlice body,
                             td::Promise promise);
 private:
  ContractAddress address_;
  td::actor::ActorId client_;
  td::actor::ActorId keyring_;
  td::unique_ptr callback_;
  td::Timestamp process_transactions_at_ = td::Timestamp::now();
  td::uint64 last_processed_lt_ = 0;
  struct PendingMessage {
    ContractAddress dest;
    td::RefInt256 value;
    vm::CellSlice body;
    td::Bits256 body_hash;
    td::Promise promise;
  };
  struct CurrentExtMessage {
    std::vector int_msgs;
    td::uint32 seqno = 0;
    bool sent = false;
    td::Bits256 ext_msg_body_hash = td::Bits256::zero();
    td::uint32 timeout = 0;
  };
  std::queue pending_messages_;
  td::Timestamp send_message_at_ = td::Timestamp::never();
  td::optional current_ext_message_;
  void load_transactions();
  void load_last_transactions(std::vector> transactions,
                              tl_object_ptr next_id, td::uint32 utime);
  void loaded_last_transactions(
      td::Result>, td::uint32>> R);
  void do_send_external_message();
  void do_send_external_message_cont(td::uint32 seqno, td::uint32 subwallet_id, td::Bits256 public_key);
  void do_send_external_message_cont2(td::Ref ext_msg_body);
  void do_send_external_message_finish(td::Result>*> R);
};
template 
inline td::Result entry_to_int(const tl_object_ptr& entry) {
  auto num = dynamic_cast(entry.get());
  if (num == nullptr) {
    return td::Status::Error("Unexpected value type");
  }
  return td::to_integer_safe(num->number_->number_);
}
template <>
inline td::Result entry_to_int(const tl_object_ptr& entry) {
  auto num = dynamic_cast(entry.get());
  if (num == nullptr) {
    return td::Status::Error("Unexpected value type");
  }
  auto x = td::dec_string_to_int256(num->number_->number_);
  if (x.is_null()) {
    return td::Status::Error("Invalid integer value");
  }
  return x;
}
inline td::Result entry_to_bits256(const tl_object_ptr& entry) {
  TRY_RESULT(x, entry_to_int(entry));
  td::Bits256 bits;
  if (!x->export_bytes(bits.data(), 32, false)) {
    return td::Status::Error("Invalid int256");
  }
  return bits;
}
bool store_coins(vm::CellBuilder& b, const td::RefInt256& x);
bool store_coins(vm::CellBuilder& b, td::uint64 x);
struct FabricContractInit {
  ContractAddress address;
  td::Ref state_init;
  td::Ref msg_body;
};
td::Result generate_fabric_contract(td::actor::ActorId keyring);
td::Ref create_new_contract_message_body(td::Ref info, td::Bits256 microchunk_hash,
                                                   td::uint64 query_id, td::RefInt256 rate, td::uint32 max_span);
struct StorageContractData {
  bool active;
  td::RefInt256 balance;
  td::Bits256 microchunk_hash;
  td::uint64 file_size;
  td::uint64 next_proof;
  td::RefInt256 rate_per_mb_day;
  td::uint32 max_span;
  td::uint32 last_proof_time;
  td::Bits256 torrent_hash;
};
void get_storage_contract_data(ContractAddress address, td::actor::ActorId client,
                               td::Promise promise);
}  // namespace ton