/*
* Neural Tanks is a game created by Eddie O'Hagan that leverages Neural Networking
* to use for AI. This was created in Unreal Engine 4 which is developed by Epic Games.
* (Copyright Epic Games, Inc. All Rights Reserved.)
*
* Smart Tanks was originally developed by Mat Buckland in 2002 as an example
* on how Neural Networking can be used to train a set of minesweepers
* to collect mines. I (Eddie O'Hagan) updated this project in 2024 to use more
* Object Oriented Programming and modern practices. To see the original tutorial;
* visit <see href="http://www.ai-junkie.com/ann/evolved/nnt1.html">Neural Networking Tutorial</see>
* and <see href="http://www.ai-junkie.com/ga/intro/gat1.html">Genetic Algorithm Tutorial</see>
*/
#pragma once
#include <BehaviorTree/BehaviorTree.h>
#include "../Items/TankAmmunitionItem.h"
#include "TankPawn.h"
#include "../NeuralNetworking/Chromosome.h"
#include "../NeuralNetworking/NeuralNetwork.h"
#include "AITankPawn.generated.h"

/// <summary>
/// <c>AAITankPawn</c> is a single Tank that uses neural networking to collect TankAmmunitions in the game world.
/// I originally had this class inherit from ATankPawn, but the problem is, having more that two AI tanks 
/// with full physics simulation running (like the player tank) the game lags severally. That is why
/// these tanks are bare bones and can be built up upon depending on your needs. 
/// </summary>
UCLASS(config=Game)
class AAITankPawn : public ATankPawn
{
	GENERATED_BODY()

protected:
	/// <summary>
	/// true if this tank is initialized and ready to move, false otherwise.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	bool myIsInitialized;

	/// <summary>
	/// The number of tank bullets available for this AI tank to fire. Increases by 1 when this tank
	/// picks up a TankAmmunition. Decreases by 1 when this tank fires its cannon.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	int myNumOfReadyTankAmmo;

	/// <summary>
	/// The array index (in the TankAmmunition collection) of the closest TankAmmunition.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	int myClosestTankAmmunitionIndex;

	/// <summary>
	/// The number of shots before triggering the AI to react.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	int myNumShotsSinceLastHitThreshold;

	/// <summary>
	/// The number of shots since the last successful tank hit.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	int myNumShotsSinceLastHit;

	/// <summary>
	/// The multiplier used for the AI tanks speed.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	float mySpeedMultiplier;

	/// <summary>
	/// The rotation angle of this Tank.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	float myRotation;

	/// <summary>
	/// The amount of input to apply to the left track.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	float myLeftTrackInput;

	/// <summary>
	/// The amount of input to apply to the right track.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	float myRightTrackInput;

	/// <summary>
	/// The maximum rate the tank can turn per frame.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	float myMaxTurnRate;

	/// <summary>
	/// Describes how efficient this Tank is at collecting TankAmmunition.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	float myFitnessScore;

	/// <summary>
	/// The smallest possible X value for this tank's location.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	float myMinXPosition;

	/// <summary>
	/// The smallest possible Y value for this tank's location.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	float myMinYPosition;

	/// <summary>
	/// The largest possible X value for this tank's location.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	float myMaxXPosition;

	/// <summary>
	/// The largest possible Y value for this tank's location.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	float myMaxYPosition;

	/// <summary>
	/// The amount of extra space to add/subtract before detecting the min/max position of the level. 
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	float myPositionBuffer;

	/// <summary>
	/// The direction this tank is currently facing.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	FVector myLookAtVector;

	/// <summary>
	/// The chromosome belonging to this Tank.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Internal")
	UChromosome* myChromosome;

	/// <summary>
	/// The "Brain" of this Tank.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	UBehaviorTree* myBehaviorTree;

	/// <summary>
	/// The Neural Network portion of the "Brain".
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Internal")
	UNeuralNetwork* myNeuralNetwork;

	/// <summary>
	/// A reference to this actor's root static mesh component, used during normal operation.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Internal")
	UStaticMeshComponent* myAIStaticMeshComponent;

	/// <summary>
	/// A reference to this actor's destroyed tank static mesh component, used when this tank is destroyed.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Internal")
	UStaticMeshComponent* myAIDestroyedStaticMeshComponent;

