//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
//
// 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/telegram/ChannelType.h"
#include "td/telegram/DialogId.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
#include "td/telegram/UserId.h"
#include "td/telegram/Version.h"

#include "td/utils/common.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/tl_helpers.h"

namespace td {

class Td;

class AdministratorRights {
  static constexpr uint64 CAN_CHANGE_INFO_AND_SETTINGS = 1 << 0;
  static constexpr uint64 CAN_POST_MESSAGES = 1 << 1;
  static constexpr uint64 CAN_EDIT_MESSAGES = 1 << 2;
  static constexpr uint64 CAN_DELETE_MESSAGES = 1 << 3;
  static constexpr uint64 CAN_INVITE_USERS = 1 << 4;
  // static constexpr uint64 CAN_EXPORT_DIALOG_INVITE_LINK = 1 << 5;
  static constexpr uint64 CAN_RESTRICT_MEMBERS = 1 << 6;
  static constexpr uint64 CAN_PIN_MESSAGES = 1 << 7;
  static constexpr uint64 CAN_PROMOTE_MEMBERS = 1 << 8;
  static constexpr uint64 CAN_MANAGE_CALLS = 1 << 9;
  static constexpr uint64 CAN_MANAGE_DIALOG = 1 << 10;
  static constexpr uint64 CAN_MANAGE_TOPICS = 1 << 11;
  static constexpr uint64 IS_ANONYMOUS = 1 << 13;

  static constexpr uint64 ALL_ADMINISTRATOR_RIGHTS = CAN_CHANGE_INFO_AND_SETTINGS | CAN_POST_MESSAGES |
                                                     CAN_EDIT_MESSAGES | CAN_DELETE_MESSAGES | CAN_INVITE_USERS |
                                                     CAN_RESTRICT_MEMBERS | CAN_PIN_MESSAGES | CAN_MANAGE_TOPICS |
                                                     CAN_PROMOTE_MEMBERS | CAN_MANAGE_CALLS | CAN_MANAGE_DIALOG;

  uint64 flags_;

  friend class DialogParticipantStatus;

  explicit AdministratorRights(uint64 flags) : flags_(flags & (ALL_ADMINISTRATOR_RIGHTS | IS_ANONYMOUS)) {
  }

 public:
  AdministratorRights() : flags_(0) {
  }

  AdministratorRights(const tl_object_ptr<telegram_api::chatAdminRights> &admin_rights, ChannelType channel_type);

  AdministratorRights(const td_api::object_ptr<td_api::chatAdministratorRights> &administrator_rights,
                      ChannelType channel_type);

  AdministratorRights(bool is_anonymous, bool can_manage_dialog, bool can_change_info, bool can_post_messages,
                      bool can_edit_messages, bool can_delete_messages, bool can_invite_users,
                      bool can_restrict_members, bool can_pin_messages, bool can_manage_topics,
                      bool can_promote_members, bool can_manage_calls, ChannelType channel_type);

  telegram_api::object_ptr<telegram_api::chatAdminRights> get_chat_admin_rights() const;

  td_api::object_ptr<td_api::chatAdministratorRights> get_chat_administrator_rights_object() const;

  bool can_manage_dialog() const {
    return (flags_ & CAN_MANAGE_DIALOG) != 0;
  }

  bool can_change_info_and_settings() const {
    return (flags_ & CAN_CHANGE_INFO_AND_SETTINGS) != 0;
  }

  bool can_post_messages() const {
    return (flags_ & CAN_POST_MESSAGES) != 0;
  }

  bool can_edit_messages() const {
    return (flags_ & CAN_EDIT_MESSAGES) != 0;
  }

  bool can_delete_messages() const {
    return (flags_ & CAN_DELETE_MESSAGES) != 0;
  }

  bool can_invite_users() const {
    return (flags_ & CAN_INVITE_USERS) != 0;
  }

  bool can_restrict_members() const {
    return (flags_ & CAN_RESTRICT_MEMBERS) != 0;
  }

  bool can_pin_messages() const {
    return (flags_ & CAN_PIN_MESSAGES) != 0;
  }

  bool can_manage_topics() const {
    return (flags_ & CAN_MANAGE_TOPICS) != 0;
  }

