// Copyright 2020 Mookie. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "Components/PrimitiveComponent.h"
#include "Kismet/GameplayStatics.h"

#include "EBBullet.h"

#include "EBBarrel.generated.h"

UENUM(BlueprintType)
enum class EFireMode : uint8
{
	FM_Auto UMETA(DisplayName = "Full Auto"),
	FM_Semiauto UMETA(DisplayName = "Semiauto"),
	FM_Burst UMETA(DisplayName = "Burst"),
	FM_InterBurst UMETA(DisplayName = "Interruptible Burst"),
	FM_Manual UMETA(DisplayName = "Manual"),
	FM_Slamfire UMETA(DisplayName = "Slam Fire"),
	FM_Gatling UMETA(DisplayName = "Gatling")
};

UENUM(BlueprintType)
enum class EAntiRecoilMode : uint8
{
	ARM_None UMETA(DisplayName = "Disabled", ToolTip = "No anti-recoil processing. Uses raw tracker data directly. Use this when no IMU shock compensation is needed."),
	ARM_Buffer UMETA(DisplayName = "Buffer (No Prediction)", ToolTip = "Legacy mode. Returns the oldest sample in the buffer, guaranteed to be pre-shock. Simple and reliable but introduces a fixed time delay equal to BufferTime. Best for static or slow-moving aiming."),
	ARM_LinearExtrapolation UMETA(DisplayName = "Linear Extrapolation", ToolTip = "Computes average linear and angular velocity from consecutive safe (pre-shock) samples, then extrapolates forward to the current time. Good balance of simplicity and accuracy for steady movements. May overshoot on sudden direction changes."),
	ARM_WeightedRegression UMETA(DisplayName = "Weighted Regression", ToolTip = "Fits a weighted least-squares regression line through all safe samples (recent safe samples weighted higher), then extrapolates to current time. More robust to individual noisy samples than linear extrapolation. Slightly heavier computation."),
	ARM_KalmanFilter UMETA(DisplayName = "Kalman Filter", ToolTip = "Maintains an internal state model (position + velocity, aim + angular velocity) updated only with safe samples. Predicts forward using the estimated dynamics. Best for smooth continuous tracking with optimal noise rejection. Requires tuning ProcessNoise and MeasurementNoise for best results.")
};

USTRUCT()
struct FTimestampedTransform
{
	GENERATED_BODY()

	double Timestamp = 0.0;
	FVector Location = FVector::ZeroVector;
	FVector Aim = FVector::ForwardVector;
};

UCLASS(Blueprintable, ClassGroup = (Custom), hidecategories = (Object, LOD, Physics, Lighting, TextureStreaming, Collision, HLOD, Mobile, VirtualTexture, ComponentReplication), editinlinenew, meta = (BlueprintSpawnableComponent))
	class EASYBALLISTICS_API UEBBarrel : public UPrimitiveComponent
{

	GENERATED_BODY()

public:
	// Sets default values for this component's properties
	UEBBarrel();