	/// <summary>
	/// A collection of all the tank ammunitions in the level.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Internal")
	TArray<ATankAmmunitionItem*> myAllTankAmmunitions;

public:
	/// <summary>
	/// The Default constructor for the AI Tank Pawn. Enables tick and sets
	/// default values.
	/// </summary>
	AAITankPawn();

	/// <summary>
	/// Called before Begin Play. Finds the Static Mesh component and Destroyed static mesh component.
	/// </summary>
	/// <param name="transform">The transform the actor was constructed at.</param>
	virtual void OnConstruction(const FTransform& transform) override;

	/// <summary>
	/// Called when this AI Tank Pawn first loads into the level. Creates the Neural Network 
	/// and sets this AI Tank to an "alive" state.
	/// </summary>
	virtual void BeginPlay() override;

	/// <summary>
	/// Starts the tank and sets its world/level bounds and its turn rate. In addition to this, ammunition for the tank 
	/// can be provided.
	/// 
	/// If the tank goes past the bounds, it is simply moved to the other side (it wraps around like in pac-man).
	/// </summary>
	/// <param name="maxTurnRate">The fastest rate that the AI Tank can turn.</param>
	/// <param name="minXLocation">The lower X bounds of where this AI Tank can exist in the world.</param>
	/// <param name="minYLocation">The lower Y bounds of where this AI Tank can exist in the world.</param>
	/// <param name="maxXLocation">The upper X bounds of where this AI Tank can exist in the world.</param>
	/// <param name="maxYLocation">The upper Y bounds of where this AI Tank can exist in the world.</param>
	/// <param name="tankAmmunitions">The ammunition for the tank to use.</param>
	void Initialize(const float maxTurnRate, const float minXLocation, const float minYLocation, const float maxXLocation, const float maxYLocation, TArray<ATankAmmunitionItem*>& tankAmmunitions);
	
	/// <summary>
	/// Called when this AI Tank collides with an item in the world. In the AI's case specifically,
	/// we increase the fitness of this AI's neural network every time it finds a Tank Ammunition.
	/// </summary>
	/// <param name="theItem">The item that this AI Tank collided/overlapped with.</param>
	virtual void OnTankOverlappedItem(ATankItem* theItem) override;

	/// <summary>
	/// Called when this AI Tank is hit by projectile. This includes player
	/// and AI projectiles (Friendly Fire is possible).
	/// </summary>
	/// <param name="theAttackingPawn">The Tank that landed the hit on this AI Tank.</param>
	/// <param name="damage">The amount of damage applied to this AI Tank.</param>
	virtual void OnTankHit(ATankPawn* theAttackingPawn, float damage) override;

	/// <summary>
	/// Called when this AI Tank successfully hits another Tank. This is used in 
	/// the advanced boss AI where if the boss (Sassy Sam) does not hit the player 
	/// after shooting a certain number times. This allows the boss to know it needs
	/// to relocate in-order to get a better shot.
	/// </summary>
	/// <param name="theHitTankPawn">The Tank this AI Tank successfully hit.</param>
	virtual void OnSuccessfullyHitTank(ATankPawn* theHitTankPawn) override;

	/// <summary>
	/// Spawns and fires a projectile from the cannon. Also decrements the amount of 
	/// ammunition this AI Tank has.
	/// </summary>
	/// <param name="cannonReloadTime">The amount of time in seconds before this AI Tank can fire again.</param>
	/// <returns>true if a projectile was spawned and fired, false otherwise.</returns>
	virtual bool FireCannon(const float cannonReloadTime) override;

	/// <summary>
	/// Copies the provided chromosome's data (and weights) to this tank's chromosome.
	/// </summary>
	/// <param name="chromosomeToCopy">The chromosome to copy data from.</param>
	void CopyChromosomeData(UChromosome* chromosomeToCopy);

	/// <summary>
	/// Calculates the distance to all the TankAmmunitions and returns the location of the closest one.
	/// This is used for AI navigation.
	/// </summary>
	/// <param name="tankAmmunitions">A collection of all the tank ammunition in the level/world.</param>
	/// <returns>The smallest calculated distance found.</returns>
	FVector FindClosestTankAmmunition(TArray<ATankAmmunitionItem*>& tankAmmunitions);

	/// <summary>
	/// Increments the fitness score of this AAITankPawn.
	/// </summary>
	void IncrementFitness();

	/// <summary>
	/// Zeros out the current fitness score and resets the static mesh.
	/// </summary>
	void ResetTank();