  bool can_promote_members() const {
    return (flags_ & CAN_PROMOTE_MEMBERS) != 0;
  }

  bool can_manage_calls() const {
    return (flags_ & CAN_MANAGE_CALLS) != 0;
  }

  bool is_anonymous() const {
    return (flags_ & IS_ANONYMOUS) != 0;
  }

  template <class StorerT>
  void store(StorerT &storer) const {
    td::store(flags_, storer);
  }

  template <class ParserT>
  void parse(ParserT &parser) {
    if (parser.version() >= static_cast<int32>(Version::MakeParticipantFlags64Bit)) {
      td::parse(flags_, parser);
    } else {
      uint32 legacy_flags;
      td::parse(legacy_flags, parser);
      flags_ = legacy_flags;
    }
  }

  friend bool operator==(const AdministratorRights &lhs, const AdministratorRights &rhs);

  friend StringBuilder &operator<<(StringBuilder &string_builder, const AdministratorRights &status);
};

bool operator==(const AdministratorRights &lhs, const AdministratorRights &rhs);

bool operator!=(const AdministratorRights &lhs, const AdministratorRights &rhs);

StringBuilder &operator<<(StringBuilder &string_builder, const AdministratorRights &status);

class RestrictedRights {
  static constexpr uint64 CAN_SEND_MESSAGES = 1 << 16;
  static constexpr uint64 LEGACY_CAN_SEND_MEDIA = 1 << 17;
  static constexpr uint64 CAN_SEND_AUDIOS = static_cast<uint64>(1) << 32;
  static constexpr uint64 CAN_SEND_DOCUMENTS = static_cast<uint64>(1) << 33;
  static constexpr uint64 CAN_SEND_PHOTOS = static_cast<uint64>(1) << 34;
  static constexpr uint64 CAN_SEND_VIDEOS = static_cast<uint64>(1) << 35;
  static constexpr uint64 CAN_SEND_VIDEO_NOTES = static_cast<uint64>(1) << 36;
  static constexpr uint64 CAN_SEND_VOICE_NOTES = static_cast<uint64>(1) << 37;
  static constexpr uint64 CAN_SEND_STICKERS = 1 << 18;
  static constexpr uint64 CAN_SEND_ANIMATIONS = 1 << 19;
  static constexpr uint64 CAN_SEND_GAMES = 1 << 20;
  static constexpr uint64 CAN_USE_INLINE_BOTS = 1 << 21;
  static constexpr uint64 CAN_ADD_WEB_PAGE_PREVIEWS = 1 << 22;
  static constexpr uint64 CAN_SEND_POLLS = 1 << 23;
  static constexpr uint64 CAN_CHANGE_INFO_AND_SETTINGS = 1 << 24;
  static constexpr uint64 CAN_INVITE_USERS = 1 << 25;
  static constexpr uint64 CAN_PIN_MESSAGES = 1 << 26;
  static constexpr uint64 CAN_MANAGE_TOPICS = 1 << 12;

  static constexpr uint64 ALL_ADMIN_PERMISSION_RIGHTS =
      CAN_CHANGE_INFO_AND_SETTINGS | CAN_INVITE_USERS | CAN_PIN_MESSAGES | CAN_MANAGE_TOPICS;

  static constexpr uint64 ALL_RESTRICTED_RIGHTS =
      CAN_SEND_MESSAGES | CAN_SEND_STICKERS | CAN_SEND_ANIMATIONS | CAN_SEND_GAMES | CAN_USE_INLINE_BOTS |
      CAN_ADD_WEB_PAGE_PREVIEWS | CAN_SEND_POLLS | ALL_ADMIN_PERMISSION_RIGHTS | CAN_SEND_AUDIOS | CAN_SEND_DOCUMENTS |
      CAN_SEND_PHOTOS | CAN_SEND_VIDEOS | CAN_SEND_VIDEO_NOTES | CAN_SEND_VOICE_NOTES;

  uint64 flags_;

  friend class DialogParticipantStatus;

  explicit RestrictedRights(uint64 flags) : flags_(flags & ALL_RESTRICTED_RIGHTS) {
  }

