//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once

#include "td/actor/actor.h"
#include "td/actor/PromiseFuture.h"

#include "td/telegram/net/NetType.h"

#include "td/utils/common.h"

namespace td {

class StateManager final : public Actor {
 public:
  enum class State : int32 { WaitingForNetwork, ConnectingToProxy, Connecting, Updating, Ready, Empty };

  class Callback {
   public:
    Callback() = default;
    Callback(const Callback &) = delete;
    Callback &operator=(const Callback &) = delete;
    virtual ~Callback() = default;
    virtual bool on_state(State state) {
      return true;
    }
    virtual bool on_network(NetType network_type, uint32 generation) {
      return true;
    }
    virtual bool on_online(bool is_online) {
      return true;
    }
  };

  void on_synchronized(bool is_synchronized);

  void on_network_updated();

  void on_network(NetType new_network_type);

  void on_online(bool is_online);

  void on_proxy(bool use_proxy);

  void add_callback(unique_ptr<Callback> net_callback);

  void wait_first_sync(Promise<> promise);

  void close();

  class ConnectionToken {
   public:
    ConnectionToken() = default;
    explicit ConnectionToken(ActorShared<StateManager> state_manager) : state_manager_(std::move(state_manager)) {
    }
    ConnectionToken(const ConnectionToken &) = delete;
    ConnectionToken &operator=(const ConnectionToken &) = delete;
    ConnectionToken(ConnectionToken &&) = default;
    ConnectionToken &operator=(ConnectionToken &&other) {
      reset();
      state_manager_ = std::move(other.state_manager_);
      return *this;
    }
    ~ConnectionToken() {
      reset();
    }

    void reset() {
      if (!state_manager_.empty()) {
        send_closure(state_manager_, &StateManager::dec_connect);
        state_manager_.reset();
      }
    }

    bool empty() const {
      return state_manager_.empty();
    }

   private:
    ActorShared<StateManager> state_manager_;
  };

  static ConnectionToken connection(ActorId<StateManager> state_manager) {
    return connection_impl(state_manager, 1);
  }
  static ConnectionToken connection_proxy(ActorId<StateManager> state_manager) {
    return connection_impl(state_manager, 2);
  }

 private:
  uint32 connect_cnt_ = 0;
  uint32 connect_proxy_cnt_ = 0;
  bool sync_flag_ = true;
  bool network_flag_ = true;
  NetType network_type_ = NetType::Unknown;
  uint32 network_generation_ = 1;
  bool online_flag_ = false;
  bool use_proxy_ = false;

  static constexpr double UP_DELAY = 0.05;
  static constexpr double DOWN_DELAY = 0.3;

  State pending_state_ = State::Empty;
  bool has_timestamp_ = false;
  double pending_timestamp_ = 0;
  State flush_state_ = State::Empty;

  vector<unique_ptr<Callback>> callbacks_;

  bool was_sync_ = false;
  std::vector<Promise<>> wait_first_sync_;

  void inc_connect();
  void dec_connect();

  enum class Flag : int32 { Online, State, Network };
  void notify_flag(Flag flag);

  void start_up() override;
  void loop() override;

  void on_network_soft();
  void do_on_network(NetType new_network_type, bool inc_generation);

  State get_real_state() const;

  static ConnectionToken connection_impl(ActorId<StateManager> state_manager, int mode) {
    auto actor = ActorShared<StateManager>(state_manager, mode);
    send_closure(actor, &StateManager::inc_connect);
    return ConnectionToken(std::move(actor));
  }
};

}  // namespace td