// -----------------------------------------------------------------------------
// entitylist.cpp
// -----------------------------------------------------------------------------
// Implementation of the entity list traversal described in entitylist.h.
// Controller slots (< 64) resolve to pawns via CCSPlayerController's
// m_hPlayerPawn handle; pawn entity indices (>= 64, e.g. crosshair ids) are
// read directly. Class name resolution walks three chained pointers
// (identity -> classInfo -> binding) before dereferencing the name string.
// -----------------------------------------------------------------------------

#include "entitylist.h"

#include <cmath>

namespace sdk {

void EntityList::Initialize(const core::ProcessMemory* memory, const core::GameOffsets* offsets, uintptr_t clientBase)
{
	m_memory = memory;
	m_offsets = offsets;
	m_clientBase = clientBase;
}

uintptr_t EntityList::GetPawn(int slot) const
{
	if (slot < 0 || slot >= kMaxRawEntities || m_clientBase == 0) {
		return 0;
	}

	if (slot < kMaxPlayers) {
		const uintptr_t controller = GetRawEntity(slot);
		if (controller == 0) {
			return 0;
		}

		const uint32_t pawnHandle = m_memory->Read<uint32_t>(controller + m_offsets->hPlayerPawn) & 0x7FFF;
		if (pawnHandle == 0) {
			return 0;
		}
		return GetRawEntity(static_cast<int>(pawnHandle));
	}

	const std::string className = GetEntityClassName(slot);
	if (className != "C_CSPlayerPawn" && className != "CCSPlayerPawn") {
		return 0;
	}
	return GetRawEntity(slot);
}

uintptr_t EntityList::GetRawEntity(int entityIndex) const
{
	if (entityIndex < 0 || entityIndex >= kMaxRawEntities || m_clientBase == 0) {
		return 0;
	}

	const uintptr_t outer = m_memory->Read<uintptr_t>(m_clientBase + m_offsets->entityList);
	if (outer == 0) {
		return 0;
	}

	const uintptr_t inner = m_memory->Read<uintptr_t>(outer + static_cast<uintptr_t>(entityIndex / 512) * 0x8);
	if (inner == 0) {
		return 0;
	}

	const uintptr_t identity = inner + static_cast<uintptr_t>(entityIndex % 512) * 0x70;
	return m_memory->Read<uintptr_t>(identity);
}

uintptr_t EntityList::GetIdentityAddress(int entityIndex) const
{
	if (entityIndex < 0 || entityIndex >= kMaxRawEntities || m_clientBase == 0) {
		return 0;
	}

	const uintptr_t outer = m_memory->Read<uintptr_t>(m_clientBase + m_offsets->entityList);
	if (outer == 0) {
		return 0;
	}

	const uintptr_t inner = m_memory->Read<uintptr_t>(outer + static_cast<uintptr_t>(entityIndex / 512) * 0x8);
	if (inner == 0) {
		return 0;
	}

	return inner + static_cast<uintptr_t>(entityIndex % 512) * 0x70;
}

bool EntityList::GetPawnWithIdentity(int slot, uintptr_t& outPawn, uintptr_t& outIdentity) const
{
	if (slot < 0 || slot >= kMaxPlayers || m_clientBase == 0) {
		return false;
	}

	const uintptr_t controller = GetRawEntity(slot);
	if (controller == 0) {
		return false;
	}

	const uint32_t pawnHandle = m_memory->Read<uint32_t>(controller + m_offsets->hPlayerPawn) & 0x7FFF;
	if (pawnHandle == 0) {
		return false;
	}

	outPawn = GetRawEntity(static_cast<int>(pawnHandle));
	if (outPawn == 0) {
		return false;
	}

	outIdentity = GetIdentityAddress(static_cast<int>(pawnHandle));
	return outIdentity != 0;
}

std::string EntityList::GetEntityClassName(int entityIndex) const
{
	if (entityIndex < 0 || entityIndex >= kMaxRawEntities || m_clientBase == 0) {
		return "";
	}

	const uintptr_t outer = m_memory->Read<uintptr_t>(m_clientBase + m_offsets->entityList);
	if (outer == 0) {
		return "";
	}

	const uintptr_t inner = m_memory->Read<uintptr_t>(outer + static_cast<uintptr_t>(entityIndex / 512) * 0x8);
	if (inner == 0) {
		return "";
	}

	const uintptr_t identity = inner + static_cast<uintptr_t>(entityIndex % 512) * 0x70;
	const uintptr_t classInfo = m_memory->Read<uintptr_t>(identity + 0x8);
	if (classInfo == 0) {
		return "";
	}

	const uintptr_t binding = m_memory->Read<uintptr_t>(classInfo + 0x8);
	if (binding == 0) {
		return "";
	}

	return m_memory->ReadString(m_memory->Read<uintptr_t>(binding + 0x8), 64);
}

int EntityList::GetTeam(int slot) const
{
	const uintptr_t pawn = GetPawn(slot);
	if (pawn == 0) {
		return -1;
	}
	return m_memory->Read<int>(pawn + m_offsets->teamNum);
}

int EntityList::FindEntityByClassName(const char* className) const
{
	if (className == nullptr || m_clientBase == 0 || m_offsets->entityList == 0) {
		return -1;
	}

	const uintptr_t outer = m_memory->Read<uintptr_t>(m_clientBase + m_offsets->entityList);
	if (outer == 0) {
		return -1;
	}

	for (int chunk = 0; chunk < kMaxRawEntities / 512; chunk++) {
		const uintptr_t inner = m_memory->Read<uintptr_t>(outer + static_cast<uintptr_t>(chunk) * 0x8);
		if (inner == 0) {
			continue;
		}

		for (int slot = 0; slot < 512; slot++) {
			const int entityIndex = chunk * 512 + slot;
			const uintptr_t identity = inner + static_cast<uintptr_t>(slot) * 0x70;

			const uintptr_t entity = m_memory->Read<uintptr_t>(identity);
			if (entity == 0) {
				continue;
			}

			const uintptr_t classInfo = m_memory->Read<uintptr_t>(identity + 0x8);
			if (classInfo == 0) {
				continue;
			}

			const uintptr_t binding = m_memory->Read<uintptr_t>(classInfo + 0x8);
			if (binding == 0) {
				continue;
			}

			const uintptr_t namePtr = m_memory->Read<uintptr_t>(binding + 0x8);
			if (namePtr == 0) {
				continue;
			}

			if (m_memory->ReadString(namePtr, 64) == className) {
				return entityIndex;
			}
		}
	}

	return -1;
}

bool EntityList::IsValidPawn(uintptr_t pawn, int localTeam) const
{
	if (pawn == 0 || m_offsets->teamNum == 0 || m_offsets->health == 0 || m_offsets->lifeState == 0) {
		return false;
	}

	const int team = m_memory->Read<int>(pawn + m_offsets->teamNum);
	if (team != 2 && team != 3) {
		return false;
	}

	if (localTeam >= 2 && team == localTeam) {
		return false;
	}

	const int health = m_memory->Read<int>(pawn + m_offsets->health);
	if (health <= 0 || health > 100) {
		return false;
	}

	if (m_memory->Read<uint8_t>(pawn + m_offsets->lifeState) != 0) {
		return false;
	}

	return true;
}

bool EntityList::IsValidEnemy(int slot, int localTeam) const
{
	const uintptr_t pawn = GetPawn(slot);
	if (pawn == 0) {
		return false;
	}

	return IsValidPawn(pawn, localTeam);
}

bool EntityList::GetOrigin(int slot, Vec3& outOrigin) const
{
	const uintptr_t pawn = GetPawn(slot);
	if (pawn == 0) {
		return false;
	}

	m_memory->ReadArray<float>(pawn + m_offsets->oldOrigin, &outOrigin.x, 3);
	return true;
}

bool EntityList::GetBonePosition(int slot, int boneIndex, Vec3& outPosition) const
{
	const uintptr_t pawn = GetPawn(slot);
	if (boneIndex < 0 || pawn == 0 ||
		m_offsets->modelState == 0 || m_offsets->boneArray == 0) {
		return false;
	}

	uintptr_t sceneNode = m_memory->Read<uintptr_t>(pawn + m_offsets->gameSceneNode);
	if (sceneNode == 0) {
		return false;
	}

	const uintptr_t boneArray = m_memory->Read<uintptr_t>(sceneNode + m_offsets->modelState + m_offsets->boneArray);
	if (boneArray == 0) {
		return false;
	}

	m_memory->ReadArray<float>(boneArray + static_cast<uintptr_t>(boneIndex) * 0x20, &outPosition.x, 3);

	if (outPosition.x == 0.0f && outPosition.y == 0.0f && outPosition.z == 0.0f) {
		return false;
	}

	if (!std::isfinite(outPosition.x) || !std::isfinite(outPosition.y) || !std::isfinite(outPosition.z)) {
		return false;
	}
	if (std::fabs(outPosition.x) > 200000.0f || std::fabs(outPosition.y) > 200000.0f || std::fabs(outPosition.z) > 200000.0f) {
		return false;
	}

	return true;
}

int EntityList::FindBoneByName(uintptr_t pawn, const char* boneName) const
{
	if (pawn == 0 || m_offsets->hModel == 0) {
		return -1;
	}

	uintptr_t sceneNode = m_memory->Read<uintptr_t>(pawn + m_offsets->gameSceneNode);
	if (sceneNode == 0) {
		return -1;
	}

	const uintptr_t modelHandle = m_memory->Read<uintptr_t>(sceneNode + m_offsets->modelState + m_offsets->hModel);
	if (modelHandle == 0) {
		return -1;
	}

	const uintptr_t model = m_memory->Read<uintptr_t>(modelHandle);
	if (model == 0) {
		return -1;
	}

	const int boneCount = m_memory->Read<int>(model + 0x160);
	if (boneCount <= 0 || boneCount > 256) {
		return -1;
	}

	const uintptr_t nameArray = m_memory->Read<uintptr_t>(model + 0x168);
	if (nameArray == 0) {
		return -1;
	}

	for (int i = 0; i < boneCount; i++) {
		const uintptr_t nameAddress = m_memory->Read<uintptr_t>(nameArray + static_cast<uintptr_t>(i) * 0x8);
		if (nameAddress == 0) {
			continue;
		}
		if (m_memory->ReadString(nameAddress, 64) == boneName) {
			return i;
		}
	}
	return -1;
}

} // namespace sdk