#include "../common/global_define.h"
#include "cliententry.h"
#include "clientlist.h"
#include "login_server.h"
#include "login_server_list.h"
#include "shared_task_manager.h"
#include "worlddb.h"
#include "zoneserver.h"
#include "world_config.h"

extern uint32            numplayers;
extern volatile bool     RunLoops;

ClientListEntry::ClientListEntry(
	uint32 id,
	uint32 login_server_id,
	const char *login_server_name,
	const char *account_name,
	const char *login_key,
	int16 is_world_admin,
	uint32 ip_address,
	uint8 local
)
	: m_id(id)
{
	ClearVars(true);

	LogDebug(
		"id [{}] loginserver_id [{}] loginserver_name [{}] login_name [{}] login_key [{}] is_world_admin [{}] ip [{}] local [{}]",
		id,
		login_server_id,
		login_server_name,
		account_name,
		login_key,
		is_world_admin,
		ip_address,
		local
	);

	m_ip_address      = ip_address;
	m_login_server_id = login_server_id;
	if (login_server_id > 0) {
		m_account_id = database.GetAccountIDFromLSID(
			login_server_name,
			login_server_id,
			m_account_name,
			&m_admin
		);
	}

	strn0cpy(m_login_account_name, account_name, sizeof(m_login_account_name));
	strn0cpy(m_key, login_key, sizeof(m_key));
	strn0cpy(m_source_loginserver, login_server_name, sizeof(m_source_loginserver));

	m_world_admin = is_world_admin;
	m_is_local    = (local == 1);

	memset(m_lfg_comments, 0, 64);
}

ClientListEntry::ClientListEntry(uint32 in_id, ZoneServer *z, ServerClientList_Struct *scl, CLE_Status online)
	: m_id(in_id)
{
	ClearVars(true);

	m_ip_address      = 0;
	m_login_server_id = scl->LSAccountID;
	strn0cpy(m_login_account_name, scl->name, sizeof(m_login_account_name));
	strn0cpy(m_key, scl->lskey, sizeof(m_key));
	m_world_admin = 0;

	m_account_id = scl->AccountID;
	strn0cpy(m_account_name, scl->AccountName, sizeof(m_account_name));
	m_admin = scl->Admin;

	m_instance         = 0;
	m_lfg_from_level   = 0;
	m_lfg_to_level     = 0;
	m_lfg_match_filter = false;
	memset(m_lfg_comments, 0, 64);

	if (online >= CLE_Status::Zoning) {
		Update(z, scl, online);
	}
	else {
		SetOnline(online);
	}
}

ClientListEntry::~ClientListEntry()
{
	if (RunLoops) {
		Camp(); // updates zoneserver's numplayers
		ClientList::Instance()->RemoveCLEReferances(this);
	}
	for (auto &elem: m_tell_queue) {
		safe_delete_array(elem);
	}
	m_tell_queue.clear();
}

void ClientListEntry::SetChar(uint32 iCharID, const char *iCharName)
{
	m_char_id = iCharID;
	strn0cpy(m_char_name, iCharName, sizeof(m_char_name));
}

void ClientListEntry::SetOnline(CLE_Status iOnline)
{
	LogClientLogin(
		"Online status [{}] ({}) status [{}] ({})",
		AccountName(),
		AccountID(),
		CLEStatusString[iOnline],
		static_cast<int>(iOnline)
	);

	if (iOnline >= CLE_Status::Online && m_online < CLE_Status::Online) {
		numplayers++;
	}
	else if (iOnline < CLE_Status::Online && m_online >= CLE_Status::Online) {
		numplayers--;
	}
	if (iOnline != CLE_Status::Online || m_online < CLE_Status::Online) {
		m_online = iOnline;
	}
	if (iOnline < CLE_Status::Zoning) {
		Camp();
	}
	if (m_online >= CLE_Status::Online) {
		m_stale = 0;
	}
}

void ClientListEntry::LSUpdate(ZoneServer *iZS)
{
	if (WorldConfig::get()->UpdateStats) {
		auto pack = new ServerPacket;
		pack->opcode  = ServerOP_LSZoneInfo;
		pack->size    = sizeof(LoginserverZoneInfoUpdate);
		pack->pBuffer = new uchar[pack->size];
		auto *zone = (LoginserverZoneInfoUpdate *) pack->pBuffer;
		zone->count    = iZS->NumPlayers();
		zone->zone     = iZS->GetZoneID();
		zone->zone_wid = iZS->GetID();
		LoginServerList::Instance()->SendPacket(pack);
		safe_delete(pack);
	}
}

void ClientListEntry::LSZoneChange(ZoneToZone_Struct *ztz)
{
	if (WorldConfig::get()->UpdateStats) {
		auto pack = new ServerPacket;
		pack->opcode  = ServerOP_LSPlayerZoneChange;
		pack->size    = sizeof(ServerLSPlayerZoneChange_Struct);
		pack->pBuffer = new uchar[pack->size];
		auto *zonechange = (ServerLSPlayerZoneChange_Struct *) pack->pBuffer;
		zonechange->lsaccount_id = LSID();
		zonechange->from         = ztz->current_zone_id;
		zonechange->to           = ztz->requested_zone_id;
		LoginServerList::Instance()->SendPacket(pack);
		safe_delete(pack);
	}
}

