/*
* 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 "GameFramework/GameModeBase.h"
#include "Pawns/AITankPawn.h"
#include "Items/TankAmmunitionItem.h"
#include "NeuralNetworking/GeneticAlgorithm.h"
#include "Gameplay/TankProjectile.h"
#include "NeuralTanksGameMode.generated.h"

class UNeuralTanksCheatManager;
class APlayerTankPawn;

DECLARE_MULTICAST_DELEGATE_OneParam(FOnRoundEndedSignature, bool);

/// <summary>
/// The Neural Tanks Game Mode is responsible for running and managing the game round.
/// In our case, each round starts when the level starts, and ends if the player dies
/// or the player beats the level.
/// 
/// This class is also responsible for running and managing the Neural Networks, triggering
/// the reproduction process, and incrementing the generation count.
/// </summary>
UCLASS()
class ANeuralTanksGameMode : public AGameModeBase
{
	GENERATED_BODY()

protected:
	/// <summary>
	/// true if the game mode has finished initializing all the Tanks and TankAmmunition, false otherwise.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	bool myIsInitialized;

	/// <summary>
	/// true when the player has destroyed all the tanks in the level, false otherwise.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	bool myHasRoundEnded;

	/// <summary>
	/// The Number of seconds per generation.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Neural Network")
	float myNumSecondsPerGeneration;

	/// <summary>
	/// The accumulated seconds.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Neural Network")
	float myCurrSecondsInGeneration;

	/// <summary>
	/// The current generation.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Neural Network")
	int myGenerationCounter;

	/// <summary>
	/// The total number of Tanks in the world.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	int myNumTanks;

	/// <summary>
	/// The total number of TankAmmunition in the world.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	int myNumTankAmmunitions;

	/// <summary>
	/// The number of weights in the entire Neural Network.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Neural Network")
	int myTotalNumWeightsInNeuralNetwork;

	/// <summary>
	/// The velocity to use for the player's projectile.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	float myPlayerProjectileVelocity;

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

	/// <summary>
	/// The smallest possible position the Tanks can move to.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	FVector myMinWorldBoundsPosition;

	/// <summary>
	/// The largest possible position the Tanks can move to.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	FVector myMaxWorldBoundsPosition;

	/// <summary>
	/// The largest possible Y value the Tanks can move to.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	float myMaxWorldBoundsYPosition;

	/// <summary>
	/// A rate to determine if/where in the weights collection we separate this chromosome.
	/// This is used for mating; we take weights of one parent and cross them at the chosen index
	/// with the other parent.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Neural Network")
	float myCrossoverRate;

	/// <summary>
	/// A rate to determine if/where we mutate a single weight by adding a slight value to it.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Neural Network")
	float myMutationRate;

	/// <summary>
	/// A pointer to the Genetic Algorithm.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Neural Network")
	UGeneticAlgorithm* myGeneticAlgorithm;

	/// <summary>
	/// A reference to the player pawn in the world. Used for monitoring health of tank
	/// and detecting game over.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Internal")
	APlayerTankPawn* myPlayerPawn;

	/// <summary>
	/// Stores the average fitnesses per generation for use in statistic graphing.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Internal")
	TArray<float> myAverageFitnesses;

	/// <summary>
	/// Stores the best fitnesses per generation for use in statistic graphing.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Internal")
	TArray<float> myBestFitnesses;

	/// <summary>
	/// The collection of TankAmmunition in the world.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Internal")
	TArray<ATankAmmunitionItem*> myTankAmmunitions;

	/// <summary>
	/// The collection of Tanks in the world.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Internal")
	TArray<AAITankPawn*> myTanks;

public:
	/// <summary>
	/// Public event that can be registered to know if/when the round has ended
	/// (and if the player won or not).
	/// </summary>
	FOnRoundEndedSignature OnRoundEnded_Event;

	/// <summary>
	/// Default constructor.
	/// </summary>
	ANeuralTanksGameMode();

	/// <summary>
	/// Called when the round has ended but on the blueprint side. 
	/// This is used for updating the UI based on if the player won/lost.
	/// </summary>
	/// <param name="hasPlayerWon">true if the player has won, false if they died.</param>
	UFUNCTION(BlueprintImplementableEvent, Category = "Events")
	void OnRoundEndedBlueprint(bool hasPlayerWon);

	/// <summary>
	/// Called when the level first loads. This triggers the initialize function after 5 seconds.
	/// We want to give the player a few seconds to get their bearings. 
	/// </summary>
	virtual void BeginPlay() override;

	/// <summary>
	/// Initializes all of the enemy tanks in the level and their corresponding Neural Networks.
	/// Also records the total number of tank ammunitions and the min/max level bounds.
	/// </summary>
	UFUNCTION()
	void Initialize();

	/// <summary>
	/// Called once per frame. This checks to see if the current generation is done and
	/// the tanks are ready for reproduction. When this happens, the generation is incremented
	/// and the genetic algorithm calculates the new/updated weights for the Neural Networks.
	/// </summary>
	/// <param name="deltaTime">Time in milliseconds between frames.</param>
	virtual void Tick(float deltaTime) override;

	/// <summary>
	/// Checks to see if all of the enemy tanks are destroyed or
	/// the player is killed.
	/// </summary>
	void CheckForEndOfRound();

	/// <summary>
	/// Prints the Neural Network statistics to the log.
	/// (This is only used for debugging and doesn't run in shipped builds).
	/// </summary>
	void PrintStatistics();

	/// <summary>
	/// Checks the fitness for all the tanks in the collection and if the fitness
	/// of a particular tank is below zero (negative) it is removed from the collection.
	/// </summary>
	void RemoveDeadTanks();

	/// <summary>
	/// Called when this round has ended for some reason.
	/// </summary>
	/// <param name="hasPlayerWon">true if the player destroyed all the enemy tanks, false if the player died.</param>
	void RoundEnded(bool hasPlayerWon);
	
	/// <summary>
	/// Returns true if the enemy tanks and level is initialized, false otherwise.
	/// </summary>
	/// <returns>true if the enemy tanks and level is initialized, false otherwise.</returns>
	bool GetIsInitialized();

	/// <summary>
	/// Returns the current generation number of the Neural Network.
	/// </summary>
	/// <returns>The current generation number of the Neural Network.</returns>
	int GetCurrentGeneration();

	/// <summary>
	/// Returns the average calculated fitness of the Neural Network.
	/// </summary>
	/// <returns>The average calculated fitness of the Neural Network.</returns>
	float GetAverageFitness();

	/// <summary>
	/// Returns the best calculated fitness in the Neural Network.
	/// </summary>
	/// <returns>The best calculated fitness in the Neural Network.</returns>
	float GetBestFitness();
};