 public:
  explicit RestrictedRights(const tl_object_ptr<telegram_api::chatBannedRights> &rights);

  explicit RestrictedRights(const td_api::object_ptr<td_api::chatPermissions> &rights);

  RestrictedRights(bool can_send_messages, bool can_send_audios, bool can_send_documents, bool can_send_photos,
                   bool can_send_videos, bool can_send_video_notes, bool can_send_voice_notes, bool can_send_stickers,
                   bool can_send_animations, bool can_send_games, bool can_use_inline_bots,
                   bool can_add_web_page_previews, bool can_send_polls, bool can_change_info_and_settings,
                   bool can_invite_users, bool can_pin_messages, bool can_manage_topics);

  td_api::object_ptr<td_api::chatPermissions> get_chat_permissions_object() const;

  tl_object_ptr<telegram_api::chatBannedRights> get_chat_banned_rights() const;

  bool can_change_info_and_settings() const {
    return (flags_ & CAN_CHANGE_INFO_AND_SETTINGS) != 0;
  }

  bool can_invite_users() const {
    return (flags_ & CAN_INVITE_USERS) != 0;
  }

  bool can_pin_messages() const {
    return (flags_ & CAN_PIN_MESSAGES) != 0;
  }

  bool can_manage_topics() const {
    return (flags_ & CAN_MANAGE_TOPICS) != 0;
  }

  bool can_send_messages() const {
    return (flags_ & CAN_SEND_MESSAGES) != 0;
  }

  bool can_send_audios() const {
    return (flags_ & CAN_SEND_AUDIOS) != 0;
  }

  bool can_send_documents() const {
    return (flags_ & CAN_SEND_DOCUMENTS) != 0;
  }

  bool can_send_photos() const {
    return (flags_ & CAN_SEND_PHOTOS) != 0;
  }

  bool can_send_videos() const {
    return (flags_ & CAN_SEND_VIDEOS) != 0;
  }

  bool can_send_video_notes() const {
    return (flags_ & CAN_SEND_VIDEO_NOTES) != 0;
  }

  bool can_send_voice_notes() const {
    return (flags_ & CAN_SEND_VOICE_NOTES) != 0;
  }

  bool can_send_stickers() const {
    return (flags_ & CAN_SEND_STICKERS) != 0;
  }

  bool can_send_animations() const {
    return (flags_ & CAN_SEND_ANIMATIONS) != 0;
  }

  bool can_send_games() const {
    return (flags_ & CAN_SEND_GAMES) != 0;
  }

  bool can_use_inline_bots() const {
    return (flags_ & CAN_USE_INLINE_BOTS) != 0;
  }

  bool can_add_web_page_previews() const {
    return (flags_ & CAN_ADD_WEB_PAGE_PREVIEWS) != 0;
  }

  bool can_send_polls() const {
    return (flags_ & CAN_SEND_POLLS) != 0;
  }

  template <class StorerT>
  void store(StorerT &storer) const {
    td::store(flags_, storer);
  }

  template <class ParserT>
  void parse(ParserT &parser) {
    if (parser.version() >= static_cast<int32>(Version::MakeParticipantFlags64Bit)) {
      td::parse(flags_, parser);
    } else {
      uint32 legacy_flags;
      td::parse(legacy_flags, parser);
      flags_ = legacy_flags;
    }
    if (flags_ & LEGACY_CAN_SEND_MEDIA) {
      flags_ |= CAN_SEND_AUDIOS | CAN_SEND_DOCUMENTS | CAN_SEND_PHOTOS | CAN_SEND_VIDEOS | CAN_SEND_VIDEO_NOTES |
                CAN_SEND_VOICE_NOTES;
    }
  }

  friend bool operator==(const RestrictedRights &lhs, const RestrictedRights &rhs);

  friend StringBuilder &operator<<(StringBuilder &string_builder, const RestrictedRights &status);
};

bool operator==(const RestrictedRights &lhs, const RestrictedRights &rhs);

bool operator!=(const RestrictedRights &lhs, const RestrictedRights &rhs);

StringBuilder &operator<<(StringBuilder &string_builder, const RestrictedRights &status);

class DialogParticipantStatus {
  static constexpr uint64 HAS_RANK = 1 << 14;
  static constexpr uint64 CAN_BE_EDITED = 1 << 15;

