/*
* 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 "Math/BigInt.h"
#include "GameFramework/SaveGame.h"
#include "../Items/TankItem.h"
#include "TankItemData.h"
#include "PlayerProgressionSaveGame.generated.h"

/// <summary>
/// The <c>UPlayerProgressionSaveGame</c> is the save file that is used for recording,
/// managing, and saving/loading the game data such as player money, levels completed,
/// etc.
/// </summary>
UCLASS(config = Game)
class UPlayerProgressionSaveGame : public USaveGame
{
	GENERATED_BODY()

protected:
	/// <summary>
	/// True if this save game is about to be saved as an auto-save or not.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General")
	bool myIsAutoSave;

	/// <summary>
	/// The display name of the save file to use in the save/load menu(s).
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "General")
	FName mySaveFileName;

	/// <summary>
	/// The date and time of the last time this was saved.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "General")
	FDateTime myDateOfSave;

	/// <summary>
	/// The amount of tank ammunition the player has obtained for this round.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Progression")
	int myNumTankAmmunitionObtained;

	/// <summary>
	/// The number of enemy tanks the player has destroyed this round.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Progression")
	int myNumTanksDestroyed;

	/// <summary>
	/// The total amount of tank ammunition the player has obtained for this save file.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "Progression")
	int myTotalNumOfTankAmmunitionObtained;

	/// <summary>
	/// The total number of tanks destroyed by the player for this save file.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "Progression")
	int myTotalNumTanksDestroyed;

	/// <summary>
	/// The amount of money the player has.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "Progression")
	int myAmountOfMoney;

	/// <summary>
	/// The number of levels completed (for keeping track of what levels are unlocked).
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "Progression")
	int myNumLevelsCompleted;

	/// <summary>
	/// The number of items currently in the inventory (needed specifically for serialization).
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "Inventory")
	int myNumInventoryItems;

	/// <summary>
	/// The number of accessories currently in the accessory inventory.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "Accessories")
	int myNumAccessoryItems;

	/// <summary>
	/// The number of items currently equipped (needed specifically for serialization).
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "Equipment")
	int myNumEquippedItems;

	/// <summary>
	/// The collection of items the player has.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
	TArray<UTankItemData*> myInventory;

	/// <summary>
	/// The collection of accessory items the player has.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Accessories")
	TArray<UTankItemData*> myAccessories;

	/// <summary>
	/// The collection of armor the player has equipped. This is similar to the inventory and stored
	/// the same way, however, they are septate so avoid duplication and confusion.
	/// </summary>
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Equipment")
	TArray<UTankItemData*> myEquippedItems;

public:
	/// <summary>
	/// The serialized raw data of this object (used for saving and loading).
	/// </summary>
	UPROPERTY()
	TArray<uint8> ByteData;

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

	/// <summary>
	/// Called when the player has beaten the level.
	/// </summary>
	/// <param name="ownerWorld">The world pointer to the level that was just beaten.</param>
	void OnRoundWon(UWorld* ownerWorld);

	/// <summary>
	/// Called if the player dies (loses the level).
	/// </summary>
	/// <param name="ownerWorld">The world pointer to the level that the player died in.</param>
	void OnRoundLost(UWorld* ownerWorld);

	/// <summary>
	/// Saves or loads this Player Progression Save Game's data such as inventory,
	/// equipment, and accessories.
	/// </summary>
	/// <param name="Ar">The archive to either save this data to or load this data from.</param>
	virtual void Serialize(FArchive& Ar) override;

	/// <summary>
	/// Adds the specified amount of money to the player's inventory.
	/// </summary>
	/// <param name="amountOfMoneyToAdd">The amount of money to give the player.</param>
	UFUNCTION(BlueprintCallable)
	void AddMoney(const int amountOfMoneyToAdd);

	/// <summary>
	/// Subtracts the specified amount of money from the player's inventory.
	/// </summary>
	/// <param name="amountOfMoneyToSubtract">The amount of money to take from player.</param>
	UFUNCTION(BlueprintCallable)
	void SubtractMoney(const int amountOfMoneyToSubtract);

	/// <summary>
	/// Increments the total number of tank ammunition the player collected in
	/// the levels.
	/// </summary>
	UFUNCTION(BlueprintCallable)
	void IncrementNumberOfTankAmmunitionObtained();

	/// <summary>
	/// Increments the total number of enemy tanks this player has destroyed.
	/// </summary>
	UFUNCTION(BlueprintCallable)
	void IncrementNumberOfTanksDestroyed();

	/// <summary>
	/// Adds the provided item actor to the player's inventory.
	/// </summary>
	/// <param name="itemToAddToInventory">The item to give the player.</param>
	/// <returns>true if successful, false otherwise.</returns>
	bool AddItemToInventory(ATankItem* itemToAddToInventory);

	/// <summary>
	/// Adds the provided item to the player's inventory.
	/// </summary>
	/// <param name="itemToAddToInventory">The item to give to the player.</param>
	/// <returns>true if successful, false otherwise.</returns>
	UFUNCTION(BlueprintCallable)
	bool AddItemToInventory(UTankItemData* itemToAddToInventory);

	/// <summary>
	/// Some achievements are directly tied to what is in the player's inventory
	/// and that is why we do this check.
	/// </summary>
	void CheckForPossibleAchievement();

	/// <summary>
	/// Removes the specified item from the player's inventory.
	/// </summary>
	/// <param name="item">The item to take away from the player.</param>
	/// <returns>true if successful, false otherwise.</returns>
	UFUNCTION(BlueprintCallable)
	bool RemoveItemFromInventory(UTankItemData* item);

	/// <summary>
	/// Removes the corresponding item from the players inventory.
	/// </summary>
	/// <param name="itemName">The name of the item to remove from the inventory.</param>
	/// <returns>true if successful, false otherwise.</returns>
	bool RemoveItemFromInventory(const FString& itemName);

	/// <summary>
	/// Removes the corresponding item from the inventory based on provided
	/// inventory array index.
	/// </summary>
	/// <param name="index">The inventory array index to remove.</param>
	/// <returns>true if successful, false otherwise.</returns>
	bool RemoveItemFromInventory(const int index);
	
	/// <summary>
	/// Returns the item in the player's inventory corresponding to the provided
	/// <c>itemName</c>.
	/// </summary>
	/// <param name="itemName">The name of the item to look for.</param>
	/// <returns>The corresponding item if found, NULL otherwise.</returns>
	UTankItemData* FindItemFromInventory(const FString& itemName);

	/// <summary>
	/// Returns the item in the player's inventory array based on the provided index.
	/// </summary>
	/// <param name="index">The inventory array index.</param>
	/// <returns>The item at the specified array index, NULL otherwise.</returns>
	UTankItemData* FindItemFromInventory(const int index);

	/// <summary>
	/// Returns the item in the player's accessories corresponding to the provided
	/// <c>itemName</c>.
	/// </summary>
	/// <param name="itemName">The name of the item to look for.</param>
	/// <returns>The corresponding item if found, NULL otherwise.</returns>
	UTankItemData* FindItemFromAccessories(const FString& itemName);

	/// <summary>
	/// Returns the item in the player's accessories array based on the provided index.
	/// </summary>
	/// <param name="index">The accessories array index.</param>
	/// <returns>The item at the specified array index, NULL otherwise.</returns>
	UTankItemData* FindItemFromAccessories(const int index);

	/// <summary>
	/// Returns the item in the player's equipment corresponding to the provided
	/// <c>itemName</c>.
	/// </summary>
	/// <param name="itemName">The name of the item to look for.</param>
	/// <returns>The corresponding item if found, NULL otherwise.</returns>
	UTankItemData* FindItemFromEquipment(const FString& index);

	/// <summary>
	/// Returns the item in the player's equipment array based on the provided index.
	/// </summary>
	/// <param name="index">The equipment array index.</param>
	/// <returns>The item at the specified array index, NULL otherwise.</returns>
	UTankItemData* FindItemFromEquipment(const int index);

	/// <summary>
	/// Returns the inventory array index of the corresponding item based on the provided
	/// <c>itemName</c>.
	/// </summary>
	/// <param name="itemName">The name of the item to look for in the inventory.</param>
	/// <returns>The inventory array index of the corresponding item if it exists, -1 otherwise.</returns>
	int FindIndexOfItemInInventory(const FString& itemName);

	/// <summary>
	/// Returns the accessories array index of the corresponding item based on the provided
	/// <c>itemName</c>.
	/// </summary>
	/// <param name="itemName">The name of the item to look for in the accessories.</param>
	/// <returns>The accessories array index of the corresponding item if it exists, -1 otherwise.</returns>
	int FindIndexOfItemInAccessories(const FString& itemName);

	/// <summary>
	/// Returns the equipment array index of the corresponding item based on the provided
	/// <c>itemName</c>.
	/// </summary>
	/// <param name="itemName">The name of the item to look for in the equipment.</param>
	/// <returns>The equipment array index of the corresponding item if it exists, -1 otherwise.</returns>
	int FindIndexOfItemInEquipment(const FString& itemName);

	/// <summary>
	/// Returns true if the corresponding item based on the provided <c>itemName</c>
	/// exists in either the accessories array, equipment array, or the inventory 
	/// array. Returns false if the item does not exist in any of those arrays.
	/// </summary>
	/// <param name="itemName">The name of the item to look for.</param>
	/// <returns>True if the corresponding item exists in the accessories array, equipment array, or inventory array. False if the item is not found.</returns>
	UFUNCTION(BlueprintCallable)
	bool DoesPlayerHaveItem(const FString& itemName);

	/// <summary>
	/// Returns true if the provided item exists in the player's inventory. 
	/// This uses the provided <c>itemToCheck</c>'s item name to search.
	/// </summary>
	/// <param name="itemToCheck">The item to search for in the player's inventory.</param>
	/// <returns>true if found, false otherwise.</returns>
	UFUNCTION(BlueprintCallable)
	bool IsItemInInventory(UTankItemData* itemToCheck);

	/// <summary>
	/// Returns true if the corresponding item based on the provided item 
	/// name exists in the player's inventory.
	/// </summary>
	/// <param name="itemNameToCheck">The item to search for in the player's inventory.</param>
	/// <returns>true if found, false otherwise.</returns>
	bool IsItemInInventory(const FString itemNameToCheck);

	/// <summary>
	/// Returns true if the provided item exists in the player's equipment. 
	/// This uses the provided <c>itemToCheck</c>'s item name to search.
	/// </summary>
	/// <param name="itemToCheck">The item to search for in the player's equipment.</param>
	/// <returns>true if found, false otherwise.</returns>
	UFUNCTION(BlueprintCallable)
	bool IsItemEquipped(UTankItemData* itemToCheck);

	/// <summary>
	/// Converts the provided item actor to its item data and adds it to the
	/// player's equipment.
	/// </summary>
	/// <param name="itemToEquip">The item actor to add to the player's equipment.</param>
	/// <returns>true if found, false otherwise.</returns>
	bool EquipItem(ATankItem* itemToEquip);

	/// <summary>
	/// Adds the provided item to the player's equipment.
	/// </summary>
	/// <param name="itemToEquip">The item to add to the player's inventory.</param>
	/// <returns>true if found, false otherwise.</returns>
	UFUNCTION(BlueprintCallable)
	bool EquipItem(UTankItemData* itemToEquip);

	/// <summary>
	/// Removes the specified item from the player's equipment and then
	/// adds it to the player's inventory. In other words, this moves the
	/// specified item from the player's equipment to inventory.
	/// </summary>
	/// <param name="itemToUnEquip">The item to move from equipment to inventory.</param>
	/// <returns>true on success, false otherwise.</returns>
	bool UnEquipItem(ATankItem* itemToUnEquip);

	/// <summary>
	/// Removes the specified item from the player's equipment and then
	/// adds it to the player's inventory. In other words, this moves the
	/// specified item from the player's equipment to inventory.
	/// </summary>
	/// <param name="itemToUnEquip">The item to move from equipment to inventory.</param>
	/// <returns>true on success, false otherwise.</returns>
	UFUNCTION(BlueprintCallable)
	bool UnEquipItem(UTankItemData* itemToUnEquip);

	/// <summary>
	/// Returns true if the provided item is equipped as an accessory, false
	/// otherwise.
	/// </summary>
	/// <param name="accessoryToCheck">The item to check if it's currently equipped as an accessory.</param>
	/// <returns>true if the provided item is equipped as an accessory, false otherwise.</returns>
	UFUNCTION(BlueprintCallable)
	bool IsAccessoryEquipped(UTankItemData* accessoryToCheck);

	/// <summary>
	/// Adds the provided item to the player's accessories.
	/// </summary>
	/// <param name="accessoryToEquip">The accessory to equip.</param>
	/// <returns>true on success, false otherwise.</returns>
	bool EquipAccessory(ATankItem* accessoryToEquip);

	/// <summary>
	/// Adds the provided item to the player's accessories.
	/// </summary>
	/// <param name="accessoryToEquip">The accessory to equip.</param>
	/// <returns>true on success, false otherwise.</returns>
	UFUNCTION(BlueprintCallable)
	bool EquipAccessory(UTankItemData* accessoryToEquip);

	/// <summary>
	/// Removes the provided item from the player's accessories
	/// and adds it back to the player's inventory.
	/// </summary>
	/// <param name="accessoryToUnEquip">The item to unequip from the player's accessories.</param>
	/// <returns>true on success, false otherwise.</returns>
	bool UnEquipAccessory(ATankItem* accessoryToUnEquip);

	/// <summary>
	/// Removes the provided item from the player's accessories
	/// and adds it back to the player's inventory.
	/// </summary>
	/// <param name="accessoryToUnEquip">The item to unequip from the player's accessories.</param>
	/// <returns>true on success, false otherwise.</returns>
	UFUNCTION(BlueprintCallable)
	bool UnEquipAccessory(UTankItemData* accessoryToUnEquip);

	/// <summary>
	/// Applies the meshes to the different parts of the tank based
	/// on the equipment. This is where the equipment is visually attached
	/// to the tank.
	/// </summary>
	/// <param name="pawnNeedingEquipment">The pawn that is going to have the equipment attached to.</param>
	/// <returns>true on success, false otherwise.</returns>
	UFUNCTION(BlueprintCallable)
	bool EquipLoadout(ATankPawn* pawnNeedingEquipment);

	/// <summary>
	/// Applies the stats from the equipment (such as increased speed and traction) to
	/// the tank and the player.
	/// </summary>
	/// <param name="pawnNeedingEquipmentStats">The tank and player to apply the stats to.</param>
	/// <param name="spawnedEquipment">The equipment (and stats) to apply to the player.</param>
	/// <returns>true on success, false otherwise.</returns>
	bool ApplyEquipmentStats(ATankPawn* pawnNeedingEquipmentStats, TArray<AActor*>& spawnedEquipment);

	/// <summary>
	/// Takes the provided save game data and copies/loads it into this save file. 
	/// </summary>
	/// <param name="theSaveToCopyFrom">The save game file to copy/load data from.</param>
	void CopyDataFrom(UPlayerProgressionSaveGame* theSaveToCopyFrom);

	/// <summary>
	/// Returns the name of this save file.
	/// </summary>
	/// <returns>The name of this save file.</returns>
	UFUNCTION(BlueprintCallable)
	FName GetSaveFileName();

	/// <summary>
	/// Returns the DateTime for when this save file was last saved.
	/// </summary>
	/// <returns>The DateTime for when this save file was last saved.</returns>
	FDateTime GetDateOfSave();

	/// <summary>
	/// Returns the amount of money the player has (in this save file).
	/// </summary>
	/// <returns>The amount of money the player has (in this save file).</returns>
	int GetAmountOfMoney();

	/// <summary>
	/// Returns the total number of items in the player's inventory.
	/// </summary>
	/// <returns>The total number of items in the player's inventory.</returns>
	int GetNumberOfInventoryItems();

	/// <summary>
	/// Returns the total number of items in the player's accessories.
	/// </summary>
	/// <returns>The total number of items in the player's accessories.</returns>
	int GetNumberOfAccessoryItems();

	/// <summary>
	/// Returns the total number of items in the player's equipment.
	/// </summary>
	/// <returns>The total number of items in the player's equipment.</returns>
	int GetNumberOfEquippedItems();

	/// <summary>
	/// Returns the total number of levels the player has completed/beat.
	/// </summary>
	/// <returns>The total number of levels the player has completed/beat.</returns>
	int GetNumLevelsCompleted();

	/// <summary>
	/// Returns the serialized byte data at the provided index.
	/// </summary>
	/// <param name="index">The array index in the byte data.</param>
	/// <returns>The serialized byte data at the provided index.</returns>
	uint8 GetByteDataAtIndex(const int index);

	/// <summary>
	/// Returns the date and time of this save in a string format.
	/// </summary>
	/// <returns>The date and time of this save in a string format.</returns>
	UFUNCTION(BlueprintCallable)
	FString GetDateOfSaveDisplayStr();
	
	/// <summary>
	/// Returns true if this is an auto saved (automatically saved by the game
	/// and not the player), false if this was player created.
	/// </summary>
	/// <param name="isAutoSave">true if this is an auto saved, false if this was player created.</param>
	void SetIsAutoSave(const bool isAutoSave);

	/// <summary>
	/// Changes/Updates the save file name for this save game.
	/// </summary>
	/// <param name="saveFileName">The new save file name for this save game.</param>
	void SetSaveFileName(const FName& saveFileDisplayName);

	/// <summary>
	/// Changes/Updates the save file's date and time for this save game.
	/// </summary>
	/// <param name="dateOfSave">The new save file's date and time for this save game.</param>
	void SetDateOfSave(const FDateTime& dateOfSave);

	/// <summary>
	/// Changes/Updates the total number of levels the player has completed.
	/// </summary>
	/// <param name="numLevelsCompleted">The new total number of levels the player has completed.</param>
	void SetNumLevelsCompleted(const int numLevelsCompleted);

	/// <summary>
	/// Changes/Updates the total amount of money the player has.
	/// </summary>
	/// <param name="amountOfMoney">The new total amount of money the player has.</param>
	void SetAmountOfMoney(const int amountOfMoney);
};