#include "CMisc.hpp"

#include <algorithm>
#include <cstdio>
#include <ctime>
#include <mmsystem.h>

#pragma comment( lib , "winmm.lib" )

#include <CS2/SDK/SDK.hpp>
#include <CS2/SDK/Interface/IEngineToClient.hpp>
#include <CS2/SDK/Interface/IGameEvent.hpp>
#include <CS2/SDK/Update/CCSGOInput.hpp>
#include <CS2/SDK/Update/CUserCmd.hpp>
#include <CS2/SDK/Types/CEntityData.hpp>

#include <AndromedaClient/Settings/Settings.hpp>
#include <AndromedaClient/Fonts/CFontManager.hpp>
#include <AndromedaClient/Render/CRenderStackSystem.hpp>

#include <GameClient/CL_Players.hpp>
#include <GameClient/CL_Weapons.hpp>
#include <GameClient/CL_Trace.hpp>
#include <GameClient/CL_Bypass.hpp>
#include <GameClient/CEntityCache/CEntityCache.hpp>
#include <CS2/SDK/Types/CBaseTypes.hpp>

#include <ImGui/imgui.h>

static CMisc g_CMisc{};

auto CMisc::OnRender() -> void
{
	if ( !Settings::Misc::Watermark )
		return;

	const auto t = std::time( nullptr );
	std::tm tm{};
	localtime_s( &tm , &t );

	char szBuffer[ 128 ];

	std::snprintf( szBuffer , sizeof szBuffer , XorStr( "Evicted  |  FPS: %.0f  |  %02d:%02d:%02d" ) , ImGui::GetIO().Framerate , tm.tm_hour , tm.tm_min , tm.tm_sec );

	GetFontManager()->m_VerdanaFont.DrawString( 8 , 8 , ImColor( 255 , 255 , 0 ) , FW1_LEFT , "%s" , szBuffer );
}

auto CMisc::OnClientOutput() -> void
{
	if ( !SDK::Interfaces::EngineToClient()->IsInGame() )
		return;

	if ( Settings::Misc::SpectatorList )
	{
		auto* pLocalController = GetCL_Players()->GetLocalPlayerController();

		auto* pFont = &GetFontManager()->m_VerdanaFont;

		if ( pLocalController && pFont )
		{
			const CHandle LocalPawnHandle = pLocalController->m_hPawn();

			const auto DisplaySize = ImGui::GetIO().DisplaySize;

			const float ListX = DisplaySize.x - 8.f;
			float ListY = pFont->GetFontSize() + 12.f;

			const auto& CachedVec = GetEntityCache()->GetCachedEntity();

			std::scoped_lock Lock( GetEntityCache()->GetLock() );

			for ( const auto& CachedEntity : *CachedVec )
			{
				if ( CachedEntity.m_Type != CachedEntity_t::PLAYER_CONTROLLER )
					continue;

				auto* pEntity = CachedEntity.m_Handle.Get();

				if ( !pEntity )
					continue;

				auto* pController = reinterpret_cast<CCSPlayerController*>( pEntity );

				if ( pController == pLocalController )
					continue;

				auto* pPawn = pController->m_hPawn().Get<C_CSPlayerPawn>();

				if ( !pPawn || !pPawn->IsPlayerPawn() )
					continue;

				auto* pObserverServices = pPawn->m_pObserverServices();

				if ( !pObserverServices )
					continue;

				const auto ObserverMode = pObserverServices->m_iObserverMode();

				if ( ObserverMode != OBS_MODE_IN_EYE && ObserverMode != OBS_MODE_CHASE )
					continue;

				if ( pObserverServices->m_hObserverTarget() != LocalPawnHandle )
					continue;

				const char* szName = pController->m_sSanitizedPlayerName();

				if ( !szName || !szName[ 0 ] )
					continue;

				GetRenderStackSystem()->DrawString( pFont , ImVec2( ListX , ListY ) , FW1_RIGHT | FW1_TOP , ImColor( 255 , 255 , 255 ) , "%s" , szName );

				ListY += pFont->GetFontSize() + 2.f;
			}
		}
	}

	if ( Settings::Triggerbot::Active && Settings::Triggerbot::DrawIndicator )
	{
		if ( m_bTargetInCrosshair )
		{
			const auto DisplaySize = ImGui::GetIO().DisplaySize;

			const ImVec2 Center( DisplaySize.x * 0.5f , DisplaySize.y * 0.5f );

			const ImColor IndicatorColor = m_bTriggerFiring ? ImColor( 0 , 255 , 0 ) : ImColor( 255 , 200 , 0 );

			GetRenderStackSystem()->DrawCircleFilled( Center , 3.f , IndicatorColor );
		}
	}

	if ( Settings::Misc::Hitmarker )
	{
		if ( m_HitTime.time_since_epoch().count() > 0 )
		{
			const auto Elapsed = std::chrono::duration<float>( std::chrono::steady_clock::now() - m_HitTime ).count();

			if ( Elapsed <= 0.25f )
			{
				const auto DisplaySize = ImGui::GetIO().DisplaySize;

				const ImVec2 Center( DisplaySize.x * 0.5f , DisplaySize.y * 0.5f );

				const float Size = 6.f;

				GetRenderStackSystem()->DrawLine( ImVec2( Center.x - Size , Center.y - Size ) , ImVec2( Center.x - 2.f , Center.y - 2.f ) , ImColor( 255 , 255 , 255 ) , 1.5f );
				GetRenderStackSystem()->DrawLine( ImVec2( Center.x + Size , Center.y - Size ) , ImVec2( Center.x + 2.f , Center.y - 2.f ) , ImColor( 255 , 255 , 255 ) , 1.5f );
				GetRenderStackSystem()->DrawLine( ImVec2( Center.x - Size , Center.y + Size ) , ImVec2( Center.x - 2.f , Center.y + 2.f ) , ImColor( 255 , 255 , 255 ) , 1.5f );
				GetRenderStackSystem()->DrawLine( ImVec2( Center.x + Size , Center.y + Size ) , ImVec2( Center.x + 2.f , Center.y + 2.f ) , ImColor( 255 , 255 , 255 ) , 1.5f );
			}
		}
	}
}