  static constexpr uint64 IS_MEMBER = 1 << 27;

  // bits 28-30 reserved for Type
  static constexpr int TYPE_SHIFT = 28;
  static constexpr int TYPE_SIZE = 3;
  static constexpr uint64 HAS_UNTIL_DATE = 1u << 31;

  enum class Type : int32 { Creator, Administrator, Member, Restricted, Left, Banned };
  // all fields are logically const, but should be updated in update_restrictions()
  mutable Type type_;
  mutable int32 until_date_;  // restricted and banned only
  mutable uint64 flags_;
  string rank_;  // creator and administrator only

  static int32 fix_until_date(int32 date);

  DialogParticipantStatus(Type type, uint64 flags, int32 until_date, string rank);

  AdministratorRights get_administrator_rights() const {
    return AdministratorRights(flags_);
  }

  RestrictedRights get_restricted_rights() const {
    return RestrictedRights(flags_);
  }

 public:
  static DialogParticipantStatus Creator(bool is_member, bool is_anonymous, string &&rank);

  static DialogParticipantStatus Administrator(AdministratorRights administrator_rights, string &&rank,
                                               bool can_be_edited);

  static DialogParticipantStatus Member();

  static DialogParticipantStatus Restricted(RestrictedRights restricted_rights, bool is_member,
                                            int32 restricted_until_date);

  static DialogParticipantStatus Left();

  static DialogParticipantStatus Banned(int32 banned_until_date);

  // legacy rights
  static DialogParticipantStatus GroupAdministrator(bool is_creator);

  // legacy rights
  static DialogParticipantStatus ChannelAdministrator(bool is_creator, bool is_megagroup);

  // forcely returns an administrator
  DialogParticipantStatus(bool can_be_edited, tl_object_ptr<telegram_api::chatAdminRights> &&admin_rights, string rank,
                          ChannelType channel_type);

  // forcely returns a restricted or banned
  DialogParticipantStatus(bool is_member, tl_object_ptr<telegram_api::chatBannedRights> &&banned_rights);

  bool has_all_administrator_rights(AdministratorRights administrator_rights) const {
    auto flags = administrator_rights.flags_ &
                 (AdministratorRights::ALL_ADMINISTRATOR_RIGHTS | AdministratorRights::IS_ANONYMOUS);
    return (get_administrator_rights().flags_ & flags) == flags;
  }

  RestrictedRights get_effective_restricted_rights() const;

  DialogParticipantStatus apply_restrictions(RestrictedRights default_restrictions, bool is_bot) const;

  tl_object_ptr<td_api::ChatMemberStatus> get_chat_member_status_object() const;

  tl_object_ptr<telegram_api::chatAdminRights> get_chat_admin_rights() const;

  tl_object_ptr<telegram_api::chatBannedRights> get_chat_banned_rights() const;

  // unrestricts user if restriction time expired. Should be called before all privileges checks
  void update_restrictions() const;

  bool can_manage_dialog() const {
    return get_administrator_rights().can_manage_dialog();
  }

  bool can_change_info_and_settings() const {
    return get_administrator_rights().can_change_info_and_settings() ||
           get_restricted_rights().can_change_info_and_settings();
  }

  bool can_post_messages() const {
    return get_administrator_rights().can_post_messages();
  }

  bool can_edit_messages() const {
    return get_administrator_rights().can_edit_messages();
  }

  bool can_delete_messages() const {
    return get_administrator_rights().can_delete_messages();
  }

  bool can_invite_users() const {
    return get_administrator_rights().can_invite_users() || get_restricted_rights().can_invite_users();
  }

  bool can_manage_invite_links() const {
    // invite links can be managed, only if administrator was explicitly granted the right
    return get_administrator_rights().can_invite_users();
  }

  bool can_restrict_members() const {
    return get_administrator_rights().can_restrict_members();
  }

  bool can_pin_messages() const {
    return get_administrator_rights().can_pin_messages() || get_restricted_rights().can_pin_messages();
  }

