/*
* 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>
*/
#include "PlayerProgressionSaveGame.h"
#include <Kismet/GameplayStatics.h>
#include "../Gameplay/AchievementManager.h"
#include "../Pawns/TankPawn.h"
#include "../Pawns/PlayerTankPawn.h"
#include "../Utils/NeuralTanksUtils.h"
#include "../NeuralTanksGameLevel.h"
#include "../NeuralTanksGameInstance.h"

/// <summary>
/// Default constructor.
/// </summary>
UPlayerProgressionSaveGame::UPlayerProgressionSaveGame()
{
	myIsAutoSave = false;
	myNumTankAmmunitionObtained = 0;
	myNumTanksDestroyed = 0;
	myTotalNumOfTankAmmunitionObtained = 0;
	myTotalNumTanksDestroyed = 0;
	myAmountOfMoney = 0;
	myNumLevelsCompleted = 0;
	mySaveFileName = "UNSET";
}

/// <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>
void UPlayerProgressionSaveGame::Serialize(FArchive& Ar)
{
	int index;
	UTankItemData* currTankItemData;

	Super::Serialize(Ar);

	if (Ar.IsSaving() == true)
	{
		//Save the inventory data to the FArchive.
		for (index = 0; index < myInventory.Num(); index++)
		{
			myInventory[index]->Serialize(Ar);
		}

		//Save the accessory data to the FArchive.
		for (index = 0; index < myAccessories.Num(); index++)
		{
			myAccessories[index]->Serialize(Ar);
		}

		//Save the equipment data to the FArchive.
		for (index = 0; index < myEquippedItems.Num(); index++)
		{
			myEquippedItems[index]->Serialize(Ar);
		}
	}
	else
	{	
		//Read the inventory data from the FArchive.
		myInventory.Empty();
		for (index = 0; index < myNumInventoryItems; index++)
		{
			currTankItemData = NewObject<UTankItemData>();
			currTankItemData->Serialize(Ar);
			
			myInventory.Add(currTankItemData);
		}

		//Read the accessory data from the FArchive.
		myAccessories.Empty();
		for (index = 0; index < myNumAccessoryItems; index++)
		{
			currTankItemData = NewObject<UTankItemData>();
			currTankItemData->Serialize(Ar);

			myAccessories.Add(currTankItemData);
		}

		//Read the equipment data from the FArchive.
		myEquippedItems.Empty();
		for (index = 0; index < myNumEquippedItems; index++)
		{
			currTankItemData = NewObject<UTankItemData>();
			currTankItemData->Serialize(Ar);

			myEquippedItems.Add(currTankItemData);
		}
	}
}

