// Fill out your copyright notice in the Description page of Project Settings.


#include "InventoryComponent.h"

#include "BaseItem.h"
#include "Camera/CameraComponent.h"


// Sets default values for this component's properties
UInventoryComponent::UInventoryComponent()
{
	// Set this component to be initialized when the game starts, and to be ticked every frame.  You can turn these features
	// off to improve performance if you don't need them.
	bWantsInitializeComponent = true;
	PrimaryComponentTick.bCanEverTick = true;
}

void UInventoryComponent::InitializeComponent()
{
	Super::InitializeComponent();
	PlayerCharacter = GetOwner();
	ItemToPickup = nullptr;
}

// Called when the game starts
void UInventoryComponent::BeginPlay()
{
	Super::BeginPlay();

	//FTimerHandle TraceTimerHandle;
	//GetWorld()->GetTimerManager().SetTimer(TraceTimerHandle, this, &UInventoryComponent::ProjectTraceForItem, 0.1f, true);
	InventoryItems.Init(nullptr, Columns * Rows);
}


// Called every frame
void UInventoryComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	if (IsDirty)
	{
		IsDirty = false;
		OnInventoryUpdated.Broadcast();
	}
}

bool UInventoryComponent::AddItem(UBaseItem* Item)
{
	if (!IsValid(Item)) return false;
	for (int i = 0; i < InventoryItems.Num(); i++)
	{
		if (IsRoomAvailable(Item, i))
		{
			AddItemAt(Item, i);
			return true;
		}
	}
	Item->RotateItem();
	for (int i = 0; i < InventoryItems.Num(); i++)
	{
		if (IsRoomAvailable(Item, i))
		{
			AddItemAt(Item, i);
			return true;
		}
	}
	Item->RotateItem();
	return false;
}

void UInventoryComponent::ProjectTraceForItem()
{
	if (!IsValid(PlayerCharacter)) return;
	FVector StartLocation = Cast<UCameraComponent>(PlayerCharacter->GetComponentByClass(UCameraComponent::StaticClass()))->GetComponentLocation();
	FVector EndLocation = StartLocation + Cast<UCameraComponent>(PlayerCharacter->GetComponentByClass(UCameraComponent::StaticClass()))->GetForwardVector() * MaxPickupDistance;
	FCollisionQueryParams CollisionQueryParams;
	CollisionQueryParams.AddIgnoredActor(PlayerCharacter);
	if (FHitResult HitResult; GetWorld()->LineTraceSingleByChannel(HitResult, StartLocation, EndLocation, ECC_Visibility, CollisionQueryParams))
	{
		if (Cast<UBaseItem>(HitResult.GetActor()))
		{
			ItemToPickup = HitResult.GetActor();
			Cast<UStaticMeshComponent>(ItemToPickup->GetComponentByClass(UStaticMeshComponent::StaticClass()))->SetRenderCustomDepth(true);
			return;
		}
	}
	if (!IsValid(ItemToPickup)) return;
	Cast<UStaticMeshComponent>(ItemToPickup->GetComponentByClass(UStaticMeshComponent::StaticClass()))->SetRenderCustomDepth(false);
}

void UInventoryComponent::Pickup()
{
	//TODO: Add pickup logic and create pickup Actor
	if (!IsValid(ItemToPickup)) return;
}

bool UInventoryComponent::IsRoomAvailable(UBaseItem* Item, const int TopLeftIndex)
{
	for (int i = IndexToTile(TopLeftIndex).X; i < IndexToTile(TopLeftIndex).X + Item->ItemSize.X; i++)
	{
		for (int j = IndexToTile(TopLeftIndex).Y; j < IndexToTile(TopLeftIndex).Y + Item->ItemSize.Y; j++)
		{
			FInventoryTile TileToCheck;
			TileToCheck.X = i;
			TileToCheck.Y = j;
			if (!IsTileValid(TileToCheck)) return false;
			TTuple<UBaseItem*, bool> ItemAtIndex = GetItemAtIndex(TileToIndex(TileToCheck));
			if (ItemAtIndex.Get<1>()) return false;
			if (IsValid(ItemAtIndex.Get<0>())) return false;
		}
	}
	return true;
}

