/*
    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 "dht.hpp"
#include "td/utils/tl_storers.h"
#include "td/utils/crypto.h"
#include "td/utils/Random.h"
#include "td/utils/format.h"
#include "auto/tl/ton_api.hpp"
#include "dht-remote-node.hpp"
namespace ton {
namespace dht {
static const double PING_INTERVAL_DEFAULT = 60.0;
static const double PING_INTERVAL_MULTIPLIER = 1.1;
static const double PING_INTERVAL_MAX = 3600.0 * 4;
DhtRemoteNode::DhtRemoteNode(DhtNode node, td::uint32 max_missed_pings, td::int32 our_network_id)
    : node_(std::move(node))
    , max_missed_pings_(max_missed_pings)
    , our_network_id_(our_network_id)
    , ping_interval_(PING_INTERVAL_DEFAULT) {
  failed_from_ = td::Time::now_cached();
  id_ = node_.get_key();
}
td::Status DhtRemoteNode::receive_ping(DhtNode node, td::actor::ActorId adnl,
                                       adnl::AdnlNodeIdShort self_id) {
  TRY_STATUS(update_value(std::move(node), adnl, self_id));
  receive_ping();
  return td::Status::OK();
}
void DhtRemoteNode::receive_ping() {
  missed_pings_ = 0;
  ping_interval_ = PING_INTERVAL_DEFAULT;
  if (ready_from_ == 0) {
    ready_from_ = td::Time::now_cached();
  }
}
td::Status DhtRemoteNode::update_value(DhtNode node, td::actor::ActorId adnl,
                                       adnl::AdnlNodeIdShort self_id) {
  if (node.adnl_id() != node_.adnl_id()) {
    return td::Status::Error("Wrong adnl id");
  }
  if (node.version() <= node_.version()) {
    return td::Status::OK();
  }
  TRY_STATUS(node.check_signature());
  node_ = std::move(node);
  td::actor::send_closure(adnl, &adnl::Adnl::add_peer, self_id, node_.adnl_id(), node_.addr_list());
  return td::Status::OK();
}
void DhtRemoteNode::send_ping(bool client_only, td::actor::ActorId adnl, td::actor::ActorId node,
                              adnl::AdnlNodeIdShort src) {
  missed_pings_++;
  if (missed_pings_ > max_missed_pings_) {
    ping_interval_ = std::min(ping_interval_ * PING_INTERVAL_MULTIPLIER, PING_INTERVAL_MAX);
    if (ready_from_ > 0) {
      ready_from_ = 0;
      failed_from_ = td::Time::now_cached();
    }
  }
  last_ping_at_ = td::Time::now_cached();
  td::actor::send_closure(adnl, &adnl::Adnl::add_peer, src, node_.adnl_id(), node_.addr_list());
  auto P = td::PromiseCreator::lambda([key = id_, id = node_.adnl_id().compute_short_id(), client_only, node, src, adnl,
                                       our_network_id = our_network_id_](td::Result R) mutable {
    if (R.is_error()) {
      LOG(ERROR) << "[dht]: failed to get self node";
      return;
    }
    auto P = td::PromiseCreator::lambda([key, node, adnl, our_network_id](td::Result R) {
      if (R.is_error()) {
        VLOG(DHT_INFO) << "[dht]: received error for query to " << key << ": " << R.move_as_error();
        return;
      }
      auto F = fetch_tl_object(R.move_as_ok(), true);
      if (F.is_ok()) {
        auto N = DhtNode::create(F.move_as_ok(), our_network_id);
        if (N.is_ok()) {
          td::actor::send_closure(node, &DhtMember::receive_ping, key, N.move_as_ok());
        } else {
          VLOG(DHT_WARNING) << "[dht]: bad answer from " << key
                            << ": dropping bad getSignedAddressList() query answer: " << N.move_as_error();
        }
      } else {
        VLOG(DHT_WARNING) << "[dht]: bad answer from " << key
                          << ": dropping invalid getSignedAddressList() query answer: " << F.move_as_error();
      }
    });
    auto Q = create_serialize_tl_object();
    td::BufferSlice B;
    if (client_only) {
      B = std::move(Q);
    } else {
      B = create_serialize_tl_object_suffix(Q.as_slice(), R.move_as_ok().tl());
    }
    td::actor::send_closure(adnl, &adnl::Adnl::send_query, src, id, "dht ping", std::move(P),
                            td::Timestamp::in(10.0 + td::Random::fast(0, 100) * 0.1), std::move(B));
  });
  td::actor::send_closure(node, &DhtMember::get_self_node, std::move(P));
}
adnl::AdnlAddressList DhtRemoteNode::get_addr_list() const {
  return node_.addr_list();
}
adnl::AdnlNodeIdFull DhtRemoteNode::get_full_id() const {
  return node_.adnl_id();
}
td::Result> DhtRemoteNode::create(DhtNode node, td::uint32 max_missed_pings,
                                                                 td::int32 our_network_id) {
  TRY_STATUS(node.check_signature());
  return std::make_unique(std::move(node), max_missed_pings, our_network_id);
}
}  // namespace dht
}  // namespace ton