#pragma once
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <string>
#include <memory>
#include <vector>
#include <mutex>
#include <queue>
#include <unordered_set>
#include <Eigen/Dense>
#include "../src/network/ClientState.h"
#define _USE_MATH_DEFINES
#include <math.h>

namespace beast = boost::beast;
namespace http = beast::http;
namespace websocket = beast::websocket;
namespace net = boost::asio;
using tcp = net::ip::tcp;
static constexpr float kWorldZOffset = 45000.0f;
static const Eigen::Vector3f kWorldOffset(0.0f, 0.0f, kWorldZOffset);

static constexpr float kShipRadius = 15.0f;
static constexpr float kSpawnShipMargin = 25.0f;
static constexpr float kSpawnBoxMargin = 15.0f;
static constexpr float kSpawnZJitter = 60.0f;

std::vector<std::string> split(const std::string& s, char delimiter);

struct DeathInfo {
	int targetId = -1;
	uint64_t serverTime = 0;
	Eigen::Vector3f position = Eigen::Vector3f::Zero();
	int killerId = -1;
};


struct ServerBox {
	Eigen::Vector3f position;
	Eigen::Matrix3f rotation;
	float collisionRadius = 2.0f;
	bool destroyed = false;
};

struct Projectile {
	int shooterId = -1;
	uint64_t spawnMs = 0;
	Eigen::Vector3f pos;
	Eigen::Vector3f vel;
	float lifeMs = PROJECTILE_LIFE;
};

struct BoxDestroyedInfo {
	int boxIndex = -1;
	uint64_t serverTime = 0;
	Eigen::Vector3f position = Eigen::Vector3f::Zero();
	int destroyedBy = -1;
};

class Server;

class Session : public std::enable_shared_from_this<Session> {
	Server& server_;
	websocket::stream<beast::tcp_stream> ws_;
	beast::flat_buffer buffer_;
	int id_;
	bool is_writing_ = false;
	std::queue<std::shared_ptr<std::string>> writeQueue_;
	std::mutex writeMutex_;


public:
	ClientStateInterval timedClientStates;
	bool joined_ = false;
	std::chrono::system_clock::time_point lastReceivedTime_;

	bool hasReservedSpawn_ = false;
	Eigen::Vector3f reservedSpawn_ = Eigen::Vector3f(0.0f, 0.0f, kWorldZOffset);

	std::string nickname = "Player";
	int shipType = 0;

	Session(Server& server, tcp::socket&& socket, int id);
	int get_id() const;
	bool hasSpawnReserved() const;
	const Eigen::Vector3f& reservedSpawn() const;
	bool fetchStateAtTime(std::chrono::system_clock::time_point targetTime, ClientState& outState) const;
	void send_message(const std::string& msg);
	void run();
	bool IsMessageValid(const std::string& fullMessage);
	bool is_timed_out(std::chrono::system_clock::time_point now) const;
	void force_disconnect();

private:
	void sendBoxesToClient();
public:
	void init();
	ClientState get_latest_state(std::chrono::system_clock::time_point now);
	void doWrite();
private:

	void do_read();
	void process_message(const std::string& msg);
};

class Server
{
public:
	tcp::acceptor& acceptor_;
	net::io_context& ioc_;
	net::steady_timer timer;
	std::vector<BoxDestroyedInfo> g_boxDestructions;
	std::mutex g_boxDestructions_mutex;

	std::vector<ServerBox> g_serverBoxes;
	std::mutex g_boxes_mutex;

	std::vector<std::shared_ptr<Session>> g_sessions;
	std::mutex g_sessions_mutex;

	std::vector<Projectile> g_projectiles;
	std::mutex g_projectiles_mutex;

	std::unordered_set<int> g_dead_players;
	std::mutex g_dead_mutex;

	int next_id = 1000;

	std::vector<ServerBox> generateServerBoxes(int count);
public:
	Server(tcp::acceptor& acceptor, net::io_context& ioc);

	void broadcastToAll(const std::string& message);
	void broadcastToAllExceptId(const std::string& message, int id);
	void createProjectile(int id, Eigen::Vector3f pos, Eigen::Quaternionf dir, float velocity);
	void update_world();
	Eigen::Vector3f PickSafeSpawnPos(int forPlayerId);
	// Caller must hold g_boxes_mutex
	Eigen::Vector3f PickSafeBoxPos(int skipIdx);
	void init();
	void accept();
};