  bool can_edit_topics() const {
    // topics can be edited, only if administrator was explicitly granted the right
    return get_administrator_rights().can_manage_topics();
  }

  bool can_pin_topics() const {
    // topics can be pinned, only if administrator was explicitly granted the right
    return get_administrator_rights().can_manage_topics();
  }

  bool can_create_topics() const {
    return get_administrator_rights().can_manage_topics() || get_restricted_rights().can_manage_topics();
  }

  bool can_promote_members() const {
    return get_administrator_rights().can_promote_members();
  }

  bool can_manage_calls() const {
    return get_administrator_rights().can_manage_calls();
  }

  bool can_be_edited() const {
    return (flags_ & CAN_BE_EDITED) != 0;
  }

  void toggle_can_be_edited() {
    flags_ ^= CAN_BE_EDITED;
  }

  bool can_send_messages() const {
    return get_restricted_rights().can_send_messages();
  }

  bool can_send_audios() const {
    return get_restricted_rights().can_send_audios();
  }

  bool can_send_documents() const {
    return get_restricted_rights().can_send_documents();
  }

  bool can_send_photos() const {
    return get_restricted_rights().can_send_photos();
  }

  bool can_send_videos() const {
    return get_restricted_rights().can_send_videos();
  }

  bool can_send_video_notes() const {
    return get_restricted_rights().can_send_video_notes();
  }

  bool can_send_voice_notes() const {
    return get_restricted_rights().can_send_voice_notes();
  }

  bool can_send_stickers() const {
    return get_restricted_rights().can_send_stickers();
  }

  bool can_send_animations() const {
    return get_restricted_rights().can_send_animations();
  }

  bool can_send_games() const {
    return get_restricted_rights().can_send_games();
  }

  bool can_use_inline_bots() const {
    return get_restricted_rights().can_use_inline_bots();
  }

  bool can_add_web_page_previews() const {
    return get_restricted_rights().can_add_web_page_previews();
  }

  bool can_send_polls() const {
    return get_restricted_rights().can_send_polls();
  }

  void set_is_member(bool is_member) {
    if (is_member) {
      flags_ |= IS_MEMBER;
    } else {
      flags_ &= ~IS_MEMBER;
    }
  }

  bool is_member() const {
    return (flags_ & IS_MEMBER) != 0;
  }

  bool is_left() const {
    return (flags_ & IS_MEMBER) == 0;
  }

  bool is_creator() const {
    return type_ == Type::Creator;
  }

  bool is_administrator() const {
    return type_ == Type::Administrator || type_ == Type::Creator;
  }

  bool is_restricted() const {
    return type_ == Type::Restricted;
  }

  bool is_banned() const {
    return type_ == Type::Banned;
  }

  int32 get_until_date() const {
    return until_date_;
  }

  bool is_anonymous() const {
    return get_administrator_rights().is_anonymous();
  }

  const string &get_rank() const {
    return rank_;
  }

  template <class StorerT>
  void store(StorerT &storer) const {
    uint64 stored_flags = flags_ | (static_cast<uint64>(static_cast<int32>(type_)) << TYPE_SHIFT);
    if (until_date_ > 0) {
      stored_flags |= HAS_UNTIL_DATE;
    }
    if (!rank_.empty()) {
      stored_flags |= HAS_RANK;
    }
    td::store(stored_flags, storer);
    if (until_date_ > 0) {
      td::store(until_date_, storer);
    }
    if (!rank_.empty()) {
      td::store(rank_, storer);
    }
  }

