/*
    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
*/
#pragma once
#include "vm/cells.h"
#include "vm/cellslice.h"
#include "Ed25519.h"
#include "block/block-auto.h"
#include "block/block-parse.h"
#include "td/utils/Variant.h"
#include "SmartContract.h"
#include "SmartContractCode.h"
namespace ton {
namespace pchan {
//
// Payment channels
//
struct Config {
  td::uint32 init_timeout{0};
  td::uint32 close_timeout{0};
  td::SecureString a_key;
  td::SecureString b_key;
  block::StdAddress a_addr;
  block::StdAddress b_addr;
  td::uint64 channel_id{0};
  td::uint64 min_A_extra{0};
  td::Ref serialize() const;
};
struct MsgInit {
  td::uint64 inc_A{0};
  td::uint64 inc_B{0};
  td::uint64 min_A{0};
  td::uint64 min_B{0};
  td::uint64 channel_id{0};
  td::Ref serialize() const;
};
struct Promise {
  td::uint64 channel_id;
  td::uint64 promise_A{0};
  td::uint64 promise_B{0};
  td::Ref serialize() const;
};
td::Ref maybe_sign(const td::Ref& msg, const td::Ed25519::PrivateKey* key);
td::Ref maybe_ref(td::Ref msg);
struct MsgClose {
  td::uint64 extra_A{0};
  td::uint64 extra_B{0};
  td::Ref signed_promise;
  td::Ref serialize() const;
};
struct MsgTimeout {
  td::Ref serialize() const;
};
struct MsgPayout {
  td::Ref serialize() const;
};
struct SignedPromise {
  Promise promise;
  td::optional o_signature;
  bool unpack(td::Ref cell);
  static td::SecureString signature(const td::Ed25519::PrivateKey* key, const td::Ref& promise);
  static td::Ref create_and_serialize(td::Slice signature, const td::Ref& promise);
  static td::Ref create_and_serialize(const td::Ed25519::PrivateKey* key, const td::Ref& promise);
};
struct StateInit {
  bool signed_A{false};
  bool signed_B{false};
  td::uint64 min_A{0};
  td::uint64 min_B{0};
  td::uint64 A{0};
  td::uint64 B{0};
  td::uint32 expire_at{0};
  td::Ref serialize() const;
};
struct StateClose {
  bool signed_A{false};
  bool signed_B{false};
  td::uint64 promise_A{0};
  td::uint64 promise_B{0};
  td::uint64 A{0};
  td::uint64 B{0};
  td::uint32 expire_at{0};
};
struct StatePayout {
  td::uint64 A{0};
  td::uint64 B{0};
};
struct Data {
  td::Ref config;
  td::Ref state;
  static td::Ref init_state();
  td::Ref serialize() const;
};
template 
struct MsgBuilder {
  td::Ed25519::PrivateKey* a_key{nullptr};
  td::Ed25519::PrivateKey* b_key{nullptr};
  T&& with_a_key(td::Ed25519::PrivateKey* key) && {
    a_key = key;
    return static_cast(*this);
  }
  T&& with_b_key(td::Ed25519::PrivateKey* key) && {
    b_key = key;
    return static_cast(*this);
  }
  td::Ref finalize() && {
    block::gen::ChanSignedMsg::Record rec;
    auto msg = static_cast(*this).msg.serialize();
    rec.msg = vm::load_cell_slice_ref(msg);
    rec.sig_A = maybe_ref(maybe_sign(msg, a_key));
    rec.sig_B = maybe_ref(maybe_sign(msg, b_key));
    block::gen::ChanOp::Record op_rec;
    CHECK(tlb::csr_pack(op_rec.msg, rec));
    LOG(ERROR) << op_rec.msg->size();
    td::Ref res;
    CHECK(tlb::pack_cell(res, op_rec));
    return res;
  }
};
struct MsgInitBuilder : public MsgBuilder {
  MsgInit msg;
  MsgInitBuilder&& min_A(td::uint64 value) && {
    msg.min_A = value;
    return std::move(*this);
  }
  MsgInitBuilder&& min_B(td::uint64 value) && {
    msg.min_B = value;
    return std::move(*this);
  }
  MsgInitBuilder&& inc_A(td::uint64 value) && {
    msg.inc_A = value;
    return std::move(*this);
  }
  MsgInitBuilder&& inc_B(td::uint64 value) && {
    msg.inc_B = value;
    return std::move(*this);
  }
  MsgInitBuilder&& channel_id(td::uint64 value) && {
    msg.channel_id = value;
    return std::move(*this);
  }
};
struct MsgTimeoutBuilder : public MsgBuilder {
  MsgTimeout msg;
};
struct MsgPayoutBuilder : public MsgBuilder {
  MsgPayout msg;
};
struct MsgCloseBuilder : public MsgBuilder {
  MsgClose msg;
  MsgCloseBuilder&& extra_A(td::uint64 value) && {
    msg.extra_A = value;
    return std::move(*this);
  }
  MsgCloseBuilder&& extra_B(td::uint64 value) && {
    msg.extra_B = value;
    return std::move(*this);
  }
  MsgCloseBuilder&& signed_promise(td::Ref signed_promise) && {
    msg.signed_promise = vm::load_cell_slice_ref(signed_promise);
    return std::move(*this);
  }
};
struct SignedPromiseBuilder {
  Promise promise;
  td::optional o_signature;
  td::Ed25519::PrivateKey* key{nullptr};
  SignedPromiseBuilder& with_key(td::Ed25519::PrivateKey* key) {
    this->key = key;
    return *this;
  }
  SignedPromiseBuilder& promise_A(td::uint64 value) {
    promise.promise_A = value;
    return *this;
  }
  SignedPromiseBuilder& promise_B(td::uint64 value) {
    promise.promise_B = value;
    return *this;
  }
  SignedPromiseBuilder& channel_id(td::uint64 value) {
    promise.channel_id = value;
    return *this;
  }
  SignedPromiseBuilder& signature(td::SecureString signature) {
    o_signature = std::move(signature);
    return *this;
  }
  bool check_signature(td::Slice signature, const td::Ed25519::PublicKey& pk) {
    return pk.verify_signature(promise.serialize()->get_hash().as_slice(), signature).is_ok();
  }
  td::SecureString calc_signature() {
    CHECK(key);
    return SignedPromise::signature(key, promise.serialize());
  }
  td::Ref finalize() {
    if (o_signature) {
      return SignedPromise::create_and_serialize(o_signature.value().copy(), promise.serialize());
    } else {
      return SignedPromise::create_and_serialize(key, promise.serialize());
    }
  }
};
}  // namespace pchan
class PaymentChannel : public SmartContract {
 public:
  PaymentChannel(State state) : SmartContract(std::move(state)) {
  }
  struct Info {
    pchan::Config config;
    td::Variant state;
    std::string description;
  };
  td::Result get_info() const;
  static td::Ref create(State state) {
    return td::Ref(true, std::move(state));
  }
  static td::optional guess_revision(const vm::Cell::Hash& code_hash);
  static td::Ref create(const pchan::Config& config, td::int32 revision) {
    State state;
    state.code = SmartContractCode::get_code(SmartContractCode::PaymentChannel, revision);
    pchan::Data data;
    data.config = config.serialize();
    pchan::StateInit init;
    data.state = init.serialize();
    state.data = data.serialize();
    return create(std::move(state));
  }
};
}  // namespace ton