/*
    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 "td/utils/int_types.h"
#include "td/utils/buffer.h"
#include "auto/tl/ton_api.h"
#include "td/utils/UInt.h"
#include "td/utils/Variant.h"
#include "td/actor/actor.h"
#include "crypto/common/bitstring.h"
#include "crypto/Ed25519.h"
#include "common/errorcode.h"
namespace ton {
class Encryptor;
class EncryptorAsync;
class Decryptor;
class DecryptorAsync;
class PublicKeyHash {
 public:
  explicit PublicKeyHash(td::Bits256 value) : value_(value) {
  }
  explicit PublicKeyHash(const tl_object_ptr &value);
  PublicKeyHash() {
  }
  static PublicKeyHash zero() {
    return PublicKeyHash{td::Bits256::zero()};
  }
  explicit PublicKeyHash(td::Slice data) {
    CHECK(data.size() == 32);
    value_.as_slice().copy_from(data);
  }
  td::UInt256 uint256_value() const {
    td::UInt256 x;
    x.as_slice().copy_from(value_.as_slice());
    return x;
  }
  td::Bits256 bits256_value() const {
    return value_;
  }
  auto tl() const {
    return value_;
  }
  bool operator<(const PublicKeyHash &with) const {
    return value_ < with.value_;
  }
  bool operator==(const PublicKeyHash &with) const {
    return value_ == with.value_;
  }
  bool operator!=(const PublicKeyHash &with) const {
    return value_ != with.value_;
  }
  td::Slice as_slice() const {
    return td::as_slice(value_);
  }
  bool is_zero() const {
    return value_.is_zero();
  }
 private:
  td::Bits256 value_;
};
namespace pubkeys {
class Ed25519 {
 private:
  td::Bits256 data_;
 public:
  Ed25519(const ton_api::pub_ed25519 &obj) {
    data_ = obj.key_;
  }
  Ed25519(td::Bits256 id) : data_(id) {
  }
  Ed25519() {
  }
  Ed25519(td::Ed25519::PublicKey pk);
  td::Ed25519::PublicKey export_key() {
    return td::Ed25519::PublicKey{td::SecureString(data_.as_slice())};
  }
  auto raw() const {
    return data_;
  }
  td::uint32 serialized_size() const {
    return 36;
  }
  tl_object_ptr tl() const {
    return create_tl_object(data_);
  }
  bool operator==(const Ed25519 &with) const {
    return data_ == with.data_;
  }
  bool operator!=(const Ed25519 &with) const {
    return data_ != with.data_;
  }
};
class AES {
 private:
  td::Bits256 data_;
 public:
  ~AES() {
    data_.set_zero_s();
  }
  AES(const ton_api::pub_aes &obj) {
    data_ = obj.key_;
  }
  AES(td::Slice data) {
    CHECK(data.size() == 32);
    data_.as_slice().copy_from(data);
  }
  AES(td::Bits256 data) : data_(data) {
  }
  td::uint32 serialized_size() const {
    return 36;
  }
  tl_object_ptr tl() const {
    return create_tl_object(data_);
  }
  bool operator==(const AES &with) const {
    return data_ == with.data_;
  }
  bool operator!=(const AES &with) const {
    return data_ != with.data_;
  }
};
class Unenc {
 private:
  td::SharedSlice data_;
 public:
  Unenc(const ton_api::pub_unenc &obj) {
    data_ = td::SharedSlice{obj.data_.as_slice()};
  }
  Unenc(const Unenc &obj) {
    data_ = obj.data_.clone();
  }
  explicit Unenc(td::BufferSlice data) : data_(td::SharedSlice{data.as_slice()}) {
  }
  explicit Unenc(td::Slice data) : data_(td::SharedSlice{data}) {
  }
  explicit Unenc(td::SharedSlice data) : data_(std::move(data)) {
  }
  td::uint32 serialized_size() const {
    return static_cast(data_.size()) + 8;
  }
  tl_object_ptr tl() const {
    return create_tl_object(data_.clone_as_buffer_slice());
  }
  bool operator==(const Unenc &with) const {
    return data_.as_slice() == with.data_.as_slice();
  }
  bool operator!=(const Unenc &with) const {
    return data_.as_slice() != with.data_.as_slice();
  }
};  // namespace pubkeys
class Overlay {
 private:
  td::SharedSlice data_;
 public:
  Overlay(const ton_api::pub_overlay &obj) {
    data_ = td::SharedSlice{obj.name_.as_slice()};
  }
  Overlay(const Overlay &obj) {
    data_ = obj.data_.clone();
  }
  explicit Overlay(td::BufferSlice data) : data_(td::SharedSlice{data.as_slice()}) {
  }
  explicit Overlay(td::Slice data) : data_(td::SharedSlice{data}) {
  }
  explicit Overlay(td::SharedSlice data) : data_(std::move(data)) {
  }
  td::uint32 serialized_size() const {
    return static_cast(data_.size()) + 8;
  }
  tl_object_ptr tl() const {
    return create_tl_object(data_.clone_as_buffer_slice());
  }
  bool operator==(const Overlay &with) const {
    return data_.as_slice() == with.data_.as_slice();
  }
  bool operator!=(const Overlay &with) const {
    return data_.as_slice() != with.data_.as_slice();
  }
};
}  // namespace pubkeys
class PublicKey {
 private:
  class Empty {
   public:
    tl_object_ptr tl() const {
      UNREACHABLE();
    }
    td::uint32 serialized_size() const {
      UNREACHABLE();
    }
    bool operator==(const Empty &with) const {
      return false;
    }
    bool operator!=(const Empty &with) const {
      return true;
    }
  };
  td::Variant pub_key_{Empty{}};
 public:
  explicit PublicKey(const tl_object_ptr &id);
  PublicKey() {
  }
  PublicKey(pubkeys::Ed25519 pub) : pub_key_(std::move(pub)) {
  }
  PublicKey(pubkeys::AES pub) : pub_key_(std::move(pub)) {
  }
  PublicKey(pubkeys::Unenc pub) : pub_key_(std::move(pub)) {
  }
  PublicKey(pubkeys::Overlay pub) : pub_key_(std::move(pub)) {
  }
  bool empty() const;
  PublicKeyHash compute_short_id() const;
  td::uint32 serialized_size() const;
  tl_object_ptr tl() const;
  td::BufferSlice export_as_slice() const;
  static td::Result import(td::Slice s);
  pubkeys::Ed25519 ed25519_value() const {
    CHECK(pub_key_.get_offset() == pub_key_.offset());
    return pub_key_.get();
  }
  td::Result> create_encryptor() const;
  td::Result> create_encryptor_async() const;
  bool operator==(const PublicKey &with) const {
    return pub_key_ == with.pub_key_;
  }
  bool operator!=(const PublicKey &with) const {
    return !(pub_key_ == with.pub_key_);
  }
};
namespace privkeys {
class Ed25519 {
 private:
  td::Bits256 data_;
 public:
  ~Ed25519() {
    data_.set_zero_s();
  }
  Ed25519(const ton_api::pk_ed25519 &obj) {
    data_ = obj.key_;
  }
  Ed25519(td::Bits256 id) : data_(id) {
    id.set_zero_s();
  }
  Ed25519(td::Slice data) {
    CHECK(data.size() == 32);
    data_.as_slice().copy_from(data);
  }
  Ed25519() {
  }
  Ed25519(td::Ed25519::PrivateKey pk);
  td::Ed25519::PrivateKey export_key() {
    return td::Ed25519::PrivateKey{td::SecureString(data_.as_slice())};
  }
  td::SecureString export_as_slice() const {
    td::SecureString s{36};
    auto id = ton_api::pk_ed25519::ID;
    s.as_mutable_slice().copy_from(td::Slice{reinterpret_cast(&id), 4});
    s.as_mutable_slice().remove_prefix(4).copy_from(data_.as_slice());
    return s;
  }
  bool exportable() const {
    return true;
  }
  static td::Result import(td::Slice slice) {
    if (slice.size() != 32) {
      return td::Status::Error(ErrorCode::error, "bad length");
    }
    return Ed25519{slice};
  }
  tl_object_ptr tl() const {
    return create_tl_object(data_);
  }
  tl_object_ptr pub_tl() const;
  pubkeys::Ed25519 pub() const;
  static Ed25519 random();
};
class AES {
 private:
  td::Bits256 data_;
 public:
  ~AES() {
    data_.set_zero_s();
  }
  AES(const ton_api::pk_aes &obj) {
    data_ = obj.key_;
  }
  AES(td::Slice data) {
    CHECK(data.size() == 32);
    data_.as_slice().copy_from(data);
  }
  td::SecureString export_as_slice() const {
    td::SecureString s{40};
    auto id = ton_api::pk_aes::ID;
    s.as_mutable_slice().copy_from(td::Slice{reinterpret_cast(&id), 4});
    s.as_mutable_slice().remove_prefix(4).copy_from(data_.as_slice());
    return s;
  }
  bool exportable() const {
    return true;
  }
  static td::Result import(td::Slice slice) {
    if (slice.size() != 32) {
      return td::Status::Error(ErrorCode::error, "bad length");
    }
    return AES{slice};
  }
  tl_object_ptr tl() const {
    return create_tl_object(data_);
  }
  tl_object_ptr pub_tl() const {
    return create_tl_object(data_);
  }
  pubkeys::AES pub() const {
    return pubkeys::AES{data_};
  }
};
class Unenc {
 private:
  td::SharedSlice data_;
 public:
  Unenc(const ton_api::pk_unenc &obj) {
    data_ = td::SharedSlice{obj.data_.as_slice()};
  }
  Unenc(const Unenc &obj) {
    data_ = obj.data_.clone();
  }
  explicit Unenc(td::BufferSlice data) : data_(td::SharedSlice{data.as_slice()}) {
  }
  explicit Unenc(td::Slice data) : data_(td::SharedSlice{data}) {
  }
  explicit Unenc(td::SharedSlice data) : data_(std::move(data)) {
  }
  td::SecureString export_as_slice() const {
    UNREACHABLE();
  }
  bool exportable() const {
    return false;
  }
  tl_object_ptr tl() const {
    return create_tl_object(data_.clone_as_buffer_slice());
  }
  tl_object_ptr pub_tl() const {
    return create_tl_object(data_.clone_as_buffer_slice());
  }
  pubkeys::Unenc pub() const {
    return pubkeys::Unenc{data_.clone()};
  }
};
class Overlay {
 private:
  td::SharedSlice data_;
 public:
  Overlay(const ton_api::pk_overlay &obj) {
    data_ = td::SharedSlice{obj.name_.as_slice()};
  }
  Overlay(const Overlay &obj) {
    data_ = obj.data_.clone();
  }
  explicit Overlay(td::BufferSlice data) : data_(td::SharedSlice{data.as_slice()}) {
  }
  explicit Overlay(td::Slice data) : data_(td::SharedSlice{data}) {
  }
  explicit Overlay(td::SharedSlice data) : data_(std::move(data)) {
  }
  td::SecureString export_as_slice() const {
    UNREACHABLE();
  }
  bool exportable() const {
    return false;
  }
  tl_object_ptr tl() const {
    return create_tl_object(data_.clone_as_buffer_slice());
  }
  tl_object_ptr pub_tl() const {
    return create_tl_object(data_.clone_as_buffer_slice());
  }
  pubkeys::Overlay pub() const {
    return pubkeys::Overlay{data_.clone()};
  }
};
}  // namespace privkeys
class PrivateKey {
 private:
  class Empty {
   public:
    td::SecureString export_as_slice() const {
      UNREACHABLE();
    }
    bool exportable() const {
      return false;
    }
    tl_object_ptr tl() const {
      UNREACHABLE();
    }
    tl_object_ptr pub_tl() const {
      UNREACHABLE();
    }
    PublicKey pub() const {
      UNREACHABLE();
    }
  };
  td::Variant priv_key_{Empty{}};
 public:
  explicit PrivateKey(const tl_object_ptr &pk);
  template 
  PrivateKey(T key) : priv_key_(std::move(key)) {
  }
  PrivateKey() {
  }
  bool empty() const;
  PublicKey compute_public_key() const;
  PublicKeyHash compute_short_id() const;
  td::SecureString export_as_slice() const;
  static td::Result import(td::Slice s);
  bool exportable() const;
  tl_object_ptr tl() const;
  td::Result> create_decryptor() const;
  td::Result> create_decryptor_async() const;
};
}  // namespace ton