/*
* 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 "TankItem.h"
#include "../Pawns/TankPawn.h"
#include "../Pawns/PhysicsTankPawn.h"
#include "../SavingAndLoading/TankItemData.h"
#include "../Utils/NeuralTanksUtils.h"
#include <Kismet/GameplayStatics.h>

/// <summary>
/// Default constructor for the Tank Item.
/// </summary>
ATankItem::ATankItem()
{
	myItemCost = 1;
	myItemType = EItemType::VE_AMMO;
	myEquipmentSlot = EEquipmentSlot::VE_NONE;
	myItemName = TEXT("ITEM NAME HERE");
	myItemDescription = FText::FromString("ITEM DESCRIPTION HERE");
}

/// <summary>
/// Initializes this Tank Ammunition Item with the provided data.
/// </summary>
/// <param name="itemCost">The ammount of money the item costs in the shop. (TankAmmunitionItem is not available in the shop, only the world/level.)</param>
/// <param name="itemType">The category of item such as Armor, Equipment, etc.</param>
/// <param name="itemName">The unique name for this item.</param>
/// <param name="itemDescription">The localized description of the name.</param>
void ATankItem::Initialize(const int itemCost, const EItemType itemType, const EEquipmentSlot equipmentSlot, const FString& itemName, const FText& itemDescription)
{
	myItemCost = itemCost;
	myItemType = itemType;
	myEquipmentSlot = equipmentSlot;
	myItemName = itemName;
	myItemDescription = itemDescription;
}

/// <summary>
/// Called when this item first loads in the level/world.
/// </summary>
void ATankItem::BeginPlay()
{
	Super::BeginPlay();

	AActor* landscapeActor = UGameplayStatics::GetActorOfClass(GetWorld(), ALandscape::StaticClass());
	if (landscapeActor != NULL)
	{
		myLandscape = Cast<ALandscape>(landscapeActor);
	}

	myStaticMeshComponent = FindComponentByClass<UStaticMeshComponent>();
	if (myStaticMeshComponent != NULL)
	{
		myStaticMeshComponent->OnComponentBeginOverlap.AddDynamic(this, &ATankItem::OnTankItemBeginOverlap);
	}
}

/// <summary>
/// Adds this item object to the provided player equipment slot.
/// </summary>
/// <param name="physPawnNeedingEquipment">The player to equip this item to.</param>
/// <returns>True if successfully equipped, false otherwise.</returns>
bool ATankItem::EquipToPawn(APhysicsTankPawn* physPawnNeedingEquipment)
{
	UTankItemData* theTankItemData;

	if (physPawnNeedingEquipment != NULL)
	{
		theTankItemData = CreateDataFromTankItemActor(this);
		if (theTankItemData != NULL)
		{
			return EquipToPawn(theTankItemData, physPawnNeedingEquipment);
		}

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

	UNeuralTanksUtils::WriteToLog("ATankItem::EquipToPawn(APhysicsTankPawn*) Error: PhysPawnNeedingEquipment was null!");
	return false;
}

/// <summary>
/// Adds the provided itemData to the provided player equipment slot. Since the UTankItemData class is 
/// strictly data, I decided to use a static function here instead of adding functions the data class.
/// </summary>
/// <param name="itemData">The item to equip to the physPawnNeedingEquipment.</param>
/// <param name="physPawnNeedingEquipment">The pawn to equip itemData to.</param>
/// <returns>True if successfully equipped, false otherwise.</returns>
bool ATankItem::EquipToPawn(UTankItemData* itemData, APhysicsTankPawn* physPawnNeedingEquipment)
{
	if (physPawnNeedingEquipment != NULL)
	{
		if (itemData != NULL)
		{
			if (itemData->EquipmentSlot == EEquipmentSlot::VE_HITCH)
			{
				return physPawnNeedingEquipment->AttachItemToTankBodyHitch(itemData);
			}
			else if (itemData->EquipmentSlot == EEquipmentSlot::VE_LEFTCHEST || itemData->EquipmentSlot == EEquipmentSlot::VE_RIGHTCHEST)
			{
				return physPawnNeedingEquipment->AttachItemToTankTurretChest(itemData);
			}
			else
			{
				if (physPawnNeedingEquipment->SetStaticMeshForEquipmentType(itemData) == false)
				{
					UNeuralTanksUtils::WriteToLog("ATankItem::EquipToPawn(UTankItemData*, APhysicsTankPawn*) Error: failed to set static mesh for equipment type!");
					return false;
				}

				//Set any additional static meshes that might be bundled with this itemData.
				for (TPair<FString, UStaticMesh*>& currAdditonal : itemData->AdditionalStaticMeshes)
				{
					if (physPawnNeedingEquipment->SetStaticMeshForNamedComponent(currAdditonal.Key, currAdditonal.Value) == false)
					{
						UNeuralTanksUtils::WriteToLog("ATankItem::EquipToPawn(UTankItemData*, APhysicsTankPawn*) Error: failed to set static mesh for named component!");
						return false;
					}
				}

				return true;
			}
		}
	
		UNeuralTanksUtils::WriteToLog("ATankItem::EquipToPawn(UTankItemData*, APhysicsTankPawn*) Error: itemData was null!");
	}

	UNeuralTanksUtils::WriteToLog("ATankItem::EquipToPawn(UTankItemData*, APhysicsTankPawn*) Error: PhysPawnNeedingEquipment was null!");
	return false;
}

/// <summary>
/// Called when the a tank overlaps with this Tank Item.
/// 
/// Parent Comment: Event called when something starts to overlaps this component, for example a player walking into a trigger.
/// For events when objects have a blocking collision, for example a player hitting a wall, see 'Hit' events.
///	
/// @note Both this component and the other one must have GetGenerateOverlapEvents() set to true to generate overlap events.
/// @note When receiving an overlap from another object's movement, the directions of 'Hit.Normal' and 'Hit.ImpactNormal'
/// will be adjusted to indicate force from the other object against this object.
/// 
/// </summary>
/// <param name="overlappedComp">A pointer to the specific component in this Actor that was involved with the overlap.</param>
/// <param name="otherActor">A pointer to the Actor whose component triggered the overlap.</param>
/// <param name="otherComp">A pointer to the specific component on otherActor that caused the overlap</param>
/// <param name="otherBodyIndex">The index of the body part that was overlapped. This is relevant for skeletal meshes or other objects with multiple physics bodies, and it is usually 0 for simple components.</param>
/// <param name="bFromSweep">True if the overlap was the result of a sweep test, false otherwise.</param>
/// <param name="sweepResult">Struct which contains detailed information about the overlap, such as the location and normal of the impact point.</param>
void ATankItem::OnTankItemBeginOverlap(UPrimitiveComponent* overlappedComp, AActor* other, UPrimitiveComponent* otherComp, int32 otherBodyIndex, bool bFromSweep, const FHitResult& sweepResult)
{
	ATankPawn* collidedTank = Cast<ATankPawn>(other);
	if (collidedTank != NULL)
	{
		collidedTank->OnTankOverlappedItem(this);
	}
}

/// <summary>
/// Convert's the provided Tank Item actor to its serializeable (saveable) data equivilent.
/// The <c>UTankItemData</c> class is a data holder for <c>ATankItem<c>.
/// </summary>
/// <param name="theTankItemActor">The Tank Item Actor to convert.</param>
/// <returns>The converted <c>UTankItemData</c> that can be serialized (saved).</returns>
UTankItemData* ATankItem::CreateDataFromTankItemActor(const ATankItem* theTankItemActor)
{
	UTankItemData* currTankItemData;

	if (theTankItemActor != NULL)
	{
		currTankItemData = NewObject<UTankItemData>();
		if (currTankItemData != NULL)
		{
			currTankItemData->ItemCost = theTankItemActor->GetItemCost();
			currTankItemData->ItemType = theTankItemActor->GetItemType();
			currTankItemData->EquipmentSlot = theTankItemActor->GetEquipmentSlot();
			currTankItemData->ItemName = theTankItemActor->GetItemName();
			currTankItemData->ItemDescription = theTankItemActor->GetItemDescription();
			currTankItemData->InventoryTexture = theTankItemActor->GetInventoryTexture();
			currTankItemData->StaticMesh = theTankItemActor->GetWearableStaticMesh();
			currTankItemData->AdditionalStaticMeshes = theTankItemActor->GetAdditionalStaticMeshes();
			currTankItemData->SkeletalMesh = theTankItemActor->GetWearableSkeletalMesh();

			return currTankItemData;
		}

		UNeuralTanksUtils::WriteToLog("ATankItem::CreateDataFromTankItemActor() Error: Failed to allocate data object for item.");
	}

	UNeuralTanksUtils::WriteToLog("ATankItem::CreateDataFromTankItemActor() Error: Failed to add item to inventory, item was null.");
	return NULL;
}

/// <summary>
/// Returns the amount of money it cost for the item in the shop. 
/// (Some items like TankAmmunition cannot be purchased).
/// </summary>
/// <returns>The amount of money it cost for the item in the shop.</returns>
int ATankItem::GetItemCost() const
{
	return myItemCost;
}

/// <summary>
/// Returns the landscape height at a given X,Y location. If not using a landscape
/// or failure, the current actor location's Z value is returned.
/// </summary>
/// <param name="location">The world X,Y location.</param>
/// <returns>The landscape height at a given X,Y location. If not using a landscape or failure, the current actor location's Z value is returned.</returns>
float ATankItem::GetLandscapeHeightAtLocation(FVector location) const
{
	FVector currLocation = GetActorLocation();
	TOptional<float> currHeight;

	if (myLandscape != NULL)
	{
		currHeight = myLandscape->GetHeightAtLocation(currLocation);
		if (currHeight.IsSet() == true)
		{
			return currHeight.GetValue();
		}
	}

	return currLocation.Z;
}

/// <summary>
/// Returns the category/type for this item.
/// </summary>
/// <returns>The category/type for this item.</returns>
EItemType ATankItem::GetItemType() const
{
	return myItemType;
}

/// <summary>
/// Returns the equipment slot/location that this item is attached to.
/// </summary>
/// <returns>The equipment slot/location that this item is attached to.</returns>
EEquipmentSlot ATankItem::GetEquipmentSlot() const
{
	return myEquipmentSlot;
}

/// <summary>
/// Returns the name of the item.
/// </summary>
/// <returns>The name of the item.</returns>
FString ATankItem::GetItemName() const
{
	return myItemName;
}

/// <summary>
/// Returns the description of the item (typically used in the inventory).
/// </summary>
/// <returns>The description of the item (typically used in the inventory).</returns>
FText ATankItem::GetItemDescription() const
{
	return myItemDescription;
}

/// <summary>
/// Returns the image used to represent this item in the inventory.
/// </summary>
/// <returns>The image used to represent this item in the inventory.</returns>
UTexture2D* ATankItem::GetInventoryTexture() const
{
	return myInventoryTexture;
}

/// <summary>
/// Returns the static mesh component of the item (if one exists). This is the game world representation.
/// </summary>
/// <returns>The static mesh component of the item (if one exists). This is the game world representation.</returns>
UStaticMeshComponent* ATankItem::GetStaticMeshComponent() const
{
	return myStaticMeshComponent;
}

/// <summary>
/// Returns the static mesh applied to the player pawn's corresponding component(s).
/// This is septate mostly because myStaticMeshComponent is not available at the time 
/// of equipment loading.
/// </summary>
/// <returns>The static mesh applied to the player pawn's corresponding component(s).</returns>
UStaticMesh* ATankItem::GetWearableStaticMesh() const
{
	return myWearableStaticMesh;
}

/// <summary>
/// Returns the skeletal mesh applied to the player pawn's corresponding component(s).
/// This is typically used for the Tank Boobs. 
/// </summary>
/// <returns>The skeletal mesh applied to the player pawn's corresponding component(s).</returns>
USkeletalMesh* ATankItem::GetWearableSkeletalMesh() const
{
	return myWearableSkeletalMesh;
}

/// <summary>
/// Returns the additional meshes associated with this item but are hidden so the player does not have to manually equip them.
/// For example, the Left/Right LMG/LMG Mounts that need setting when the turret is changed.
/// </summary>
/// <returns>Additional meshes associated with this item but are hidden so the player does not have to manually equip them.</returns>
TMap<FString, UStaticMesh*> ATankItem::GetAdditionalStaticMeshes() const
{
	return myAdditionalStaticMeshes;
}

/// <summary>
/// Changes this item's price/cost in the store to the provided itemCost.
/// </summary>
/// <param name="itemCost">The new price/cost of this item.</param>
void ATankItem::SetItemCost(const int itemCost)
{
	myItemCost = itemCost;
}

/// <summary>
/// Changes this item's category/type to the provided itemType.
/// </summary>
/// <param name="itemType">The new category/type for this item.</param>
void ATankItem::SetItemType(const EItemType itemType)
{
	myItemType = itemType;
}

/// <summary>
/// Changes this item's name to the provided itemName.
/// </summary>
/// <param name="itemName">The new item name for this item.</param>
void ATankItem::SetItemName(const FString& itemName)
{
	myItemName = itemName;
}

/// <summary>
/// Changes this item's description to the provided itemDescription.
/// (This text is localized).
/// </summary>
/// <param name="itemDescription">The new item description for this item.</param>
void ATankItem::SetItemDescription(const FText& itemDescription)
{
	myItemDescription = itemDescription;
}
