#include "CChams.hpp"

#include <cstdint>

#include <CS2/SDK/SDK.hpp>
#include <CS2/SDK/FunctionListSDK.hpp>
#include <CS2/SDK/Interface/CMaterialSystem2.hpp>
#include <CS2/SDK/Types/CEntityData.hpp>
#include <CS2/SDK/Types/CHandle.hpp>

#include <GameClient/CL_Players.hpp>

#include <CS2/Hook/Hook_OnDrawObject.hpp>

#include <AndromedaClient/Settings/Settings.hpp>

static CChams g_CChams{};

// ---- build-specific, version-dependent constants ---------------------------
namespace ChamsMesh
{
	// stride of one CMeshData entry in the mesh draw array
	constexpr std::size_t MESH_STRIDE = 0x70;

	// CMeshData.pSceneAnimatableObject
	constexpr std::size_t SCENE_OBJECT_OFFSET = 0x18;
	// CMeshData.pMaterial (visible)
	constexpr std::size_t MATERIAL_OFFSET = 0x20;
	// CMeshData.pMaterial (second / occluded handle)
	constexpr std::size_t MATERIAL2_OFFSET = 0x28;
	// CMeshData.colValue (00AABBGGRR)
	constexpr std::size_t COLOR_OFFSET = 0x50;

	// CSceneAnimatableObject.hOwner (CHandle of the owning entity)
	constexpr std::size_t OWNER_OFFSET = 0x18;
}

// ---- minimal KeyValues3 / KV3ID_t -----------------------------------------
class KeyValues3
{
public:
	// Opaque storage large enough for the parsed KV3 tree; LoadKV3 writes here.
	alignas( 16 ) uint8_t m_pData[ 0x400 ] = {};
};

struct KV3ID_t
{
	const char* szName = "";
	uint64_t unk0 = 0x469806E97412167C;
	uint64_t unk1 = 0xE73790B53EE6F2AF;
};

// ---- shader buffers (standard flat cham materials) -------------------------
static constexpr char szVMatVisible[] =
R"---(<!-- kv3 encoding:text:version{e21c7f3c-8a33-41c5-9977-a76d3a32aa0d} format:generic:version{7412167c-06e9-4698-aff2-e63eb59037e7} -->
{
	shader = "csgo_unlitgeneric.vfx"

	F_PAINT_VERTEX_COLORS = 1
	F_TRANSLUCENT = 1
	F_BLEND_MODE = 1

	g_vColorTint = [1, 1, 1, 1]

	TextureAmbientOcclusion = resource:"materials/default/default_mask_tga_fde710a5.vtex"
	g_tAmbientOcclusion = resource:"materials/default/default_mask_tga_fde710a5.vtex"
	g_tColor = resource:"materials/default/default_mask_tga_fde710a5.vtex"
	g_tNormal = resource:"materials/default/default_mask_tga_fde710a5.vtex"
	g_tTintMask = resource:"materials/default/default_mask_tga_fde710a5.vtex"
})---";

static constexpr char szVMatInvisible[] =
R"---(<!-- kv3 encoding:text:version{e21c7f3c-8a33-41c5-9977-a76d3a32aa0d} format:generic:version{7412167c-06e9-4698-aff2-e63eb59037e7} -->
{
	shader = "csgo_unlitgeneric.vfx"

	F_PAINT_VERTEX_COLORS = 1
	F_TRANSLUCENT = 1
	F_BLEND_MODE = 1
	F_DISABLE_Z_BUFFERING = 1

	g_vColorTint = [1, 1, 1, 1]

	TextureAmbientOcclusion = resource:"materials/default/default_mask_tga_fde710a5.vtex"
	g_tAmbientOcclusion = resource:"materials/default/default_mask_tga_fde710a5.vtex"
	g_tColor = resource:"materials/default/default_mask_tga_fde710a5.vtex"
	g_tNormal = resource:"materials/default/default_mask_tga_fde710a5.vtex"
	g_tTintMask = resource:"materials/default/default_mask_tga_fde710a5.vtex"
})---";

// ---- original draw call (set by the hook, Hook_OnDrawObject.hpp) ----------