FInventoryTile UInventoryComponent::IndexToTile(const int Index) const
{
	FInventoryTile Tile;
	Tile.X = Index % Columns;
	Tile.Y = Index / Columns;
	return Tile;
}

TTuple<UBaseItem*, bool> UInventoryComponent::GetItemAtIndex(const int Index)
{
	if (!InventoryItems.IsValidIndex(Index)) return MakeTuple(nullptr, false);
	if (!IsValid(InventoryItems[Index])) return MakeTuple(nullptr, false);
	return MakeTuple(InventoryItems[Index], true);
}

int UInventoryComponent::TileToIndex(const FInventoryTile InventoryTile) const
{
	return InventoryTile.X + InventoryTile.Y * Columns;
}

void UInventoryComponent::AddItemAt(UBaseItem* Item, const int TopLeftIndex)
{
	for (int i = IndexToTile(TopLeftIndex).X; i < IndexToTile(TopLeftIndex).X + Item->ItemSize.X; i++)
	{
		for (int j = IndexToTile(TopLeftIndex).Y; j < IndexToTile(TopLeftIndex).Y + Item->ItemSize.Y; j++)
		{
			FInventoryTile TileToCheck;
			TileToCheck.X = i;
			TileToCheck.Y = j;
			if (!IsTileValid(TileToCheck)) return;
			InventoryItems[TileToIndex(TileToCheck)] = Item;
		}
	}
	IsDirty = true;
}

TMap<UBaseItem*, FInventoryTile> UInventoryComponent::GetAllItems()
{
	TMap<UBaseItem*, FInventoryTile> Items;
	for (int i = 0; i < InventoryItems.Num(); i++)
	{
		UBaseItem* Item = InventoryItems[i];
		if (!IsValid(Item)) continue;
		if (Items.Contains(Item)) continue;
		Items.Add(Item, IndexToTile(i));
	}
	return Items;
}

void UInventoryComponent::RemoveItem(UBaseItem* Item)
{
	if (!IsValid(Item)) return;
	for (int i = 0; i < InventoryItems.Num(); i++)
	{
		if (InventoryItems[i] == Item)
		{
			InventoryItems[i] = nullptr;
			IsDirty = true;
		}
	}
}

void UInventoryComponent::SpawnItem(UBaseItem* Item, FVector Location)
{
	if (!IsValid(Item)) return;
	if (!IsValid(Item->ItemActor)) return;
	GetWorld()->SpawnActor(Item->ItemActor, &Location);
}

void UInventoryComponent::UpdateInventorySize(const int _Columns, const int _Rows)
{
	Columns = _Columns;
	Rows = _Rows;
	InventoryItems.SetNum(Columns * Rows);
	OnInventorySizeUpdated.Broadcast();
}

void UInventoryComponent::SetPrimaryWeapon(AActor* const _PrimaryWeapon)
{
	PrimaryWeapon = _PrimaryWeapon;
}

AActor* UInventoryComponent::GetPrimaryWeapon() const
{
	return PrimaryWeapon;
}

void UInventoryComponent::SetSecondaryWeapon(AActor* const _SecondaryWeapon)
{
	SecondaryWeapon = _SecondaryWeapon;
}

AActor* UInventoryComponent::GetSecondaryWeapon() const
{
	return SecondaryWeapon;
}

bool UInventoryComponent::IsTileValid(const FInventoryTile InventoryTile) const
{
	if (InventoryTile.X >= 0 && InventoryTile.Y >= 0 && InventoryTile.X < Columns && InventoryTile.Y < Columns && InventoryTile.Y <= Rows)
	{
		return true;
	}
	return false;
}