  template <class ParserT>
  void parse(ParserT &parser) {
    uint64 stored_flags;
    if (parser.version() >= static_cast<int32>(Version::MakeParticipantFlags64Bit)) {
      td::parse(stored_flags, parser);
    } else {
      uint32 legacy_flags;
      td::parse(legacy_flags, parser);
      stored_flags = legacy_flags;
    }
    if ((stored_flags & HAS_UNTIL_DATE) != 0) {
      td::parse(until_date_, parser);
      stored_flags &= ~HAS_UNTIL_DATE;
    }
    if ((stored_flags & HAS_RANK) != 0) {
      td::parse(rank_, parser);
      stored_flags &= ~HAS_RANK;
    }
    type_ = static_cast<Type>(static_cast<int32>((stored_flags >> TYPE_SHIFT) & ((1 << TYPE_SIZE) - 1)));
    stored_flags -= static_cast<uint64>(static_cast<int32>(type_)) << TYPE_SHIFT;

    flags_ = stored_flags;

    if (flags_ & RestrictedRights::LEGACY_CAN_SEND_MEDIA) {
      flags_ |= RestrictedRights::CAN_SEND_AUDIOS | RestrictedRights::CAN_SEND_DOCUMENTS |
                RestrictedRights::CAN_SEND_PHOTOS | RestrictedRights::CAN_SEND_VIDEOS |
                RestrictedRights::CAN_SEND_VIDEO_NOTES | RestrictedRights::CAN_SEND_VOICE_NOTES;
    }

    if (is_creator()) {
      flags_ |= AdministratorRights::ALL_ADMINISTRATOR_RIGHTS | RestrictedRights::ALL_RESTRICTED_RIGHTS;
    } else if (is_administrator()) {
      flags_ |= AdministratorRights::CAN_MANAGE_DIALOG;
    }
  }

  friend bool operator==(const DialogParticipantStatus &lhs, const DialogParticipantStatus &rhs);

  friend StringBuilder &operator<<(StringBuilder &string_builder, const DialogParticipantStatus &status);
};

bool operator==(const DialogParticipantStatus &lhs, const DialogParticipantStatus &rhs);

bool operator!=(const DialogParticipantStatus &lhs, const DialogParticipantStatus &rhs);

StringBuilder &operator<<(StringBuilder &string_builder, const DialogParticipantStatus &status);

struct DialogParticipant {
  DialogId dialog_id_;
  UserId inviter_user_id_;
  int32 joined_date_ = 0;
  DialogParticipantStatus status_ = DialogParticipantStatus::Left();

  DialogParticipant() = default;

  DialogParticipant(DialogId dialog_id, UserId inviter_user_id, int32 joined_date, DialogParticipantStatus status);

  DialogParticipant(tl_object_ptr<telegram_api::ChatParticipant> &&participant_ptr, int32 chat_creation_date,
                    bool is_creator);

  DialogParticipant(tl_object_ptr<telegram_api::ChannelParticipant> &&participant_ptr, ChannelType channel_type);

  static DialogParticipant left(DialogId dialog_id) {
    return {dialog_id, UserId(), 0, DialogParticipantStatus::Left()};
  }

  static DialogParticipant private_member(UserId user_id, UserId other_user_id) {
    auto inviter_user_id = other_user_id.is_valid() ? other_user_id : user_id;
    return {DialogId(user_id), inviter_user_id, 0, DialogParticipantStatus::Member()};
  }

  bool is_valid() const;

  template <class StorerT>
  void store(StorerT &storer) const {
    td::store(dialog_id_, storer);
    td::store(inviter_user_id_, storer);
    td::store(joined_date_, storer);
    td::store(status_, storer);
  }

  template <class ParserT>
  void parse(ParserT &parser) {
    if (parser.version() >= static_cast<int32>(Version::SupportBannedChannels)) {
      td::parse(dialog_id_, parser);
    } else {
      UserId user_id;
      td::parse(user_id, parser);
      dialog_id_ = DialogId(user_id);
    }
    td::parse(inviter_user_id_, parser);
    td::parse(joined_date_, parser);
    td::parse(status_, parser);
  }
};

StringBuilder &operator<<(StringBuilder &string_builder, const DialogParticipant &dialog_participant);

struct DialogParticipants {
  int32 total_count_ = 0;
  vector<DialogParticipant> participants_;

  DialogParticipants() = default;
  DialogParticipants(int32 total_count, vector<DialogParticipant> &&participants)
      : total_count_(total_count), participants_(std::move(participants)) {
  }

  td_api::object_ptr<td_api::chatMembers> get_chat_members_object(Td *td) const;
};

DialogParticipantStatus get_dialog_participant_status(const td_api::object_ptr<td_api::ChatMemberStatus> &status,
                                                      ChannelType channel_type);

}  // namespace td