	// Called every frame
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "PROSERVE") int nextFireEventID = 100.0f;
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") bool shotTrace = false;
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") float DebugArrowSize = 100.0f;

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug", meta = (ToolTip = "Draw real-time debug lines: Green = raw tracker, Red = anti-recoil predicted aim"))
		bool DebugAntiRecoil = false;
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug", meta = (ToolTip = "Length of the debug aim lines (cm)", EditCondition = "DebugAntiRecoil"))
		float DebugAntiRecoilLineLength = 200.0f;
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug", meta = (ToolTip = "Thickness of the debug aim lines", EditCondition = "DebugAntiRecoil"))
		float DebugAntiRecoilLineThickness = 2.0f;

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|IMU Shock Simulation", meta = (ToolTip = "Enable IMU shock simulation for testing anti-recoil prediction without firing"))
		bool DebugSimulateIMUShock = false;
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|IMU Shock Simulation", meta = (ToolTip = "Angular perturbation intensity in degrees", EditCondition = "DebugSimulateIMUShock", ClampMin = "0.0"))
		float DebugIMUShockAngle = 15.0f;
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|IMU Shock Simulation", meta = (ToolTip = "Position perturbation intensity in cm", EditCondition = "DebugSimulateIMUShock", ClampMin = "0.0"))
		float DebugIMUShockPosition = 2.0f;
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|IMU Shock Simulation", meta = (ToolTip = "Duration of the simulated shock in seconds", EditCondition = "DebugSimulateIMUShock", ClampMin = "0.01"))
		float DebugIMUShockDuration = 0.08f;
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|IMU Shock Simulation", meta = (ToolTip = "How long (seconds) the yellow debug line persists after a shock, showing where the shot would have gone without anti-recoil correction", EditCondition = "DebugSimulateIMUShock", ClampMin = "0.5"))
		float DebugIMUShockDisplayTime = 3.0f;

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "AntiRecoil", meta = (ToolTip = "Selects the anti-recoil compensation algorithm. Hover over each option in the dropdown for a detailed description of how it works."))
		EAntiRecoilMode AntiRecoilMode = EAntiRecoilMode::ARM_WeightedRegression;

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "AntiRecoil", meta = (ToolTip = "Total time window (seconds) of tracker history to keep. Determines how far back in time samples are stored. Must be greater than DiscardTime. Example: 0.2s at 60fps stores ~12 samples.", ClampMin = "0.05"))
		float AntiRecoilBufferTime = 0.2f;

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "AntiRecoil", meta = (ToolTip = "Time window (seconds) of most recent samples to exclude as potentially contaminated by IMU recoil shock. The prediction algorithms only use samples older than this. Increase if the shock lasts longer. Safe window = BufferTime - DiscardTime.", ClampMin = "0.0"))
		float AntiRecoilDiscardTime = 0.05f;

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "AntiRecoil", meta = (ToolTip = "Controls how the weight curve grows across safe samples in Weighted Regression mode. 1.0 = linear growth (default), >1.0 = recent samples weighted much more heavily (convex curve), <1.0 = more uniform weighting (concave curve), 0.0 = all samples weighted equally (unweighted regression). Formula: weight = pow(sampleIndex+1, exponent).", EditCondition = "AntiRecoilMode == EAntiRecoilMode::ARM_WeightedRegression", ClampMin = "0.0", ClampMax = "5.0"))
		float RegressionWeightExponent = 1.0f;

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "AntiRecoil", meta = (ToolTip = "Kalman filter process noise (higher = more responsive to movement changes, lower = smoother). Since safe samples are already filtered by DiscardTime, this should be high enough to track aiming movements.", EditCondition = "AntiRecoilMode == EAntiRecoilMode::ARM_KalmanFilter", ClampMin = "0.01"))
		float KalmanProcessNoise = 50.0f;

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "AntiRecoil", meta = (ToolTip = "Kalman filter measurement noise (higher = trusts model over measurements, lower = trusts measurements). Should be low since safe samples are clean.", EditCondition = "AntiRecoilMode == EAntiRecoilMode::ARM_KalmanFilter", ClampMin = "0.001"))
		float KalmanMeasurementNoise = 0.1f;

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Velocity", meta = (ToolTip = "Bullet inherits barrel velocity, only works with physics enabled or with additional velocity set")) float InheritVelocity = 1.0f;
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Velocity", meta = (ToolTip = "Amount of recoil applied to the barrel, only works with physics enabled")) float RecoilMultiplier = 1.0f;
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Velocity", meta = (ToolTip = "Additional velocity, for use with InheritVelocity")) FVector AdditionalVelocity = FVector(0,0,0);

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Additional maximum spread, in radians, applied on top of bullet spread", ClampMin = "0")) float Spread=0.0f;
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Additional Spread bias, higher is more accurate on average", ClampMin = "0")) float SpreadBias = 0.0f;
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Minimum of random multiplier applied to bullet muzzle velocity")) float MuzzleVelocityMultiplierMin = 1.0f;
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Maximum of random multiplier applied to bullet muzzle velocity")) float MuzzleVelocityMultiplierMax = 1.0f;
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Minimum fire rate, rounds per second")) float FireRateMin = 1.0f;
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Maximum fire rate, rounds per second, set to same number as FireRateMin to disable randomization")) float FireRateMax = 1.0f;
	UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Weapon") EFireMode FireMode = EFireMode::FM_Auto;
	UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Weapon") bool ShootingBlocked;

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Number of rounds auto fired in burst mode")) int BurstCount = 3;
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Minimum time between bursts")) float BurstCooldown = 0.0f;
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Automatically spin up gatling when trigger is being held down")) bool GatlingAutoSpool = true;
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon") float GatlingSpoolUpTime = 1.0f;
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon") float  GatlingSpoolDownTime = 1.0f;
	UPROPERTY(BlueprintReadWrite, Category = "Weapon") float  GatlingPhase = 0.0f;

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Ammo") bool CycleAmmo = true;
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Ammo", meta = (EditCondition = "CycleAmmo")) bool CycleAmmoUnlimited = true;
	UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Ammo") TArray<TSubclassOf<class AEBBullet>> Ammo;
	UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Ammo", meta = (EditCondition = "CycleAmmo")) int CycleAmmoCount;
	UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Ammo", meta = (EditCondition = "CycleAmmo")) int CycleAmmoPos;

	UPROPERTY(Replicated, BlueprintReadWrite, Category = "WeaponState") TSubclassOf<class AEBBullet> ChamberedBullet;
	UPROPERTY(Replicated, BlueprintReadWrite, Category = "WeaponState") bool Shooting;
	UPROPERTY(Replicated, BlueprintReadWrite, Category = "WeaponState") bool Spooling = false;
	UPROPERTY(BlueprintReadWrite, Category = "Weapon") float GatlingRPS = 0.0f;

	UPROPERTY(BlueprintReadWrite, Category = "WeaponState") bool LoadNext=true;
	UPROPERTY(BlueprintReadWrite, Category = "WeaponState") float Cooldown;
	UPROPERTY(BlueprintReadWrite, Category = "WeaponState") int BurstRemaining;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Replication") bool ReplicateVariables=true;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Replication") bool ReplicateShotFiredEvents = true;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Replication") bool ClientSideAim=false;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Replication") float ClientAimUpdateFrequency = 15.0f;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Replication") float ClientAimDistanceLimit = 200.0f;

	FRandomStream RandomStream;

	UFUNCTION() void NextBullet();
	UFUNCTION(BlueprintPure, Category = "Ammo") int GetAmmoCount(bool CountChambered) const;
	UFUNCTION(BlueprintPure, Category = "Ammo") TArray<TSubclassOf<class AEBBullet>> GetAmmo(bool CountChambered) const;
	UFUNCTION(BlueprintAuthorityOnly, BlueprintCallable, Category = "Ammo") void SetAmmo(int count, bool UnloadChambered, bool CancelShooting, bool ManualCharge, const TArray<TSubclassOf<class AEBBullet>>& NewAmmo);
	UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable, Category = "Ammo") void Charge();
	UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable, Category = "Ammo") void UnloadChambered(bool ManualCharge);
	UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable, Category = "Shooting") void SwitchFireMode(EFireMode NewFireMode);
	UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable, Category = "Shooting") void GatlingSpool(bool Spool);
	UFUNCTION(BlueprintCallable, Category = "Shooting") void Shoot(bool Trigger, int nextEventFire);

	UFUNCTION(BlueprintCallable, Category = "Debug", meta = (ToolTip = "Trigger a simulated IMU shock for testing anti-recoil prediction. Requires DebugSimulateIMUShock to be enabled."))
		void TriggerDebugIMUShock();

	UFUNCTION(BlueprintCallable, meta = (AutoCreateRefTerm = "IgnoredActors"), Category = "Prediction") void PredictHit(bool& Hit, FHitResult& TraceResult, FVector& HitLocation, float& HitTime, AActor*& HitActor, TArray<FVector>& Trajectory, TSubclassOf<class AEBBullet> BulletClass, TArray<AActor*>IgnoredActors, float MaxTime = 10.0f, float Step = 0.1f) const;
	UFUNCTION(BlueprintCallable, meta = (AutoCreateRefTerm = "IgnoredActors"), Category = "Prediction") void PredictHitFromLocation(bool &Hit, FHitResult& TraceResult, FVector& HitLocation, float& HitTime, AActor*& HitActor, TArray<FVector>& Trajectory, TSubclassOf<class AEBBullet> BulletClass, FVector StartLocation, FVector AimDirection, TArray<AActor*>IgnoredActors, float MaxTime = 10.0f, float Step = 0.1f) const;
	UFUNCTION(BlueprintCallable, Category = "Prediction") void CalculateAimDirection(TSubclassOf<class AEBBullet> BulletClass, FVector TargetLocation, FVector TargetVelocity, FVector& AimDirection, FVector& PredictedTargetLocation, FVector& PredictedIntersectionLocation, float& PredictedFlightTime, float& Error, float MaxTime = 10.0f, float Step = 0.1f, int NumIterations = 4) const;
	UFUNCTION(BlueprintCallable, Category = "Prediction") void CalculateAimDirectionFromLocation(TSubclassOf<class AEBBullet> BulletClass, FVector StartLocation, FVector TargetLocation, FVector TargetVelocity, FVector& AimDirection, FVector& PredictedTargetLocation, FVector& PredictedIntersectionLocation, float& PredictedFlightTime, float& Error, float MaxTime = 10.0f, float Step=0.1f, int NumIterations = 4) const;
	
	UFUNCTION(BlueprintNativeEvent, Category = "Events") void InitialBulletTransform(FVector InLocation, FVector InDirection, FVector& OutLocation, FVector& OutDirection);
	UFUNCTION(BlueprintNativeEvent, Category = "Events") void ApplyRecoil(UPrimitiveComponent* Component, FVector InLocation, FVector Impulse);

	DECLARE_DYNAMIC_MULTICAST_DELEGATE(FBeforeShotFired);
	UPROPERTY(BlueprintAssignable, Category = "Events")
		FBeforeShotFired BeforeShotFired;

	DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FSpawnBulletEvent, FVector, Start, FVector, Velocity);
	UPROPERTY(BlueprintAssignable, Category = "Events")
		FSpawnBulletEvent SpawnBulletEvent;

	DECLARE_DYNAMIC_MULTICAST_DELEGATE(FShotFired);
	UPROPERTY(BlueprintAssignable, Category = "Events")
		FShotFired ShotFired;

	DECLARE_DYNAMIC_MULTICAST_DELEGATE(FAmmoDepleted);
	UPROPERTY(BlueprintAssignable, Category = "Events")
		FAmmoDepleted AmmoDepleted;

	DECLARE_DYNAMIC_MULTICAST_DELEGATE(FReadyToShoot);
	UPROPERTY(BlueprintAssignable, Category = "Events")
		FReadyToShoot ReadyToShoot;