	/// <summary>
	/// Destroys the tank rendering it immobile and no longer used in the Neural Network.
	/// </summary>
	UFUNCTION(BlueprintCallable, Category = "AITankPawn")
	void DestroyTank();

	/// <summary>
	///	First we take sensor readings and feed these into the AI Tank's brain
	/// (which is the Neural Network).
	///
	///	The inputs are:
	///		1. A vector to the closest TankAmmunition (X)
	///		2. A vector to the closest TankAmmunition (Y)
	///		3. The AI Tank's current 'look at' vector (X)
	///		4. The AI Tank's current 'look at' vector (Y)
	/// 
	///	We receive two outputs from the brain, the lTrack and the rTrack values.
	///	So given a force for each track we calculate the resultant rotation 
	///	and acceleration and apply to current velocity vector. In other words;
	/// we use the lTrack output for how much force to turn the tank left, 
	/// and the rTrack output for how much force to turn the tank right.
	/// </summary>
	/// <returns>true if no errors occurred, false otherwise.</returns>
	bool ProcessNeuralNetwork();

	/// <summary>
	/// Called in the case where the AI Tanks has missed a number of shots 
	/// and is now attempting to reposition.
	/// </summary>
	UFUNCTION(BlueprintImplementableEvent)
	void AttemptToReposition();

	/// <summary>
	/// Used for sorting the Tanks based on its fitness score.
	/// </summary>
	/// <param name="otherTank">The other Tank to compare against this Tank.</param>
	/// <returns>true if this Tank's fitness is less than the otherTank, false otherwise.</returns>
	bool operator<(const AAITankPawn& otherTank) const;

	/// <summary>
	/// Returns the total number of weights in the entire Neural Network.
	/// </summary>
	/// <returns>The total number of weights in the entire Neural Network.</returns>
	int GetNumberOfWeights() const;

	/// <summary>
	/// Returns the fitness score of this Tank's chromosome. This is usually equal to 
	/// the fitness score of the Tank itself. 
	/// </summary>
	/// <returns>The fitness score of this Tank's chromosome</returns>
	float GetChromosomeFitness() const;

	/// <summary>
	/// Returns the fitness score of this Tank.
	/// </summary>
	/// <returns>The fitness score of this Tank.</returns>
	float GetFitness() const;

	/// <summary>
	/// Returns the x,y position of this Tank.
	/// </summary>
	/// <returns>The x,y position of this Tank.</returns>
	FVector GetPosition() const;

	/// <summary>
	/// Returns the chromosome of this Tank.
	/// </summary>
	/// <returns>The chromosome of this Tank.</returns>
	UChromosome* GetChromosome();

	/// <summary>
	/// Returns the behavior tree for this Tank.
	/// </summary>
	/// <returns>The behavior tree for this Tank.</returns>
	UBehaviorTree* GetBehaviorTree();

	/// <summary>
	/// Sets the fitness for this Tank's Neural Network.
	/// </summary>
	/// <param name="fitness">The new fitness for this Tank's Neural Network.</param>
	void SetFitness(const float fitness);

	/// <summary>
	/// Sets the rotation angle (in radians) for this Tank.
	/// </summary>
	/// <param name="angleInRadians">The new rotation angle (in radians) for this Tank.</param>
	void SetTankRotation(const float angleInRadians);

	/// <summary>
	/// Sets the weights for this Tank's Neural Network.
	/// </summary>
	/// <param name="weights">The new weights for this Tank's Neural Network.</param>
	void SetWeights(TArray<float>& weights);

	/// <summary>
	/// Sets the chromosome for this Tank.
	/// </summary>
	/// <param name="chromosome">The new chromosome for this Tank.</param>
	void SetChromosome(UChromosome* chromosome);

	/// <summary>
	/// Directly sets the 3D world/level position of this AI Tank with respect to the terrain and
	/// the Neural Network. Since the AI Tank doesn't use gravity, we set its Z location component
	/// to the height of where this Tank is located (its X,Y location) on the landscape. 
	/// </summary>
	/// <param name="theLocation">The X,Y world/level location to set the AI tank to.</param>
	/// <param name="theRotationAngle">The angle in radians to rotate this tank to.</param>
	/// <param name="theSpeed">The speed to use for moving the tank.</param>
	void SetTankLocationAndRotation(const FVector& theLocation, const float theRotationAngle, const float theSpeed);
};

