/*
* 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 "Chromosome.h"
#include "../Pawns/AITankPawn.h"
#include "GeneticAlgorithm.generated.h"

/// <summary>
/// <b>GeneticAlgorithm:</b> The Genetic Algorithm is responsible for crossing over and mutating the weights
/// in the Tank's Chromosome. This allows the chromosomes to change and evolve overtime. If the fitness
/// for a particular chromosome is higher than the others, it will have a better chance of being chosen for
/// crossover. This allows the data in the chromosomes to improve overtime. The Genetic Algorithm will also 
/// sometimes manipulate a single weight, this is similar to how mutations can happen in real world evolution
/// which sometimes is a huge benefit.
/// </summary>
UCLASS(config = Game)
class UGeneticAlgorithm : public UObject
{
	GENERATED_BODY()

protected:
	/// <summary>
	/// The accumulated sum of all the Tank's Chromosome's fitness score.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	float myTotalFitness;

	/// <summary>
	/// The highest value fitness score in this current generation.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	float myBestFitness;

	/// <summary>
	/// The average of all the fitness scores in this current generation.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	float myAverageFitness;

	/// <summary>
	/// The lowest value fitness score in this current generation.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	float myWorstFitness;

	/// <summary>
	/// The rate at if/where in the chromosome a crossover will happen. 
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	float myCrossoverRate;

	/// <summary>
	/// The rate at if/where a single weight in the Chromosome will be perturbed. 
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	float myMutationRate;

	/// <summary>
	/// The current stage/generation/level the Chromosomes are at.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	int myCurrentGeneration;
	
	/// <summary>
	/// The total number of Tanks.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	int myPopulationSize;

	/// <summary>
	/// The number of weights in the chromosome.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	int myChromosomeLength;

	/// <summary>
	/// The index in the Tank collection of the best performing Chromosome.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	int myBestChromosomeIndex;
	
public:
	/// <summary>
	/// Default constructor.
	/// </summary>
	UGeneticAlgorithm();

	/// <summary>
	/// Parameterized constructor. Initializes the chromosomes for the provided Tanks.
	/// </summary>
	/// <param name="populationSize">The number of Tanks/Chromosomes in the population.</param>
	/// <param name="crossoverRate">The crossover rate assigned to the Chromosomes.</param>
	/// <param name="mutationRate">The mutation rate assigned to the Chromosomes.</param>
	/// <param name="chromosomeLength">The number of weights in a single Chromosomes.</param>
	/// <param name="tanks">The collection of Tanks that each have their own Chromosome.</param>
	void Initialize(const int populationSize, const float crossoverRate, const float mutationRate, const int chromosomeLength, TArray<AAITankPawn*>& tanks);

	/// <summary>
	/// Calculates the best fitness, the worst fitness, and the average fitness for this generation
	/// of Chromosomes.
	/// </summary>
	void CalculateStatistics(TArray<AAITankPawn*>& tanks);

	/// <summary>
	/// Resets the total fitness, best fitness, and average fitness to 0.
	/// Also resets the worst fitness to max (9999999).
	/// </summary>
	void ResetAlgorithm();

	/// <summary>
	/// Returns the index of a randomly selected Tank/Chromosome based on its fitness. 
	/// The way this function selects a chromosome is similar to a roulette wheel. Depending 
	/// on the fitness, (the higher the better) the Tank/Chromosome has a better chance 
	/// of being picked. This allows for Tanks/Chromosomes that have performed well to 
	/// have a better chance of reproducing and being picked.
	/// </summary>
	/// <returns>The index of the randomly selected Tank/Chromosome.</returns>
	int RoulettePickChromosome(TArray<AAITankPawn*>& tanks);

	/// <summary>
	/// Called once a frame to update/run the Genetic Algorithm.
	/// </summary>
	void Update(float deltaTime, TArray<AAITankPawn*>& tanks);

	/// <summary>
	/// Returns the average fitness in the current generation.
	/// </summary>
	/// <returns>The average fitness in the current generation.</returns>
	float GetAverageFitness();

	/// <summary>
	/// Returns the best fitness in the current generation.
	/// </summary>
	/// <returns>The best fitness in the current generation.</returns>
	float GetBestFitness();

	/// <summary>
	/// Returns the Chromosome reference at the specified index.
	/// </summary>
	/// <param name="tankIndex">The index in the Tank/Chromosome collection.</param>
	/// <param name="tanks">The collection of Tanks to search.</param>
	/// <returns>The corresponding Chromosome from the collection based on the provided tankIndex.</returns>
	UChromosome* GetChromosomeAtIndex(const int tankIndex, TArray<AAITankPawn*>& tanks);

	/// <summary>
	/// Updates the fitness value for a specified Chromosome in the collection.
	/// </summary>
	/// <param name="chromosomeIndex">The index of the Tank/Chromosome in the collection.</param>
	/// <param name="fitnessForChromosome">The new fitness of the Chromosome.</param>
	void SetFitnessForChromosome(const int chromosomeIndex, const float fitnessForChromosome, TArray<AAITankPawn*>& tanks);

};