#if WITH_EDITOR
	virtual FPrimitiveSceneProxy* CreateSceneProxy() override;
	virtual bool IsZeroExtent() const override { return false; };
	virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override;
#endif

private:
	void SpawnBullet(AActor* Owner, FVector LocalLocation, FVector LocalAim, int fireEventID);

	UFUNCTION(Server, Unreliable, WithValidation) void ClientAim(FVector_NetQuantize NewLocation, FVector_NetQuantizeNormal NewAim);
	UFUNCTION(Server, Reliable, WithValidation) void ShootRep(bool Trigger, int nextFireID);
	UFUNCTION(Server, Reliable, WithValidation) void ShootRepCSA(bool Trigger, FVector_NetQuantize NewLocation, FVector_NetQuantizeNormal NewAim, int nextFireID);

	UFUNCTION(NetMulticast, Reliable)
		void ShotFiredMulticast();

	UFUNCTION(NetMulticast, Reliable)
		void SpawnBulletEventMulticast(FVector Start, FVector Velocity);

	FVector Aim;
	FVector Location;

	TArray<FTimestampedTransform> TransformHistory;

	// Debug IMU shock simulation state
	double DebugIMUShockStartTime = 0.0;
	bool DebugIMUShockActive = false;
	FVector DebugIMUShockAimOffset = FVector::ZeroVector;
	FVector DebugIMUShockPosOffset = FVector::ZeroVector;

	// Debug yellow line persistence (shows uncorrected aim after shock)
	bool DebugIMUShockLineCaptured = false;
	double DebugIMUShockLineEndTime = 0.0;
	FVector DebugIMUShockCapturedLocation = FVector::ZeroVector;
	FVector DebugIMUShockCapturedAim = FVector::ForwardVector;

	// Kalman filter state
	FVector KalmanPosition = FVector::ZeroVector;
	FVector KalmanVelocity = FVector::ZeroVector;
	FVector KalmanAim = FVector::ForwardVector;
	FVector KalmanAngularVelocity = FVector::ZeroVector;
	float KalmanPosVariance = 1.0f;
	float KalmanVelVariance = 1.0f;
	float KalmanAimVariance = 1.0f;
	float KalmanAngVelVariance = 1.0f;
	bool KalmanInitialized = false;
	double KalmanLastTimestamp = 0.0;

	void UpdateTransformHistory();
	void ComputeAntiRecoilTransform();
	void PredictLinearExtrapolation(double CurrentTime, FVector& OutLocation, FVector& OutAim) const;
	void PredictWeightedRegression(double CurrentTime, FVector& OutLocation, FVector& OutAim) const;
	void UpdateKalmanFilter(double CurrentTime, const FVector& MeasuredLocation, const FVector& MeasuredAim);
	void PredictKalmanFilter(double CurrentTime, FVector& OutLocation, FVector& OutAim) const;

	bool RemoteAimReceived;
	float TimeSinceAimUpdate;
	bool PredictTrace(UWorld* World, AEBBullet* Bullet, FVector Start, FVector End, FHitResult &HitResult, TArray<AActor*> IgnoredActors) const;
};