auto CMisc::OnCreateMove( CCSGOInput* pInput , CUserCmd* pUserCmd ) -> void
{
	if ( !pUserCmd )
		return;

	if ( Settings::Triggerbot::Active )
		RunTriggerbot( pInput , pUserCmd );

	if ( Settings::Misc::Bunnyhop )
	{
		auto* pLocalPawn = GetCL_Players()->GetLocalPlayerPawn();

		if ( pLocalPawn && pLocalPawn->IsAlive() )
		{
			constexpr auto FL_ONGROUND = 1u;

			const bool bOnGround = ( pLocalPawn->m_fFlags() & FL_ONGROUND ) != 0;
			const bool bHoldingJump = ( pUserCmd->button_states.buttonstate1 & IN_JUMP ) != 0;

			if ( bHoldingJump )
			{
				if ( bOnGround )
					pUserCmd->button_states.buttonstate1 |= IN_JUMP;
				else
					pUserCmd->button_states.buttonstate1 &= ~IN_JUMP;
			}
		}
	}

	if ( Settings::Misc::FOVChanger )
	{
		auto* pLocalPawn = GetCL_Players()->GetLocalPlayerPawn();

		if ( pLocalPawn )
		{
			if ( auto* pCameraServices = pLocalPawn->m_pCameraServices(); pCameraServices )
				pCameraServices->m_iFOV() = static_cast<uint32>( Settings::Misc::FOV );
		}
	}
}

