/*
* 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.generated.h"

/// <summary>
/// <b>Chromosome:</b> A collection of values (weights) that can be manipulated and/or mated based 
/// on how well they solve a problem. The <b>GeneticAlgorithm</b> keeps track of these chromosomes
/// and modifies them. The higher the fitness, the better this chromosome performed at the task.
/// </summary>
UCLASS(config = Game)
class UChromosome : public UObject
{
	GENERATED_BODY()

protected:
	/// <summary>
	/// The score that describes how good this chromosome is at solving a problem.
	/// The higher the fitness score, the better.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	float myFitness;

	/// <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 = "General")
	float myCrossoverRate;

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

	/// <summary>
	/// A multiplier for adjusting how much a single gene is mutated.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	float myMutationMaxPerturbation;

	/// <summary>
	/// The collection of weights associated with this chromosome. Typically manipulated by <b>GeneticAlgorithm</b>
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	TArray<float> myWeights;

public:
	/// <summary>
	/// Default Constructor.
	/// </summary>
	UChromosome();

	/// <summary>
	/// Sets/Updates this chromosome's data with the provided data. This generates <c>numberOfWeights</c> weights.
	/// </summary>
	/// <param name="numberOfWeights">The total number of weights this chromosome will hold. (Generates random number of weights)</param>
	/// <param name="crossoverRate">A rate to determine if/where to cross over genes of another chromosome.</param>
	/// <param name="mutationRate">A rate to determine if/where a single weight will be modified slightly.</param>
	void Initialize(const int numberOfWeights, const float crossoverRate, const float mutationRate);

	/// <summary>
	/// Sets/Updates this chromosome's data with the provided data. This generates <c>numberOfWeights</c> weights.
	/// </summary>
	/// <param name="weights">The collection of weights to use for this chromosome.</param>
	/// <param name="fitness">The fitness score of this chromosome.</param>
	/// <param name="crossoverRate">A rate to determine if/where to cross over genes of another chromosome.</param>
	/// <param name="mutationRate">A rate to determine if/where a single weight will be modified slightly.</param>
	void Initialize(const TArray<float>& weights, const float fitness, const float crossoverRate, const float mutationRate);

	/// <summary>
	/// Add a weight to this chromosome's weight collection.
	/// </summary>
	/// <param name="weight">The weight value to add.</param>
	void AddWeight(const float weight);

	/// <summary>
	/// Removes all weights from this chromosome's weight collection.
	/// </summary>
	void ClearWeights();

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

	/// <summary>
	/// Attempts to "mate" or crossover the genes of two chromosomes to form two new babies, 
	/// This allows for the neural network to naturally grow and attempt to solve the problem 
	/// with previous generations of chromosomes but with possible improvements. 
	/// </summary>
	/// <param name="thisPartner">This chromosome.</param>
	/// <param name="otherPartner">The other chromosome you want to cross weights with.</param>
	/// <param name="outBabyOne">One baby is produced with one swap.</param>
	/// <param name="outBabyTwo">The second baby is produced with the opposite swap.</param>
	/// <returns>true if new babies were produced, false to just re-use the original parents.</returns>
	bool Crossover(UChromosome* thisPartner, UChromosome* otherPartner, UChromosome* outBabyOne, UChromosome* outBabyTwo);

	/// <summary>
	/// Attempts to perturb or slightly change a single weight based on the mutation rate.
	/// </summary>
	void Mutate();

	/// <summary>
	/// Allows us to use the std::sort function to sort by fitness score.
	/// </summary>
	/// <param name="otherChromosome">The other chromosome you are comparing.</param>
	/// <returns>true if this chromosome has a lower score then the other, false otherwise.</returns>
	bool operator<(const UChromosome& otherChromosome);

	/// <summary>
	/// Compares two chromosomes to see if they are equal based on weight values.
	/// </summary>
	/// <param name="otherChromosome">The other chromosome you are comparing.</param>
	/// <returns>true if all the weights of the two chromosomes are equal, false otherwise.</returns>
	bool GetIsEqual(const UChromosome* otherChromosome) const;

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

	/// <summary>
	/// Returns the crossover rate of this chromosome.
	/// </summary>
	/// <returns>The crossover rate of this chromosome.</returns>
	float GetCrossoverRate() const;

	/// <summary>
	/// Returns the mutation rate of this chromosome.
	/// </summary>
	/// <returns>The mutation rate of this chromosome.</returns>
	float GetMutationRate() const;

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

	/// <summary>
	/// Returns a single weight at a specified index.
	/// </summary>
	/// <param name="index">The specific index of the weight.</param>
	/// <returns>The weight value at the provided index, -1.0 and throws exception if not found.</returns>
	float GetWeightAtIndex(const int index) const;

	/// <summary>
	/// Returns the weight collection.
	/// </summary>
	/// <returns>A reference to the weight collection.</returns>
	TArray<float>& GetWeights();

	/// <summary>
	/// Updates the fitness score with the provided value.
	/// </summary>
	/// <param name="fitness">The updated fitness score.</param>
	void SetFitness(const float& fitness);

	/// <summary>
	/// Updates the crossover rate with the provided value.
	/// </summary>
	/// <param name="crossoverRate">The updated crossover rate.</param>
	void SetCrossoverRate(const float& crossoverRate);

	/// <summary>
	/// Updates the crossover rate with the provided value.
	/// </summary>
	/// <param name="mutationRate">The updated crossover rate.</param>
	void SetMutationRate(const float mutationRate);

	/// <summary>
	/// Clears the current collection of weights and updates the collection with 
	/// the provided weights vector.
	/// </summary>
	/// <param name="weights">The new weights to use.</param>
	void SetWeights(TArray<float> weights);
};