// Copyright 2018 Mookie. All Rights Reserved.
#include "EBBarrel.h"

UEBBarrel::UEBBarrel() {
	PrimaryComponentTick.bCanEverTick = true;
	bHiddenInGame = true;
	bAutoActivate = true;
	SetIsReplicatedByDefault(ReplicateVariables);

	RandomStream.GenerateNewSeed();

	GatlingRPS = FireRateMin;
}

void UEBBarrel::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	if (AntiRecoil)
	{
		if (previousAim.Num() > AntiRecoilPrevCount)
		{
			previousAim.RemoveAt(0);
		}
		if (previousLocation.Num() > AntiRecoilPrevCount)
		{
			previousLocation.RemoveAt(0);
		}
	}

	if (ClientSideAim){
		if (GetOwner()->GetRemoteRole()==ROLE_Authority){
			TimeSinceAimUpdate += DeltaTime;
			if (TimeSinceAimUpdate >= 1.0f / ClientAimUpdateFrequency) {

				if (AntiRecoil)
				{
					// ForAntiRecoil
					previousAim.Add(GetComponentTransform().GetUnitAxis(EAxis::X));
					previousLocation.Add(GetComponentTransform().GetLocation());

					Aim = previousAim[0];
					Location = previousLocation[0];
				}
				else
				{
					Aim = GetComponentTransform().GetUnitAxis(EAxis::X);
					Location = GetComponentTransform().GetLocation();
				}

				ClientAim(UGameplayStatics::RebaseLocalOriginOntoZero(GetWorld(),Location), Aim);
				TimeSinceAimUpdate = FMath::Fmod(TimeSinceAimUpdate, 1.0f / ClientAimUpdateFrequency);
			};
		}else{
			if (!RemoteAimReceived) {
				if (AntiRecoil)
				{
					// ForAntiRecoil
					previousAim.Add(GetComponentTransform().GetUnitAxis(EAxis::X));
					previousLocation.Add(GetComponentTransform().GetLocation());

					Aim = previousAim[0];
					Location = previousLocation[0];
				}
				else
				{
					Aim = GetComponentTransform().GetUnitAxis(EAxis::X);
					Location = GetComponentTransform().GetLocation();
				}
			}
			else {
				FVector LocOffset = (Location - GetComponentLocation());
				if (LocOffset.Size() > ClientAimDistanceLimit) {
					//lag or cheater???
					Location = GetComponentLocation() + LocOffset.GetSafeNormal()*ClientAimDistanceLimit;
				}
			}
		}
	}
	else {
		if (AntiRecoil)
		{
			// ForAntiRecoil
			previousAim.Add(GetComponentTransform().GetUnitAxis(EAxis::X));
			previousLocation.Add(GetComponentTransform().GetLocation());

			Aim = previousAim[0];
			Location = previousLocation[0];
		}
		else
		{
			Aim = GetComponentTransform().GetUnitAxis(EAxis::X);
			Location = GetComponentTransform().GetLocation();
		}
	}

	//Only server can tick
	if (GetOwner()->GetLocalRole() == ROLE_Authority){

		float RemainingDelta;

		if (FireMode == EFireMode::FM_Gatling) {
			if (Spooling || (GatlingAutoSpool && Shooting)) {
				GatlingRPS = FMath::Lerp(GatlingRPS, FireRateMax, FMath::Min(GatlingSpoolUpTime*DeltaTime, 1.0f));
			}
			else {
				GatlingRPS = FMath::Lerp(GatlingRPS, FireRateMin, FMath::Min(GatlingSpoolUpTime*DeltaTime, 1.0f));
			}
			GatlingPhase += GatlingRPS*DeltaTime;
			for (int i = 1; i <= GatlingPhase; i++) {
				if (Cooldown <= 0.0f && LoadNext) {
					NextBullet();
				}

				if (Shooting && ChamberedBullet != nullptr && (!ShootingBlocked)) {
					SpawnBullet(GetOwner(), Location, Aim, nextFireEventID);
				}
			}
			GatlingPhase = FMath::Fmod(GatlingPhase, 1.0f);

		}
		else {
			RemainingDelta = DeltaTime;
			do {
				float step = FMath::Min(Cooldown, RemainingDelta);

				Cooldown -= step;

				RemainingDelta -= step;

				if (Cooldown <= 0.0f && LoadNext) {
					NextBullet();
				}

				//shoot when ready
				if (Shooting && ChamberedBullet != nullptr && (!ShootingBlocked)) {
					if (BurstRemaining > 0 || (FireMode != EFireMode::FM_Burst && FireMode != EFireMode::FM_InterBurst)) {
						SpawnBullet(GetOwner(), Location, Aim, nextFireEventID);
					}
					else {
						Shooting = false;
					}
				}
			} while (RemainingDelta > 0 && Cooldown > 0);
		}
	}
}

