// Project Sword & Gun Copyright © 2021 Kevin Poretti


#include "Weapons/SNGInstantRangedWeapon.h"

#include "DrawDebugHelpers.h"
#include "Characters/SNGCharacterBase.h"
#include "Kismet/GameplayStatics.h"
#include "Particles/ParticleSystemComponent.h"
#include "PhysicalMaterials/PhysicalMaterial.h"
#include "SwordNGun/SNGTypes.h"
#include "SwordNGun/SwordNGun.h"

static int32 DebugInstantWeaponDrawing = 0;
FAutoConsoleVariableRef CVARDebugInstantWeaponDrawing(TEXT("SNG.DebugInstantWeapons"),
                        DebugInstantWeaponDrawing, 
                        TEXT("Draw debug line traces and impact points for instant type ranged weapons"), 
                        ECVF_Cheat);

ASNGInstantRangedWeapon::ASNGInstantRangedWeapon()
{
	Range = 10000.0f;
}

void ASNGInstantRangedWeapon::PlayTracerEffect(FVector TraceEnd)
{
	// perform tracer effect from muzzle to trace end
	if(TracerEffect)
	{
		FVector MuzzleLocation = WeaponMesh->GetSocketLocation(MuzzleSocketName);
		UParticleSystemComponent* TracerComp =
			UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), TracerEffect, MuzzleLocation);
		if(TracerComp)
		{
			TracerComp->SetVectorParameter(TracerTargetName, TraceEnd);
		}
	}
}

void ASNGInstantRangedWeapon::DoFire_Implementation()
{
	Super::DoFire_Implementation();

	DoLineTrace();
}

void ASNGInstantRangedWeapon::DoLineTrace(FRotator SpreadOffset)
{
	// Get weapon owner and check if valid
	AActor* WeaponOwner = GetOwner();
	if(!WeaponOwner)
	{
		UE_LOG(LogTemp, Error, TEXT("SNGHitscanWeapon :: Weapon does not have owner"));
		return;
	}

	// Get actor eyes viewpoint
	FVector EyesLocation;
	FRotator EyesRotation;
	WeaponOwner->GetActorEyesViewPoint(EyesLocation, EyesRotation);
	
	// Calculate final raycast direction based on actor eyes and spread offset
	// find trace end using range value or final falloff curve keyframe
	FRotator ShotRotation = EyesRotation + SpreadOffset;
	FVector ShotDirection = ShotRotation.Vector();
	FVector TraceEnd = EyesLocation + (ShotDirection * Range);

	// set up collision query params (ignore weapon and owner, trace complex, return phys material)
	FCollisionQueryParams QueryParams;
	QueryParams.AddIgnoredActor(this);
	QueryParams.AddIgnoredActor(WeaponOwner);
	QueryParams.bTraceComplex = true;
	QueryParams.bReturnPhysicalMaterial = true;
	
	// do line trace
	EPhysicalSurface SurfaceType = SurfaceType_Default;
	FHitResult HitResult;
	bool IsHitSuccessful = GetWorld()->LineTraceSingleByChannel(HitResult, EyesLocation, TraceEnd, COLLISION_RANGED_WEAPON, QueryParams);
	if(IsHitSuccessful)
	{
		AActor* HitActor = HitResult.GetActor();

		SurfaceType = UPhysicalMaterial::DetermineSurfaceType(HitResult.PhysMaterial.Get());
		//int ActualDamage = CalculateWeaponDamage();
		// NOTE(kevin): This won't work once we have weapon falloff curves. Might want to separate damage from HitData
		// eventually
		if(SurfaceType == SNG_SURFACE_Flesh || SurfaceType == SNG_SURFACE_FleshVulnerable)
		{
			OnHit.Broadcast();
			if(HitMarkerSound)
				UGameplayStatics::PlaySound2D(GetWorld(), HitMarkerSound);
		}
		
		int ActualDamage = HitData.BaseDamage; 
		if(SurfaceType == SNG_SURFACE_FleshVulnerable)
		{
			ActualDamage *= VulnerableDamageMultiplier;
		}

		FSNGDamageEvent DamageEvent;
		DamageEvent.DamageTypeClass = UDamageType::StaticClass();
		DamageEvent.HitData = HitData;
		DamageEvent.SurfaceType = SurfaceType;
		DamageEvent.ImpactPoint = HitResult.ImpactPoint;
		HitActor->TakeDamage(ActualDamage, DamageEvent, WeaponOwner->GetInstigatorController(), this);
		
		if(HitData.HitstopApplicationType == ESNGHitstopApplicationType::Both ||
			HitData.HitstopApplicationType == ESNGHitstopApplicationType::CauserOnly)
		{
			ASNGCharacterBase* OwnerAsChar = Cast<ASNGCharacterBase>(GetOwner());
			OwnerAsChar->ApplyHitStop(HitData.StopTime, HitData.SlowTime);
		}

		FTransform SpawnTransform(HitResult.ImpactNormal.Rotation(), HitResult.ImpactPoint);
		PlayImpactEffects(SurfaceType, SpawnTransform);
	}

	PlayFireEffect();
	PlayTracerEffect(IsHitSuccessful ? HitResult.ImpactPoint : TraceEnd);
	
	if(DebugInstantWeaponDrawing)
	{
		DrawDebugLine(GetWorld(), EyesLocation, TraceEnd, FColor::Red, false, 1.0f, 0, 1.0f);
		DrawDebugSphere(GetWorld(), HitResult.ImpactPoint, 5.0f, 32, FColor::Green, false, 1.0f, 0, 1.0f);
	}
}


