#pragma once

#include <stdint.h>

#include <asio.hpp>
#include <deque>
#include <memory>
#include <phosg/Strings.hh>
#include <string>
#include <variant>

#include "../PlayerSubordinates.hh"

struct Lobby;

namespace Episode3 {

// The comment in Server.hh does not apply to this file (and BattleRecord.cc).

class BattleRecord {
public:
  struct PlayerEntry {
    PlayerLobbyDataDCGC lobby_data;
    PlayerInventory inventory;
    PlayerDispDataDCPCV3 disp;
    le_uint32_t level;

    void print(FILE* stream) const;
  } __packed_ws__(PlayerEntry, 0x440);

  struct Event {
    enum class Type : uint8_t {
      PLAYER_JOIN = 0,
      PLAYER_LEAVE = 1,
      SET_INITIAL_PLAYERS = 2,
      BATTLE_COMMAND = 3,
      GAME_COMMAND = 4,
      EP3_GAME_COMMAND = 5,
      CHAT_MESSAGE = 6,
      SERVER_DATA_COMMAND = 7,
    };

    // Fields used for all events
    Type type;
    uint64_t timestamp;
    // Fields used for PLAYER_JOIN and SET_INITIAL_PLAYERS only
    std::vector<PlayerEntry> players;
    // Fields used for PLAYER_LEAVE only
    uint8_t leaving_client_id;
    // Fields used for CHAT_MESSAGE only
    uint32_t guild_card_number;
    // Fields used for the COMMAND types and CHAT_MESSAGE
    std::string data;

    Event() = default;
    explicit Event(phosg::StringReader& r);
    void serialize(phosg::StringWriter& w) const;
    void print(FILE* stream) const;
  };

  explicit BattleRecord(uint32_t behavior_flags);
  explicit BattleRecord(const std::string& data);
  std::string serialize() const;

  inline bool writable() const {
    return this->is_writable;
  }

  inline uint32_t get_behavior_flags() const {
    return this->behavior_flags;
  }

  inline bool battle_in_progress() const {
    return (this->battle_start_timestamp != 0);
  }

  inline const Event* get_first_event() const {
    return this->events.empty() ? nullptr : &this->events.front();
  }

  inline std::deque<Event> get_all_events() const {
    return this->events;
  }

  void add_player(
      const PlayerLobbyDataDCGC& lobby_data,
      const PlayerInventory& inventory,
      const PlayerDispDataDCPCV3& disp,
      uint32_t level);
  void delete_player(uint8_t client_id);
  void add_command(Event::Type type, const void* data, size_t size);
  void add_command(Event::Type type, std::string&& data);
  void add_chat_message(uint32_t guild_card_number, std::string&& data);
  void add_random_data(const void* data, size_t size);
  // This function collapses all the existing player join/leave events into a
  // single SET_INITIAL_PLAYERS event, and deletes all events before the latest
  // BATTLE_COMMAND command that specifies the battle map. This should provide a
  // minimal set of commands to set up and start the battle during a replay.
  void set_battle_start_timestamp();
  void set_battle_end_timestamp();

  void print(FILE* stream) const;

  const std::string& get_random_stream() const;

private:
  static constexpr uint64_t SIGNATURE_V1 = 0x14C946D56D1DAC50;
  static constexpr uint64_t SIGNATURE_V2 = 0xD01E5EC12853C377;

  static bool is_map_definition_event(const Event& ev);

  bool is_writable;

  uint32_t behavior_flags;
  uint64_t battle_start_timestamp;
  uint64_t battle_end_timestamp;
  std::deque<Event> events;
  std::string random_stream;

  friend class BattleRecordPlayer;
};

class BattleRecordPlayer {
public:
  BattleRecordPlayer(std::shared_ptr<asio::io_context> io_context, std::shared_ptr<const BattleRecord> rec);
  ~BattleRecordPlayer() = default;

  std::shared_ptr<const BattleRecord> get_record() const;

  void set_lobby(std::shared_ptr<Lobby> l);
  void start();

private:
  std::shared_ptr<asio::io_context> io_context;
  std::shared_ptr<const BattleRecord> record;
  std::deque<BattleRecord::Event>::const_iterator event_it;
  uint64_t play_start_timestamp;
  std::weak_ptr<Lobby> lobby;
  asio::steady_timer next_command_timer;

  asio::awaitable<void> play_task();
};

} // namespace Episode3
