/*
    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 .
*/
#include "SpeedLimiter.h"
#include "common/errorcode.h"
namespace ton {
SpeedLimiter::SpeedLimiter(double max_speed) : max_speed_(max_speed) {
}
void SpeedLimiter::set_max_speed(double max_speed) {
  max_speed_ = max_speed;
  auto old_queue = std::move(queue_);
  unlock_at_ = (queue_.empty() ? td::Timestamp::now() : queue_.front().execute_at_);
  queue_ = {};
  while (!old_queue.empty()) {
    auto &e = old_queue.front();
    enqueue(e.size_, e.timeout_, std::move(e.promise_));
    old_queue.pop();
  }
  process_queue();
}
void SpeedLimiter::enqueue(double size, td::Timestamp timeout, td::Promise promise) {
  if (max_speed_ < 0.0) {
    promise.set_result(td::Unit());
    return;
  }
  if (max_speed_ == 0.0) {
    promise.set_error(td::Status::Error(ErrorCode::timeout, "Speed limit is 0"));
    return;
  }
  if (timeout < unlock_at_) {
    promise.set_error(td::Status::Error(ErrorCode::timeout, "Timeout caused by speed limit"));
    return;
  }
  if (queue_.empty() && unlock_at_.is_in_past()) {
    unlock_at_ = td::Timestamp::now();
    promise.set_result(td::Unit());
  } else {
    queue_.push({unlock_at_, size, timeout, std::move(promise)});
  }
  unlock_at_ = td::Timestamp::in(size / max_speed_, unlock_at_);
  if (!queue_.empty()) {
    alarm_timestamp() = queue_.front().execute_at_;
  }
}
void SpeedLimiter::alarm() {
  process_queue();
}
void SpeedLimiter::process_queue() {
  while (!queue_.empty()) {
    auto &e = queue_.front();
    if (e.execute_at_.is_in_past()) {
      e.promise_.set_result(td::Unit());
      queue_.pop();
    } else {
      break;
    }
  }
  if (!queue_.empty()) {
    alarm_timestamp() = queue_.front().execute_at_;
  }
}
}  // namespace ton