void ClientListEntry::Update(ZoneServer *iZS, ServerClientList_Struct *scl, CLE_Status iOnline)
{
	if (m_zone_server != iZS) {
		if (m_zone_server) {
			m_zone_server->RemovePlayer();
			LSUpdate(m_zone_server);
		}
		if (iZS) {
			iZS->AddPlayer();
			LSUpdate(iZS);
		}
	}
	m_zone_server          = iZS;
	m_zone                 = scl->zone;
	m_instance             = scl->instance_id;
	m_char_id              = scl->charid;

	strcpy(m_char_name, scl->name);
	if (m_account_id == 0) {
		m_account_id = scl->AccountID;
		strcpy(m_account_name, scl->AccountName);
		strcpy(m_login_account_name, scl->AccountName);
		m_ip_address      = scl->IP;
		m_login_server_id = scl->LSAccountID;
		strn0cpy(m_key, scl->lskey, sizeof(m_key));
	}
	m_admin                = scl->Admin;
	m_level                = scl->level;
	m_class_               = scl->class_;
	m_race                 = scl->race;
	m_anon                 = scl->anon;
	m_tells_off            = scl->tellsoff;
	m_guild_id             = scl->guild_id;
	m_guild_rank           = scl->guild_rank;
	m_guild_tribute_opt_in = scl->guild_tribute_opt_in;
	m_lfg                  = scl->LFG;
	m_gm                   = scl->gm;
	m_client_version       = scl->ClientVersion;

	// Fields from the LFG Window
	if ((scl->LFGFromLevel != 0) && (scl->LFGToLevel != 0)) {
		m_lfg_from_level   = scl->LFGFromLevel;
		m_lfg_to_level     = scl->LFGToLevel;
		m_lfg_match_filter = scl->LFGMatchFilter;
		memcpy(m_lfg_comments, scl->LFGComments, sizeof(m_lfg_comments));
	}

	SetOnline(iOnline);
}

void ClientListEntry::LeavingZone(ZoneServer *iZS, CLE_Status iOnline)
{
	if (iZS != 0 && iZS != m_zone_server) {
		return;
	}
	SetOnline(iOnline);

	SharedTaskManager::Instance()->RemoveActiveInvitationByCharacterID(CharID());

	if (m_zone_server) {
		m_zone_server->RemovePlayer();
		LSUpdate(m_zone_server);
	}
	m_zone_server = 0;
	m_zone        = 0;
}

void ClientListEntry::ClearVars(bool iAll)
{
	if (iAll) {
		m_online = CLE_Status::Never;
		m_stale  = 0;

		m_login_server_id = 0;
		memset(m_login_account_name, 0, sizeof(m_login_account_name));
		memset(m_key, 0, sizeof(m_key));
		m_world_admin = 0;

		m_account_id = 0;
		memset(m_account_name, 0, sizeof(m_account_name));
		m_admin = AccountStatus::Player;
	}
	m_zone_server = 0;
	m_zone        = 0;
	m_char_id     = 0;
	memset(m_char_name, 0, sizeof(m_char_name));
	m_level          = 0;
	m_class_         = 0;
	m_race           = 0;
	m_anon           = 0;
	m_tells_off      = 0;
	m_guild_id       = GUILD_NONE;
	m_guild_rank     = 0;
	m_lfg            = 0;
	m_gm             = 0;
	m_client_version = 0;
	for (auto &elem: m_tell_queue) {
		safe_delete_array(elem);
	}
	m_tell_queue.clear();
}

void ClientListEntry::Camp(ZoneServer *iZS)
{
	if (iZS != 0 && iZS != m_zone_server) {
		return;
	}
	if (m_zone_server) {
		m_zone_server->RemovePlayer();
		LSUpdate(m_zone_server);
	}

	ClearVars();

	m_stale = 0;
}

bool ClientListEntry::CheckStale()
{
	m_stale++;
	if (m_stale > 20) {
		if (m_online > CLE_Status::Offline) {
			SetOnline(CLE_Status::Offline);
		}

		return true;
	}
	return false;
}

bool ClientListEntry::CheckAuth(uint32 loginserver_account_id, const char *key_password)
{
	LogDebug(
		"ls_account_id [{}] key_password [{}] key [{}]",
		loginserver_account_id,
		key_password,
		m_key
	);

	if (m_login_server_id == loginserver_account_id && strncmp(m_key, key_password, 10) == 0) {
		LogDebug(
			"ls_account_id [{}] key_password [{}] m_key [{}] lsid [{}] m_account_id [{}]",
			loginserver_account_id,
			key_password,
			m_key,
			LSID(),
			m_account_id
		);

		// create account if it doesn't exist
		if (m_account_id == 0 && LSID() > 0) {
			int16 default_account_status = WorldConfig::get()->DefaultStatus;

			m_account_id = database.CreateAccount(
				m_login_account_name,
				std::string(),
				default_account_status,
				m_source_loginserver,
				LSID()
			);

			if (!m_account_id) {
				LogError(
					"Error adding local account for LS login [{}] [{}], duplicate name",
					m_source_loginserver,
					m_login_account_name
				);
				return false;
			}
			strn0cpy(m_account_name, m_login_account_name, sizeof(m_account_name));
			m_admin = default_account_status;
		}
		std::string lsworldadmin;
		if (database.GetVariable("honorlsworldadmin", lsworldadmin)) {
			if (Strings::ToInt(lsworldadmin) == 1 && m_world_admin != 0 &&
				(m_admin < m_world_admin || m_admin == AccountStatus::Player)) {
				m_admin = m_world_admin;
			}
		}
		return true;
	}
	return false;
}

void ClientListEntry::ProcessTellQueue()
{
	if (!Server()) {
		return;
	}

	ServerPacket *pack;
	auto         it = m_tell_queue.begin();
	while (it != m_tell_queue.end()) {
		pack = new ServerPacket(
			ServerOP_ChannelMessage,
			sizeof(ServerChannelMessage_Struct) + strlen((*it)->message) + 1
		);
		memcpy(pack->pBuffer, *it, pack->size);
		Server()->SendPacket(pack);
		safe_delete(pack);
		safe_delete_array(*it);
		it = m_tell_queue.erase(it);
	}
}