/// <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>
void UPlayerProgressionSaveGame::AddMoney(int amountOfMoneyToAdd)
{
	//Prevent money overflow.
	if (myAmountOfMoney < MAX_int32)
	{
		//Prevent negative numbers and ensure we don't go out of bounds.
		if (amountOfMoneyToAdd > 0 && amountOfMoneyToAdd < MAX_int32)
		{
			myAmountOfMoney += 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>
void UPlayerProgressionSaveGame::SubtractMoney(int amountOfMoneyToSubtract)
{
	//Prevent negative money.
	if (myAmountOfMoney > 0)
	{
		//Prevent negative numbers and ensure we don't go out of bounds.
		if (amountOfMoneyToSubtract > 0 && amountOfMoneyToSubtract < MAX_int32)
		{
			myAmountOfMoney -= amountOfMoneyToSubtract;
		}
	}
}

/// <summary>
/// Increments the total number of tank ammunition the player collected in
/// the levels.
/// </summary>
void UPlayerProgressionSaveGame::IncrementNumberOfTankAmmunitionObtained()
{
	myNumTankAmmunitionObtained++;
}

/// <summary>
/// Increments the total number of enemy tanks this player has destroyed.
/// </summary>
void UPlayerProgressionSaveGame::IncrementNumberOfTanksDestroyed()
{
	myNumTanksDestroyed++;
}

/// <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 UPlayerProgressionSaveGame::AddItemToInventory(ATankItem* itemToAddToInventory)
{
	UTankItemData* theTankItemData;

	if (itemToAddToInventory != NULL)
	{
		theTankItemData = ATankItem::CreateDataFromTankItemActor(itemToAddToInventory);
		if (theTankItemData != NULL)
		{
			return AddItemToInventory(theTankItemData);
		}

		UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::AddItemToInventory(ATankItem*) Error: Failed to allocate data object for item!");
	}

	UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::AddItemToInventory(ATankItem*) Error: Failed to add item to inventory, item was null!");
	return false;
}

/// <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>
bool UPlayerProgressionSaveGame::AddItemToInventory(UTankItemData* itemToAddToInventory)
{
	if (itemToAddToInventory != NULL)
	{
		//Prevent adding duplicate items.
		if(myInventory.ContainsByPredicate([itemToAddToInventory](const UTankItemData* currInventoryItem) { return itemToAddToInventory->ItemName == currInventoryItem->ItemName; }) == false)
		{
			//Prevent adding items that are already equipped in the accessories or equipment slots.
			if (IsAccessoryEquipped(itemToAddToInventory) == false && IsItemEquipped(itemToAddToInventory) == false)
			{
				myInventory.Add(itemToAddToInventory);
				myNumInventoryItems = myInventory.Num();

				CheckForPossibleAchievement();

				return true;
			}
		}

		UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::AddItemToInventory(UTankItemData*) Error: Failed to add item to inventory since it already exists!");
	}

	UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::AddItemToInventory(UTankItemData*) Error: Failed to allocate data object for item!");
	return false;
}

/// <summary>
/// Some achievements are directly tied to what is in the player's inventory
/// and that is why we do this check.
/// </summary>
void UPlayerProgressionSaveGame::CheckForPossibleAchievement()
{
	//Check if the player has all the tank boobs. (TWIN_PEAKS_ACHIEVEMENT_1_0)
	bool playerHasAllBoobs = DoesPlayerHaveItem("Vanity_BlackAndBrownLeftTankTit") &&
							 DoesPlayerHaveItem("Vanity_BlackAndBrownRightTankTit") &&
							 DoesPlayerHaveItem("Vanity_BlackAndPinkLeftTankTit") &&
							 DoesPlayerHaveItem("Vanity_BlackAndPinkRightTankTit") &&
							 DoesPlayerHaveItem("Vanity_BrownAndBlackLeftTankTit") &&
							 DoesPlayerHaveItem("Vanity_BrownAndBlackRightTankTit") &&
							 DoesPlayerHaveItem("Vanity_BrownAndPinkLeftTankTit") &&
							 DoesPlayerHaveItem("Vanity_BrownAndPinkRightTankTit") &&
							 DoesPlayerHaveItem("Vanity_ChromeLeftTankTit") &&
							 DoesPlayerHaveItem("Vanity_ChromeRightTankTit") &&
							 DoesPlayerHaveItem("Vanity_GoldenLeftTankTit") &&
							 DoesPlayerHaveItem("Vanity_GoldenRightTankTit") &&
							 DoesPlayerHaveItem("Vanity_TanAndBlackLeftTankTit") &&
							 DoesPlayerHaveItem("Vanity_TanAndBlackRightTankTit") &&
							 DoesPlayerHaveItem("Vanity_TanAndBrownLeftTankTit") &&
							 DoesPlayerHaveItem("Vanity_TanAndBrownRightTankTit") &&
							 DoesPlayerHaveItem("Vanity_TanAndPinkLeftTankTit") &&
							 DoesPlayerHaveItem("Vanity_TanAndPinkRightTankTit") &&
							 DoesPlayerHaveItem("Vanity_WhiteAndBlackLeftTankTit") &&
							 DoesPlayerHaveItem("Vanity_WhiteAndBlackRightTankTit") &&
							 DoesPlayerHaveItem("Vanity_WhiteAndBrownLeftTankTit") &&
							 DoesPlayerHaveItem("Vanity_WhiteAndBrownRightTankTit") &&
							 DoesPlayerHaveItem("Vanity_WhiteAndPinkLeftTankTit") &&
							 DoesPlayerHaveItem("Vanity_WhiteAndPinkRightTankTit");

	//Check if player has golden tank nuts. (THE_KINTAMA_TWINS_ACHIEVEMENT_1_1)
	bool playerHasGoldTankNuts = DoesPlayerHaveItem("Vanity_GoldTankNuts");

	//Check if the player has all the gold armor. (SOLID_GOLD_ACHIEVEMENT_1_3).
	bool playerHasAllGoldArmor = DoesPlayerHaveItem("Armor_GoldTankBody") &&
								 DoesPlayerHaveItem("Armor_GoldTankCannon") &&
								 DoesPlayerHaveItem("Armor_GoldTankGear") &&
								 DoesPlayerHaveItem("Armor_GoldTankTrack") &&
								 DoesPlayerHaveItem("Armor_GoldTankTurret") &&
								 DoesPlayerHaveItem("Armor_GoldTankWheel");

	if (playerHasAllBoobs == true)
	{
		UAchievementManager::RewardAchievement(ENeuralTanksAchievement::TWIN_PEAKS_ACHIEVEMENT_1_0);
	}

	if (playerHasGoldTankNuts == true)
	{
		UAchievementManager::RewardAchievement(ENeuralTanksAchievement::THE_KINTAMA_TWINS_ACHIEVEMENT_1_1);
	}

	if (playerHasAllGoldArmor == true)
	{
		UAchievementManager::RewardAchievement(ENeuralTanksAchievement::SOLID_GOLD_ACHIEVEMENT_1_3);
	}

	//Check if the player has all the items in the game (we can assume no duplicates are allowed).
	if (myNumInventoryItems >= 116)
	{
		UAchievementManager::RewardAchievement(ENeuralTanksAchievement::COMPLETIONIST_ACHIEVEMENT_1_4);
	}
}

/// <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>
bool UPlayerProgressionSaveGame::RemoveItemFromInventory(UTankItemData* item)
{
	bool removeSuccess;
	if (item != NULL)
	{
		removeSuccess = myInventory.Remove(item) > 0;
		myNumInventoryItems = myInventory.Num();

		return removeSuccess;
	}

	UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::RemoveItemFromInventory(UTankItemData*) Error: item cannot be null!");
	return false;
}

/// <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 UPlayerProgressionSaveGame::RemoveItemFromInventory(const FString& itemName)
{
	int index = FindIndexOfItemInInventory(itemName);
	if (index != -1)
	{
		return RemoveItemFromInventory(index);
	}

	UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::RemoveItemFromInventory(FString) Error: Failed to find item in inventory!");
	return false;
}

/// <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 UPlayerProgressionSaveGame::RemoveItemFromInventory(const int index)
{
	if (index > -1 && index < myInventory.Num())
	{
		myInventory.RemoveAt(index);
		myNumInventoryItems = myInventory.Num();

		return true;
	}

	UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::RemoveItemFromInventory(int) Error: Failed to find item in inventory!");
	return false;
}

/// <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* UPlayerProgressionSaveGame::FindItemFromInventory(const FString& itemName)
{
	int index;

	if (itemName.IsEmpty() == false)
	{
		index = FindIndexOfItemInInventory(itemName);
		if (index != -1)
		{
			return myInventory[index];
		}

		UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::FindItemFromInventory(FString) Error: Failed to find item in inventory!");
	}

	return NULL;
}

/// <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* UPlayerProgressionSaveGame::FindItemFromInventory(const int index)
{
	if (index > -1 && index < myInventory.Num())
	{
		return myInventory[index];
	}

	UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::FindItemFromInventory(int) Error: Failed to find item in inventory!");
	return NULL;
}

/// <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* UPlayerProgressionSaveGame::FindItemFromAccessories(const FString& itemName)
{
	int index;

	if (itemName.IsEmpty() == false)
	{
		index = FindIndexOfItemInAccessories(itemName);
		if (index != -1)
		{
			return myAccessories[index];
		}

		UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::FindItemFromAccessories(FString) Error: Failed to find item in accessories!");
	}

	return NULL;
}

/// <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* UPlayerProgressionSaveGame::FindItemFromAccessories(const int index)
{
	if (index > -1 && index < myAccessories.Num())
	{
		return myAccessories[index];
	}

	UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::FindItemFromAccessories(int) Error: Failed to find item in accessories!");
	return NULL;
}

/// <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* UPlayerProgressionSaveGame::FindItemFromEquipment(const FString& itemName)
{
	int index;

	if (itemName.IsEmpty() == false)
	{
		index = FindIndexOfItemInEquipment(itemName);
		if (index != -1)
		{
			return myEquippedItems[index];
		}

		UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::FindItemFromEquipment(FString) Error: Failed to find item in equipment!");
	}

	return NULL;
}

/// <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* UPlayerProgressionSaveGame::FindItemFromEquipment(const int index)
{
	if (index > -1 && index < myEquippedItems.Num())
	{
		return myEquippedItems[index];
	}

	UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::FindItemFromEquipment(int) Error: Failed to find item in equipment!");
	return NULL;
}

/// <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 UPlayerProgressionSaveGame::FindIndexOfItemInInventory(const FString& itemName)
{
	if (itemName.IsEmpty() == false)
	{
		for (int index = 0; myInventory.Num(); index++)
		{
			if (myInventory[index]->ItemName.Equals(itemName) == true)
			{
				return index;
			}
		}

		UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::FindIndexOfItemInInventory(FString) Error: Failed to find item in inventory!");
	}

	UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::FindIndexOfItemInInventory(FString) Error: itemName cannot be empty!");
	return -1;
}

/// <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 UPlayerProgressionSaveGame::FindIndexOfItemInAccessories(const FString& itemName)
{
	if (itemName.IsEmpty() == false)
	{
		for (int index = 0; myAccessories.Num(); index++)
		{
			if (myAccessories[index]->ItemName.Equals(itemName) == true)
			{
				return index;
			}
		}

		UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::FindIndexOfItemInAccessories(FString) Error: Failed to find item in accessories!");
	}

	UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::FindIndexOfItemInAccessories(FString) Error: itemName cannot be empty!");
	return -1;
}

/// <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 UPlayerProgressionSaveGame::FindIndexOfItemInEquipment(const FString& itemName)
{
	for (int index = 0; myEquippedItems.Num(); index++)
	{
		if (myEquippedItems[index]->ItemName.Equals(itemName) == true)
		{
			return index;
		}
	}

	UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::FindIndexOfItemInEquipment(FString) Error: Failed to find equipped item!");
	return -1;
}

/// <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>
bool UPlayerProgressionSaveGame::DoesPlayerHaveItem(const FString& itemName)
{
	return myAccessories.ContainsByPredicate([itemName](const UTankItemData* currInventoryItem) { return itemName == currInventoryItem->ItemName; }) ||
		   myEquippedItems.ContainsByPredicate([itemName](const UTankItemData* currInventoryItem) { return itemName == currInventoryItem->ItemName; }) ||
		   myInventory.ContainsByPredicate([itemName](const UTankItemData* currInventoryItem) { return itemName == currInventoryItem->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>
bool UPlayerProgressionSaveGame::IsItemInInventory(UTankItemData* itemToCheck)
{
	if (itemToCheck != NULL)
	{
		return myInventory.ContainsByPredicate([itemToCheck](const UTankItemData* currInventoryItem) { return itemToCheck->ItemName == currInventoryItem->ItemName; });
	}

	UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::IsItemInInventory(UTankItemData*) Error: itemToCheck cannot be null!");
	return false;
}

/// <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 UPlayerProgressionSaveGame::IsItemInInventory(const FString itemNameToCheck)
{
	if (itemNameToCheck.IsEmpty() == false)
	{
		return myInventory.ContainsByPredicate([itemNameToCheck](const UTankItemData* currInventoryItem) { return itemNameToCheck == currInventoryItem->ItemName; });
	}

	UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::IsItemInInventory(UTankItemData*) Error: itemToCheck cannot be null!");
	return false;
}

/// <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>
bool UPlayerProgressionSaveGame::IsItemEquipped(UTankItemData* itemToCheck)
{
	if (itemToCheck != NULL)
	{
		return myEquippedItems.ContainsByPredicate([itemToCheck](const UTankItemData* currInventoryItem) { return itemToCheck->ItemName == currInventoryItem->ItemName; });
	}

	UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::IsItemEquipped(UTankItemData*) Error: itemToCheck cannot be null!");
	return false;
}

/// <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 UPlayerProgressionSaveGame::EquipItem(ATankItem* itemToEquip)
{
	if (itemToEquip != NULL)
	{
		return EquipItem(ATankItem::CreateDataFromTankItemActor(itemToEquip));
	}

	UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::EquipItem(ATankItem*) Error: itemToEquip cannot be null!");
	return false;
}

/// <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>
bool UPlayerProgressionSaveGame::EquipItem(UTankItemData* itemToEquip)
{
	//Check for the case where the player starts to unequip an item but then re-adds the item to the same slot.
	if (myEquippedItems.ContainsByPredicate([itemToEquip](const UTankItemData* currInventoryItem) { return itemToEquip->ItemName == currInventoryItem->ItemName; }) == false)
	{
		//Remove the item from the inventory.
		if(RemoveItemFromInventory(itemToEquip) == true)
		{
			//Add item to equipment "inventory".
			if (myEquippedItems.Add(itemToEquip) >= 0)
			{
				myNumEquippedItems = myEquippedItems.Num();
				return true;
			}
		}

		UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::EquipItem(UTankItemData*) Error: Failed to find item in inventory!");
		return false;
	}

	//Return success if the item is already equipped.
	return true;
}

/// <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 UPlayerProgressionSaveGame::UnEquipItem(ATankItem* itemToUnEquip)
{
	UTankItemData* itemToUnEquipData;

	if (itemToUnEquip != NULL)
	{
		itemToUnEquipData = ATankItem::CreateDataFromTankItemActor(itemToUnEquip);
		if (itemToUnEquipData != NULL)
		{
			return UnEquipItem(itemToUnEquipData);
		}

		UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::UnEquipItem(ATankItem*) Error: Failed to create item data from provided itemToUnEquip!");
	}

	UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::UnEquipItem(ATankItem*) Error: itemToUnEquip cannot be null!");
	return false;
}

/// <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 UPlayerProgressionSaveGame::UnEquipItem(UTankItemData* itemToUnEquip)
{
	if (itemToUnEquip != NULL)
	{
		//Remove item from the equipment "inventory".
		if (myEquippedItems.Remove(itemToUnEquip) > 0)
		{
			myNumEquippedItems = myEquippedItems.Num();

			//Add the item back to the inventory.
			return AddItemToInventory(itemToUnEquip);
		}

		UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::UnEquipItem(UTankItemData*) Error: Failed to find item and remove it from equipment!");
	}

	UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::UnEquipItem(UTankItemData*) Error: itemToUnEquip cannot be null!");
	return false;
}

/// <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>
bool UPlayerProgressionSaveGame::IsAccessoryEquipped(UTankItemData* accessoryToCheck)
{
	if (accessoryToCheck != NULL)
	{
		return myAccessories.ContainsByPredicate([accessoryToCheck](const UTankItemData* currInventoryItem) { return accessoryToCheck->ItemName == currInventoryItem->ItemName; });
	}

	UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::IsAccessoryEquipped(UTankItemData*) Error: accessoryToCheck cannot be null!");
	return false;
}

/// <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 UPlayerProgressionSaveGame::EquipAccessory(ATankItem* accessoryToEquip)
{
	return EquipAccessory(ATankItem::CreateDataFromTankItemActor(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>
bool UPlayerProgressionSaveGame::EquipAccessory(UTankItemData* accessoryToEquip)
{
	//Check for the case where the player starts to unequip an item but then re-adds the item to the same slot.
	if (myAccessories.ContainsByPredicate([accessoryToEquip](const UTankItemData* currInventoryItem) { return accessoryToEquip->ItemName == currInventoryItem->ItemName; }) == false)
	{
		//Remove the item from the inventory.
		if (RemoveItemFromInventory(accessoryToEquip) == true)
		{
			//Add item to accessories "inventory".
			if (myAccessories.Add(accessoryToEquip) >= 0)
			{
				myNumAccessoryItems = myAccessories.Num();
				return true;
			}
		}

		UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::EquipAccessory(UTankItemData*) Error: Failed to find item in inventory!");
		return false;
	}

	//Return success if the item is already equipped.
	return true;
}

/// <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 UPlayerProgressionSaveGame::UnEquipAccessory(ATankItem* accessoryToUnEquip)
{
	UTankItemData* accessoryToUnEquipData;

	if (accessoryToUnEquip != NULL)
	{
		accessoryToUnEquipData = ATankItem::CreateDataFromTankItemActor(accessoryToUnEquip);
		if (accessoryToUnEquipData != NULL)
		{
			return UnEquipAccessory(accessoryToUnEquipData);
		}

		UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::UnEquipAccessory(ATankItem*) Error: Failed to create item data from provided accessoryToUnEquip!");
	}

	UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::UnEquipAccessory(ATankItem*) Error: accessoryToUnEquip cannot be null!");
	return false;
}

/// <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 UPlayerProgressionSaveGame::UnEquipAccessory(UTankItemData* accessoryToUnEquip)
{
	if (accessoryToUnEquip != NULL)
	{
		//Remove item from the equipment "inventory".
		if (myAccessories.Remove(accessoryToUnEquip) > 0)
		{
			myNumAccessoryItems = myAccessories.Num();

			//Add the item back to the inventory.
			return AddItemToInventory(accessoryToUnEquip);
		}

		UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::UnEquipAccessory(UTankItemData*) Error: Failed to find item and remove it from accessories!");
	}

	UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::UnEquipAccessory(UTankItemData*) Error: accessoryToUnEquip cannot be null!");
	return false;
}

/// <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>
bool UPlayerProgressionSaveGame::EquipLoadout(ATankPawn* pawnNeedingEquipment)
{
	int index;
	APhysicsTankPawn* physPawnNeedingEquipment; //Need the physics version since it has the tank components being referenced (i.e. the turret, the wheel, etc) and the stats. 

	if (pawnNeedingEquipment != NULL)
	{
		physPawnNeedingEquipment = Cast<APhysicsTankPawn>(pawnNeedingEquipment);
		if (physPawnNeedingEquipment != NULL)
		{
			//Go through each item in the equipped collection and apply the corresponding visual(s) and stats.
			for (index = 0; index < myEquippedItems.Num(); index++)
			{
				if (ATankItem::EquipToPawn(myEquippedItems[index], physPawnNeedingEquipment) == false)
				{
					UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::EquipLoadout(ATankPawn*) Error: Failed to equip item to APhyscisTankPawn!");
					return false;
				}
			}

			//Also apply accessories.
			for (index = 0; index < myAccessories.Num(); index++)
			{
				if (ATankItem::EquipToPawn(myAccessories[index], physPawnNeedingEquipment) == false)
				{
					UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::EquipLoadout(ATankPawn*) Error: Failed to equip accessory to APhyscisTankPawn!");
					return false;
				}
			}

			return true;
		}

		UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::EquipLoadout(ATankPawn*) Error: Failed to cast argument to APhyscisTankPawn!");
		return false;
	}

	UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::EquipLoadout(ATankPawn*) Error: pawnNeedingEquipment cannot be null!");
	return false;
}

/// <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 UPlayerProgressionSaveGame::ApplyEquipmentStats(ATankPawn* pawnNeedingEquipmentStats, TArray<AActor*>& spawnedEquipment)
{
	//Need the player pawn version since it has the tank components being referenced (i.e. the turret, the wheel, etc) and the stats such as hp, armor, etc. 
	APlayerTankPawn* playerPawnNeedingEquipmentStats;
	ATankItem* currSpawnedEquipment;

	if (pawnNeedingEquipmentStats != NULL)
	{
		playerPawnNeedingEquipmentStats = Cast<APlayerTankPawn>(pawnNeedingEquipmentStats);
		if (playerPawnNeedingEquipmentStats != NULL)
		{
			for (int index = 0; index < spawnedEquipment.Num(); index++)
			{
				if (spawnedEquipment[index] != NULL)
				{
					currSpawnedEquipment = Cast<ATankItem>(spawnedEquipment[index]);
					currSpawnedEquipment->ApplyItemStatsToPawn(playerPawnNeedingEquipmentStats);
				}
				else
				{
					UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::ApplyEquipmentStats(ATankPawn*, TArray<ATankItem*>&) Error: spawnedEquipment was null when accessing!");
					return false;
				}
			}

			return true;
		}

		UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::ApplyEquipmentStats(ATankPawn*, TArray<ATankItem*>&) Error: Failed to cast argument to APhyscisTankPawn!");
		return false;
	}

	UNeuralTanksUtils::WriteToLog("UPlayerProgressionSaveGame::ApplyEquipmentStats(ATankPawn*, TArray<ATankItem*>&) Error: pawnNeedingEquipment cannot be null!");
	return false;
}

/// <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 UPlayerProgressionSaveGame::CopyDataFrom(UPlayerProgressionSaveGame* theSaveToCopyFrom)
{
	int index;

	//mySaveFileName = theSaveToCopyFrom->GetSaveFileName(); temp for logging.
	SetSaveFileName(theSaveToCopyFrom->GetSaveFileName());
	myDateOfSave = theSaveToCopyFrom->GetDateOfSave();
	myAmountOfMoney = theSaveToCopyFrom->GetAmountOfMoney();
	myNumInventoryItems = theSaveToCopyFrom->GetNumberOfInventoryItems();
	myNumAccessoryItems = theSaveToCopyFrom->GetNumberOfAccessoryItems();
	myNumEquippedItems = theSaveToCopyFrom->GetNumberOfEquippedItems();
	myNumLevelsCompleted = theSaveToCopyFrom->GetNumLevelsCompleted();
	
	for (index = 0; index < theSaveToCopyFrom->GetNumberOfInventoryItems(); index++)
	{
		myInventory.Add(theSaveToCopyFrom->FindItemFromInventory(index));
	}

	for (index = 0; index < theSaveToCopyFrom->GetNumberOfAccessoryItems(); index++)
	{
		myAccessories.Add(theSaveToCopyFrom->FindItemFromAccessories(index));
	}

	for (index = 0; index < theSaveToCopyFrom->GetNumberOfEquippedItems(); index++)
	{
		myEquippedItems.Add(theSaveToCopyFrom->FindItemFromEquipment(index));
	}

	ByteData = theSaveToCopyFrom->ByteData;
}

/// <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 UPlayerProgressionSaveGame::OnRoundWon(UWorld* ownerWorld)
{
	UNeuralTanksGameInstance* theGameInstance;
	ANeuralTanksGameMode* theGameMode;
	ANeuralTanksGameLevel* theGameLevel;

	myTotalNumOfTankAmmunitionObtained += myNumTankAmmunitionObtained;
	myTotalNumTanksDestroyed += myNumTanksDestroyed;

	//Use the level number as the number of levels completed.
	if (ownerWorld != NULL)
	{
		theGameLevel = Cast<ANeuralTanksGameLevel>(ownerWorld->GetLevelScriptActor());
		if (theGameLevel != NULL)
		{
			myNumLevelsCompleted = theGameLevel->GetLevelNumberFromLevelName();

			//Check if player has finished all the levels (beat the game).
			if (myNumLevelsCompleted >= 6)
			{
				UAchievementManager::RewardAchievement(ENeuralTanksAchievement::GREAT_SUCCESS_ACHIEVEMENT_1_2);
			}
		}
		else
		{
			UNeuralTanksUtils::WriteToLog(TEXT("UPlayerProgressionSaveGame::OnRoundWon(UWorld*) Error: Failed to cast level script actor!"));
		}

		theGameMode = Cast<ANeuralTanksGameMode>(UGameplayStatics::GetGameMode(ownerWorld));
		if (theGameMode != NULL)
		{
			//Calculate the money reward based on the generation and the number of tanks destroyed.
			AddMoney(theGameMode->GetCurrentGeneration() * myNumTanksDestroyed);
		}
		else
		{
			UNeuralTanksUtils::WriteToLog(TEXT("UPlayerProgressionSaveGame::OnRoundWon(UWorld*) Error: Failed to obtain game mode, this is needed for saving!"));
		}

		//Autosave the game after the round ends.
		theGameInstance = Cast<UNeuralTanksGameInstance>(UGameplayStatics::GetGameInstance(ownerWorld));
		if (theGameInstance != NULL)
		{
			if (theGameInstance->SaveGame(this, true) == false)
			{
				UNeuralTanksUtils::WriteToLog(TEXT("UPlayerProgressionSaveGame::OnRoundWon(UWorld*) Error: Failed to autosave the game progress!"));
			}
		}
		else
		{
			UNeuralTanksUtils::WriteToLog(TEXT("UPlayerProgressionSaveGame::OnRoundWon(UWorld*) Error: Failed to obtain game instance, this is needed for saving!"));
		}
	}
	else
	{
		UNeuralTanksUtils::WriteToLog(TEXT("UPlayerProgressionSaveGame::OnRoundWon(UWorld*) Error: Provided ownerWorld was null, this is needed for saving!"));
	}
}

/// <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 UPlayerProgressionSaveGame::OnRoundLost(UWorld* ownerWorld)
{
	//We currently don't record anything on round loss.
}

/// <summary>
/// Returns the name of this save file.
/// </summary>
/// <returns>The name of this save file.</returns>
FName UPlayerProgressionSaveGame::GetSaveFileName()
{
	return mySaveFileName;
}

/// <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 UPlayerProgressionSaveGame::GetDateOfSave()
{
	return myDateOfSave;
}

/// <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 UPlayerProgressionSaveGame::GetAmountOfMoney()
{
	return myAmountOfMoney;
}

/// <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 UPlayerProgressionSaveGame::GetNumberOfInventoryItems()
{
	return myNumInventoryItems;
}

/// <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 UPlayerProgressionSaveGame::GetNumberOfAccessoryItems()
{
	return myNumAccessoryItems;
}

/// <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 UPlayerProgressionSaveGame::GetNumberOfEquippedItems()
{
	return myNumEquippedItems;
}

/// <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 UPlayerProgressionSaveGame::GetNumLevelsCompleted()
{
	return myNumLevelsCompleted;
}

/// <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 UPlayerProgressionSaveGame::GetByteDataAtIndex(const int index)
{
	return ByteData[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>
FString UPlayerProgressionSaveGame::GetDateOfSaveDisplayStr()
{
	FString retVal;
	
	myDateOfSave.ExportTextItem(retVal, myDateOfSave, this, 0, NULL);

	return retVal;
}

/// <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 UPlayerProgressionSaveGame::SetIsAutoSave(const bool isAutoSave)
{
	myIsAutoSave = 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 UPlayerProgressionSaveGame::SetSaveFileName(const FName& saveFileName)
{
	mySaveFileName = saveFileName;
	UNeuralTanksUtils::WriteToLog(FString::Printf(TEXT("UPlayerProgressionSaveGame::SetSaveFileName(FName) mySaveFileName=%s saveFileName=%s..."), *mySaveFileName.ToString(), *saveFileName.ToString()));
}

/// <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 UPlayerProgressionSaveGame::SetDateOfSave(const FDateTime& dateOfSave)
{
	myDateOfSave = 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 UPlayerProgressionSaveGame::SetNumLevelsCompleted(const int numLevelsCompleted)
{
	myNumLevelsCompleted = 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 UPlayerProgressionSaveGame::SetAmountOfMoney(const int amountOfMoney)
{
	myAmountOfMoney = FMath::Clamp(amountOfMoney, 0, MAX_int32);
}