auto CChams::CreateMaterial( const char* szName , const char* szVmatBuffer ) -> CMaterial2*
{
	// KeyValues3 must be constructed via the game's SetTypeKV3 before use,
	// otherwise KeyValues3::LoadKV3 dereferences an uninitialised allocator and
	// access-violates inside tier0.dll (C0000005).
	auto* kv3 = new KeyValues3[ 0x10 ];
	KV3ID_t kv3ID{};

	ClientKeyValues3_SetTypeKV3( kv3 , 1U , 6U );

	if ( !KeyValues3_LoadKV3( kv3 , szVmatBuffer , &kv3ID ) )
	{
		delete[] kv3;
		return nullptr;
	}

	auto* pMaterialSystem = SDK::Interfaces::MaterialSystem2();

	if ( !pMaterialSystem )
	{
		delete[] kv3;
		return nullptr;
	}

	CMaterial2** pHandle = nullptr;

	pMaterialSystem->CreateMaterial( &pHandle , szName , kv3 );

	delete[] kv3;

	if ( pHandle && *pHandle )
		return *pHandle;

	return nullptr;
}

auto CChams::CreateMaterials() -> void
{
	if ( IsReady() )
		return;

	if ( !m_pMaterialVisible )
		m_pMaterialVisible = CreateMaterial( "chams_visible" , szVMatVisible );

	if ( !m_pMaterialInvisible )
		m_pMaterialInvisible = CreateMaterial( "chams_invisible" , szVMatInvisible );

	DEV_LOG( "[Chams] CreateMaterials: visible=%p invisible=%p\n" , (void*)m_pMaterialVisible , (void*)m_pMaterialInvisible );
}

auto CChams::OnDrawObject( void* pAnimatableSceneObjectDesc , void* pDx11 , void* pMeshData , int nDataCount , void* pSceneView , void* pSceneLayer , void* pUnk , void* pUnk2 ) -> bool
{
	if ( !Settings::Chams::Enabled )
		return false;

	if ( !IsReady() || !pMeshData || nDataCount <= 0 )
		return false;

	auto* pLocalPawn = GetCL_Players()->GetLocalPlayerPawn();

	if ( !pLocalPawn )
		return false;

	{
		static bool bLogged = false;

		if ( !bLogged )
		{
			bLogged = true;

			DEV_LOG( "[Chams] OnDrawObject fired: meshData=%p count=%d IsReady=%d\n" , pMeshData , nDataCount , IsReady() ? 1 : 0 );
		}
	}

	const auto* pBytes = static_cast<const uint8_t*>( pMeshData );

	for ( int i = 0; i < nDataCount; i++ )
	{
		const uint8_t* pEntry = pBytes + static_cast<std::size_t>( i ) * ChamsMesh::MESH_STRIDE;

		const void* pSceneObject = *(const void**)( pEntry + ChamsMesh::SCENE_OBJECT_OFFSET );

		if ( !pSceneObject )
			continue;

		const CHandle hOwner = *(const CHandle*)( (const uint8_t*)pSceneObject + ChamsMesh::OWNER_OFFSET );

		if ( !hOwner.IsValid() )
			continue;

		auto* pPawn = hOwner.Get<C_CSPlayerPawn>();

		if ( !pPawn || !pPawn->IsPlayerPawn() || !pPawn->IsAlive() || pPawn == pLocalPawn )
			continue;

		// only apply chams to enemies
		if ( pPawn->m_iTeamNum() == pLocalPawn->m_iTeamNum() )
			continue;

		{
			static bool bLoggedMatch = false;

			if ( !bLoggedMatch )
			{
				bLoggedMatch = true;

				DEV_LOG( "[Chams] matched enemy mesh entry %d\n" , i );
			}
		}

		auto* pWN = const_cast<uint8_t*>( pEntry );

		if ( Settings::Chams::IgnoreZ )
		{
			*(CMaterial2**)( pWN + ChamsMesh::MATERIAL_OFFSET ) = m_pMaterialInvisible;
			*(CMaterial2**)( pWN + ChamsMesh::MATERIAL2_OFFSET ) = m_pMaterialInvisible;
			*(uint32_t*)( pWN + ChamsMesh::COLOR_OFFSET ) = ImGui::ColorConvertFloat4ToU32( Settings::Chams::Occluded );

			OnDrawObject_o( pAnimatableSceneObjectDesc , pDx11 , pMeshData , nDataCount , pSceneView , pSceneLayer , pUnk , pUnk2 );
		}

		*(CMaterial2**)( pWN + ChamsMesh::MATERIAL_OFFSET ) = m_pMaterialVisible;
		*(CMaterial2**)( pWN + ChamsMesh::MATERIAL2_OFFSET ) = m_pMaterialVisible;
		*(uint32_t*)( pWN + ChamsMesh::COLOR_OFFSET ) = ImGui::ColorConvertFloat4ToU32( Settings::Chams::Visible );

		OnDrawObject_o( pAnimatableSceneObjectDesc , pDx11 , pMeshData , nDataCount , pSceneView , pSceneLayer , pUnk , pUnk2 );

		return true;
	}

	return false;
}

auto GetChams() -> CChams*
{
	return &g_CChams;
}