auto CMisc::RunTriggerbot( CCSGOInput* pInput , CUserCmd* pUserCmd ) -> void
{
	auto Reset = [ & ]()
	{
		m_bTriggerShooting = false;
		m_TriggerArmTime = {};
		m_ShotStartTime = {};

		m_bTargetInCrosshair = false;
		m_bTriggerFiring = false;
	};

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

	if ( !pLocalPawn || !pLocalPawn->IsAlive() )
	{
		Reset();
		return;
	}

	const auto WeaponType = GetCL_Weapons()->GetLocalWeaponType();

	const bool bIsGun = WeaponType == CSWeaponType_t::WEAPONTYPE_PISTOL ||
		WeaponType == CSWeaponType_t::WEAPONTYPE_SUBMACHINEGUN ||
		WeaponType == CSWeaponType_t::WEAPONTYPE_RIFLE ||
		WeaponType == CSWeaponType_t::WEAPONTYPE_SHOTGUN ||
		WeaponType == CSWeaponType_t::WEAPONTYPE_SNIPER_RIFLE ||
		WeaponType == CSWeaponType_t::WEAPONTYPE_MACHINEGUN ||
		WeaponType == CSWeaponType_t::WEAPONTYPE_TASER;

	if ( !bIsGun )
	{
		Reset();
		return;
	}

	if ( Settings::Triggerbot::ScopedOnly )
	{
		if ( WeaponType == CSWeaponType_t::WEAPONTYPE_SNIPER_RIFLE && !pLocalPawn->m_bIsScoped() )
		{
			Reset();
			return;
		}
	}

	bool bShouldRun = false;

	switch ( Settings::Triggerbot::Mode )
	{
		case 1:
		{
			bShouldRun = true;
		}
		break;

		case 2:
		{
			const bool bKeyDown = ( GetAsyncKeyState( Settings::Triggerbot::Key ) & 0x8000 ) != 0;

			if ( bKeyDown && !m_bTriggerKeyDown )
				m_bTriggerToggledOn = !m_bTriggerToggledOn;

			m_bTriggerKeyDown = bKeyDown;

			bShouldRun = m_bTriggerToggledOn;
		}
		break;

		default:
		{
			const bool bKeyDown = ( GetAsyncKeyState( Settings::Triggerbot::Key ) & 0x8000 ) != 0;

			bShouldRun = bKeyDown;
		}
		break;
	}

	if ( !bShouldRun )
	{
		Reset();
		return;
	}

	const auto Result = GetCL_Trace()->TraceToHitGroup( pInput );

	auto* pTargetPawn = reinterpret_cast<C_CSPlayerPawn*>( Result.pHitEntity );

	bool bTargetValid = false;

	if ( pTargetPawn && pTargetPawn->IsPlayerPawn() && pTargetPawn != pLocalPawn && pTargetPawn->IsAlive() )
	{
		if ( !Settings::Triggerbot::IgnoreTeammates || pTargetPawn->m_iTeamNum() != pLocalPawn->m_iTeamNum() )
		{
			switch ( Result.nHitGroup )
			{
				case 1: bTargetValid = Settings::Triggerbot::Hitbox_Head; break;
				case 2: bTargetValid = Settings::Triggerbot::Hitbox_Chest; break;
				case 3: bTargetValid = Settings::Triggerbot::Hitbox_Stomach; break;
				case 4:
				case 5: bTargetValid = Settings::Triggerbot::Hitbox_Arms; break;
				case 6:
				case 7: bTargetValid = Settings::Triggerbot::Hitbox_Legs; break;
				default: bTargetValid = true; break;
			}
		}
	}

	m_bTargetInCrosshair = bTargetValid;

	if ( !bTargetValid )
	{
		m_bTriggerShooting = false;
		m_TriggerArmTime = {};
		m_ShotStartTime = {};

		m_bTriggerFiring = false;

		return;
	}

	const auto Now = std::chrono::steady_clock::now();

	const auto DelayMs = std::chrono::milliseconds( std::max( 0 , Settings::Triggerbot::Delay ) );
	const auto HoldMs = std::chrono::milliseconds( std::max( 0 , Settings::Triggerbot::HoldTime ) );

	if ( !m_bTriggerShooting )
	{
		if ( m_TriggerArmTime.time_since_epoch().count() == 0 )
			m_TriggerArmTime = Now;

		if ( ( Now - m_TriggerArmTime ) >= DelayMs )
		{
			m_bTriggerShooting = true;
			m_ShotStartTime = Now;
			m_TriggerArmTime = {};
		}
	}
	else
	{
		if ( ( Now - m_ShotStartTime ) >= HoldMs )
		{
			m_bTriggerShooting = false;
			m_TriggerArmTime = Now;
		}
	}

	if ( m_bTriggerShooting )
	{
		GetCL_Bypass()->SetAttack( pUserCmd , true );

		m_bTriggerFiring = true;
	}
	else
	{
		m_bTriggerFiring = false;
	}
}

auto CMisc::OnFrameStageNotify( int FrameStage ) -> void
{
	if ( FrameStage != 6 )
		return;

	if ( !Settings::Misc::NoFlash )
		return;

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

	if ( !pLocalPawn )
		return;

	pLocalPawn->m_flFlashDuration() = 0.f;
	pLocalPawn->m_flFlashMaxAlpha() = 0.f;
}

auto CMisc::OnFireEventClientSide( IGameEvent* pGameEvent ) -> void
{
	if ( !pGameEvent )
		return;

	if ( _strcmpi( pGameEvent->GetName() , XorStr( "player_hurt" ) ) != 0 )
		return;

	auto* pLocalController = GetCL_Players()->GetLocalPlayerController();

	if ( !pLocalController )
		return;

	auto* pAttackerController = pGameEvent->GetPlayerController( XorStr( "attacker" ) );

	if ( pAttackerController != pLocalController )
		return;

	const auto Now = std::chrono::steady_clock::now();

	if ( Settings::Misc::Hitmarker )
		m_HitTime = Now;

	if ( Settings::Misc::HitSound )
	{
		if ( ( Now - m_HitSoundTime ) > std::chrono::milliseconds( 50 ) )
		{
			m_HitSoundTime = Now;

			PlaySoundW( L"SystemHand" , nullptr , SND_ALIAS | SND_ASYNC | SND_NODEFAULT );
		}
	}
}

auto GetMisc() -> CMisc*
{
	return &g_CMisc;
}