void UEBBarrel::NextBullet() {
	if (ChamberedBullet == nullptr) {
		if (Ammo.Num() > 0 && (CycleAmmoCount > 0 || CycleAmmoUnlimited || (!CycleAmmo))) {

			//cycle ammo
			if (CycleAmmo) {
				if (CycleAmmoPos >= Ammo.Num()) { CycleAmmoPos = 0; }
				ChamberedBullet = Ammo[CycleAmmoPos];
				CycleAmmoPos++;

				if (!CycleAmmoUnlimited) {
					CycleAmmoCount--;
				}
			}
			else {
				ChamberedBullet = Ammo[0];
				Ammo.RemoveAt(0, 1, EAllowShrinking::Yes);
			}

			ReadyToShoot.Broadcast();
		}
		else {
			AmmoDepleted.Broadcast();
		}
	}
}

void UEBBarrel::SpawnBullet(AActor* Owner, FVector InLocation, FVector InAim, int fireEventID) {
	TSubclassOf<class AEBBullet> BulletClass = ChamberedBullet;

	if (BulletClass != nullptr) {
		FVector OutLocation;
		FVector OutAim;

		InitialBulletTransform(InLocation, InAim, OutLocation, OutAim);

		AEBBullet* Default = Cast<AEBBullet>(BulletClass->GetDefaultObject());

		float BulletSpread = Default->Spread;
		if (Default->SpreadBias > 0.0f) {
			float SpreadMult = FMath::Pow(FMath::FRand(), Default->SpreadBias);
			BulletSpread *= SpreadMult;
		}
		float BarrelSpread = Spread;
		if (SpreadBias > 0.0f) {
			float SpreadMult = FMath::Pow(FMath::FRand(), SpreadBias);
			BarrelSpread *= SpreadMult;
		}

		float TotalSpread = BulletSpread+BarrelSpread;

		OutAim = RandomStream.VRandCone(OutAim,TotalSpread);
		float BulletVelocity = FMath::Lerp(MuzzleVelocityMultiplierMin* Default->MuzzleVelocityMin, MuzzleVelocityMultiplierMax*Default->MuzzleVelocityMax, RandomStream.FRand());
		FVector Velocity = OutAim*BulletVelocity;

		//get parent physics body
		UPrimitiveComponent* parent = Cast<UPrimitiveComponent>(GetAttachParent());
		Velocity += AdditionalVelocity;

		if (parent != nullptr) {

			if (parent->IsSimulatingPhysics()) {
				Velocity += parent->GetPhysicsLinearVelocityAtPoint(OutLocation)*InheritVelocity;
			}

			if (Default->Shotgun) {
				ApplyRecoil(parent, OutLocation, -Velocity*Default->Mass*RecoilMultiplier*Default->ShotCount);
			}
			else{
				ApplyRecoil(parent, OutLocation, -Velocity*Default->Mass*RecoilMultiplier);
			}
		}

		BeforeShotFired.Broadcast();
#ifdef WITH_EDITOR
		if (shotTrace) {
			DrawDebugLine(GetWorld(), OutLocation, Velocity, FColor(1, 0, 0, 1), false, 3, 0, 0);
		};
#endif
		if (ReplicateShotFiredEvents) {
			SpawnBulletEventMulticast(OutLocation, Velocity);
		}
		else {
			SpawnBulletEvent.Broadcast(OutLocation, Velocity);
		}

		AEBBullet::SpawnWithExactVelocity(BulletClass, Owner, Owner->GetInstigator(), OutLocation, Velocity, fireEventID);

		//spend ammo
		ChamberedBullet = nullptr;
		if (FireMode != EFireMode::FM_Gatling) {
			Cooldown = 1.0f / FMath::Lerp(FireRateMin, FireRateMax, RandomStream.FRand());
		}

		//fire modes
		switch (FireMode) {
		case EFireMode::FM_Auto:
			LoadNext = true;
			break;

		case EFireMode::FM_Burst:
			LoadNext = true;
			break;

		case EFireMode::FM_InterBurst:
			LoadNext = true;
			break;

		case EFireMode::FM_Semiauto:
			Shooting = false;
			LoadNext = true;
			break;

		case EFireMode::FM_Manual:
			Shooting = false;
			LoadNext = false;
			break;

		case EFireMode::FM_Slamfire:
			LoadNext = false;
			break;

		case EFireMode::FM_Gatling:
			LoadNext = true;
			break;
		};

		if (BurstRemaining > 0) {
			BurstRemaining--;
		}
		else {
			if (FireMode == EFireMode::FM_Burst || FireMode == EFireMode::FM_InterBurst) {
				Cooldown = FMath::Max(Cooldown, BurstCooldown);
			}
		}

		if (ReplicateShotFiredEvents) {
			ShotFiredMulticast();
		}
		else {
			ShotFired.Broadcast();
		}
	}
}

void UEBBarrel::InitialBulletTransform_Implementation(FVector InLocation, FVector InDirection, FVector& OutLocation, FVector& OutDirection) {
	OutLocation = InLocation;
	OutDirection = InDirection;
}

void UEBBarrel::ApplyRecoil_Implementation(UPrimitiveComponent* Component, FVector InLocation, FVector Impulse){
	if (Component->IsSimulatingPhysics()) {
		Component->AddImpulseAtLocation(Impulse, InLocation);
	}
}