// Project Lab - NHTV Igad

#include "UnrealProject.h"
#include "SkillTreeWidget.h"
#include "SkillTreeObject.h"
#include "BaseSkillObject.h"
#include "HexagonTile.h"
#include "ScrollBox.h"
#include "SkillWidget.h"
#include "CanvasPanel.h"
#include "PlayerControllerBase.h"
#include "DefaultGameInstance.h"
#include "CharacterSettings.h"
#include "SlateBlueprintLibrary.h"
#include "ToolTipWidget.h"
#include "AbilityInfo.h"
#include "SizeBorder.h"
#include "WidgetLayoutLibrary.h"

int32 roundToNearestOdd1(float a_in)
{
	return 2 * FMath::FloorToInt((a_in / 2) + 0.5f) - 2;
}

int32 roundToNearestEven1(float a_in)
{
	return 2 * FMath::FloorToInt(a_in / 2);
}

void USkillTreeWidget::NativeOnSkillTreeChanged(ESkillTreeChangeEvent event, UBaseSkillObject* relevantObject)
{
	// Recalculate dominantDamageType
	int32_t categoryCounters[] = { 0,0,0 };
	int32_t largest = 0;
	for(USkillWidget* skill : m_skillWidgets)
	{
		if(skill->selectedEffect >= 0 && skill->skillAsset)
		{
			if(skill->selectedEffect >= skill->skillAsset->abilityEffects.Num())
			{
				GERROR("CORRUPT SKILL TREE");
				continue;
			}

			UAbilityInfo* info = skill->skillAsset->abilityEffects[skill->selectedEffect];
			int32_t& myCounter = categoryCounters[(size_t)info->abilityCategory];
			myCounter++;

			if(myCounter >= largest)
			{
				largest = myCounter;
				dominantAbilityCategory = info->abilityCategory;
			}
		}
	}

	onSkillTreeChanged.Broadcast(event, relevantObject);
}

USkillTreeWidget::USkillTreeWidget(const FObjectInitializer& init)
	: Super( init )
{
	m_interactive = true;
	m_shown = false;
	m_lastFocusedWidget = nullptr;
	isValid = false;
	placedSkillHexMap = new FHexMap();
	for (int32 x = 0; x < 13; x++)
	{
		placedSkillHexMap->rawdata[x] = 0;
	}
}

void USkillTreeWidget::NativeConstruct()
{
	Super::NativeConstruct();

	UpdateVisibility();

	// Get the tooltip widget
	m_toolTipWidget = Cast<UToolTipWidget>(WidgetTree->FindWidget("ToolTipSWidget"));

	m_sizeBorder = Cast<USizeBorder>(WidgetTree->FindWidget("SizeBorder"));

	if ( !skillTreeAsset ) return;
	TArray<UWidget*> widgets;
	WidgetTree->GetAllWidgets( widgets );
	UScrollBox* scrollBox = NULL;
	m_skillTreeCanvas = WidgetTree->FindWidget<UCanvasPanel>("Main_SkillTree");
	//m_focusBorder = WidgetTree->FindWidget<UBorder>("FocusBorder");
	m_inputCapture = WidgetTree->FindWidget<UBorder>("InputCapture");
	m_inputCapture->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
	for ( int32 i = 0; i < widgets.Num(); i++ )
	{
		UHexagonTile* tmpHex = Cast<UHexagonTile>( widgets[i] );
		UScrollBox* tmpScrollBox = Cast<UScrollBox>( widgets[i] );
		//USkillWidget* tmpSkill = Cast<USkillWidget>( widgets[i] );
		if ( tmpScrollBox ) scrollBox = tmpScrollBox;
		//if ( tmpSkill ) m_selectedSkill = tmpSkill;
		if ( !tmpHex ) continue;
		tiles.Push( tmpHex );
		bool test = skillTreeAsset->hexMap.Get( tmpHex->x, tmpHex->y );
		if ( test ) tmpHex->SetVisibility( ESlateVisibility::Hidden );
	}
	if ( m_selectedSkill )
	{
		m_selectedSkill->parent = this;
		m_selectedSkill->SetDragable( true );
	}
	else
	{
		YWARNING( "No Selected Skill found!" );
	}
}
void USkillTreeWidget::NativeDestruct()
{
	delete placedSkillHexMap;
	Super::NativeDestruct();
}

void USkillTreeWidget::NativeTick(const FGeometry& MyGeometry, float DeltaTime)
{
	Super::NativeTick(MyGeometry, DeltaTime);

	//if ((m_focusBorder && m_focusBorder->IsHovered()) || draggingWidget)
	//{
	//	//m_inputCapture->SetVisibility(ESlateVisibility::Visible);
	//}
	//else
	//{
	//	//m_inputCapture->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
	//}

	if (m_toolTipWidget)
	{
		m_toolTipWidget->showTooltip = false;

		FVector2D mousePos;
		UWidgetLayoutLibrary::GetMousePositionScaledByDPI(GetOwningPlayer(), mousePos.X, mousePos.Y);
		FIntPoint mouseGridPos = GetGridIndex(mousePos);
		USkillWidget** sw = tileMap.Find(mouseGridPos);
		USkillWidget* skill = sw ? *sw : nullptr;
		if (skill)
		{
			// Reset hovered state
			UAbilityInfo* info = skill->GetSelectedEffectAbility();
			if (info)
			{
				m_toolTipWidget->SetTitle(info->name);
				m_toolTipWidget->SetText(info->description);
				m_toolTipWidget->position = skill->tooltipAreaPosition;
				m_toolTipWidget->size = skill->tooltipAreaSize;
				m_toolTipWidget->showTooltip = true;
			}
			skill->hovered = true;
		}
		if (m_lastFocusedWidget && m_lastFocusedWidget != skill)
		{
			m_lastFocusedWidget->hovered = false;
		}
		m_lastFocusedWidget = skill;
	}
}

FReply USkillTreeWidget::NativeOnMouseButtonUp(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
	if(!IsInteractive())
		return FReply::Handled();
	//GWPRINT(L"Skill tree up");
	if(draggingWidget && !draggingWidget->IsUsingController())
	{
		draggingWidget->StopDragging();
		return FReply::Handled();
	}
	return FReply::Handled();
}
FReply USkillTreeWidget::NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
	if(!IsInteractive())
		return FReply::Handled();
	//GWPRINT(L"Skill tree down");
	FKey button = InMouseEvent.GetEffectingButton();
	if(button == EKeys::LeftMouseButton)
	{
		if(draggingWidget && !draggingWidget->IsUsingController())
		{
			draggingWidget->StopDragging();
			return FReply::Handled();
		}
		else
		{
			if(m_lastFocusedWidget)
			{
				if(m_lastFocusedWidget->hovered)
					m_lastFocusedWidget->StartDragging();
			}
		}
	}
	else if(button == EKeys::RightMouseButton)
	{
		if(!draggingWidget && m_lastFocusedWidget)
		{
			if(m_lastFocusedWidget->hovered)
			{
				m_lastFocusedWidget->OnRequireSkillEffect();
			}
		}
	}

	return FReply::Handled();
}
FReply USkillTreeWidget::NativeOnMouseWheel(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
	//GWPRINT(L"USkillTreeWidget MouseWheel " + InMouseEvent.GetWheelDelta());
	if (draggingWidget)
	{
		float add = 60.0f * FMath::FloorToFloat(InMouseEvent.GetWheelDelta());
		draggingWidget->SetSkillRotation(add + draggingWidget->GetSkillRotation());
	}
	return FReply::Unhandled();
}

void USkillTreeWidget::SetIsInteractive(bool interactive)
{
	m_interactive = interactive;
	UpdateVisibility();
}
void USkillTreeWidget::UpdateVisibility()
{
	if (m_shown)
	{
		SetVisibility(ESlateVisibility::SelfHitTestInvisible);
	}
	else
	{
		SetVisibility(ESlateVisibility::Hidden);
	}
	UImage* validImage = WidgetTree->FindWidget<UImage>("Validation_Image");
	if (m_interactive)
	{
		validImage->SetVisibility(ESlateVisibility::Visible);
	}
	else
	{
		validImage->SetVisibility(ESlateVisibility::Hidden);
	}
}

void WalkTree(TMap<USkillWidget*, TArray<USkillWidget*>>* a_map, TArray<USkillWidget*>* a_checked, TArray<USkillWidget*> a_nodes)
{
	for (int32 i = 0; i < a_nodes.Num(); i++)
	{
		if (a_checked->Contains(a_nodes[i])) continue;
		a_checked->Push(a_nodes[i]);
		TArray<USkillWidget*>* connected = a_map->Find(a_nodes[i]);
		if (!connected)
		{
			YERROR("Could not find the connected skills.");
			continue;
		}
		WalkTree(a_map, a_checked, *connected);
	}
}

bool USkillTreeWidget::ValidateSkillTree()
{
	bool rootResult = false;
	bool connectResult = true;
	if (m_skillWidgets.Num() == 0) return true;
	TMap<USkillWidget*, TArray<USkillWidget*>> connectedSkillsMap;
	USkillWidget* rootSkill = NULL;
	for (int32 i = 0; i < m_skillWidgets.Num(); i++)
	{
		TArray<FIntPoint> points = m_skillWidgets[i]->GetPoints();
		if (!rootResult)
		{
			bool rootTest = m_skillWidgets[i]->ContainsRoot(points);
			if (rootTest)
			{
				rootResult = true;
				rootSkill = m_skillWidgets[i];
			}
		}
		TArray<USkillWidget*> connectedSkills = m_skillWidgets[i]->ConnectedSkills(points);
		connectedSkillsMap.Add(m_skillWidgets[i], connectedSkills);
		if ( connectedSkills.Num() == 0 )
		{
			connectResult = false;
			break;
		}
	}
	if (connectResult && rootResult)
	{
		if (!rootSkill)
		{
			YERROR("Root Skill is NULL!");
			return false;
		}
		TArray<USkillWidget*> checkedSkills;
		checkedSkills.Push(rootSkill);
		TArray<USkillWidget*>* connected = connectedSkillsMap.Find(rootSkill);
		if (!connected)
		{
			YPRINT("Root Skill is not attached to any skills!");
			return false;
		}
		WalkTree(&connectedSkillsMap, &checkedSkills, *connected);
		if (m_skillWidgets.Num() != checkedSkills.Num())
			connectResult = false;
		else
		{
			for (int32 i = 0; i < m_skillWidgets.Num(); i++)
			{
				if (!checkedSkills.Contains(m_skillWidgets[i]))
				{
					connectResult = false;
				}
			}
		}
	}
	if (m_skillWidgets.Num() == 1 && rootResult) 
		isValid = true;
	else
		isValid = (rootResult && connectResult);
	if (isValid)
	{
		UpdateLevel(10.0f);
	}
	else
	{
		UpdateLevel(0.0f);
	}
	return isValid;
}

FSkillTreeState USkillTreeWidget::GetState()
{
	FSkillTreeState state;
	for(int32 i = 0; i < m_skillWidgets.Num(); i++)
	{
		FSkillTreeStateObject obj;
		USkillWidget* skill = m_skillWidgets[i];
		obj.gridIndex = skill->GetGridIndex() /*- FVector2D(1.5,4) * m_skillTreeCanvas->RenderTransform.Scale.X*/;
		obj.placedPoints = skill->placedPoints;
		obj.skillObject = skill->skillAsset->GetClass();
		obj.rotation = skill->GetSkillRotation();
		obj.selectedEffect = skill->selectedEffect;
		state.objects.Add(obj);

		//GWPRINT(L"Saving gridIndex " + obj.gridIndex);
		//for(int32 i = 0; i < obj.placedPoints.Num(); i++)
		//{
		//	GWPRINT(L"Saving Placed point " + i + L" " + obj.placedPoints[i].X + L", " + obj.placedPoints[i].Y);
		//}
	}

	return state;
}

TArray<class UBaseSkillObject*> USkillTreeWidget::GetApearanceState()
{
	return m_skillClasses;
}

void USkillTreeWidget::Clear()
{
	// Remove all placed hexes
	auto arrayCopy = m_skillWidgets;
	for(int32 i = 0; i < arrayCopy.Num(); i++)
	{
		arrayCopy[i]->RemoveFromParent();
		RemoveSkill(arrayCopy[i], arrayCopy[i]->placedPoints);
	}
	m_skillWidgets.SetNum(0);
	m_skillClasses.SetNum(0);
	tileMap.Reset();

	// Clear Hex map
	for(int32 x = 0; x < 13; x++)
	{
		placedSkillHexMap->rawdata[x] = 0;
	}

	isValid = true;

	// Trigger Callbacks
	NativeOnSkillTreeChanged(ESkillTreeChangeEvent::Cleared, nullptr);
}
void USkillTreeWidget::BuildFromState(const FSkillTreeState& state)
{
	Clear();
	for(int32 i = 0; i < state.objects.Num(); i++)
	{
		const FSkillTreeStateObject& obj = state.objects[i];
		UBaseSkillObject* baseSkill = obj.skillObject->GetDefaultObject<UBaseSkillObject>();

		if(!baseSkill)
			continue;

		//GWPRINT(L"gridIndex " + obj.gridIndex);
		//for(int32 i = 0; i < obj.placedPoints.Num(); i++)
		//{
		//	GWPRINT(L"Placed point " + i + L" " + obj.placedPoints[i].X + L", " + obj.placedPoints[i].Y);
		//}

		m_selectedSkill = CreateWidget<USkillWidget>(GetWorld(), WidgetTemplate);
		m_selectedSkill->skillAsset = baseSkill;
		m_selectedSkill->parent = this;
		if(m_skillTreeCanvas)
			m_selectedSkill->SetRenderScale(m_skillTreeCanvas->RenderTransform.Scale);
		m_selectedSkill->SetPlaced();
		if(m_skillTreeCanvas)
			m_skillTreeCanvas->GetParent()->AddChild(m_selectedSkill);
		UCanvasPanelSlot* slot = Cast<UCanvasPanelSlot>(m_selectedSkill->Slot);
		if(slot)
		{
			slot->SetAutoSize(true);
			slot->SetAlignment(FVector2D(-0.5f, -0.5f));
		}
		m_selectedSkill->SetSelectedEffect(obj.selectedEffect);
		m_selectedSkill->placedPoints = obj.placedPoints;
		m_selectedSkill->SetSkillRotation(obj.rotation);
		m_selectedSkill->PlaceOnGridIndex(obj.gridIndex);
		AddSkill(m_selectedSkill, obj.placedPoints);
	}
}

void USkillTreeWidget::RemoveSkill(UBaseSkillObject* skillObject)
{
	int32 skillID = m_skillClasses.Find(skillObject);
	if (skillID == INDEX_NONE) return;
	CancelSkill(m_skillWidgets[skillID]);
	m_skillWidgets[skillID]->Remove();
}

bool USkillTreeWidget::IsUsingSkill(UBaseSkillObject* skillObject) const
{
	return m_skillClasses.Contains(skillObject);
}

USkillWidget* USkillTreeWidget::GetUsedSkill(class UBaseSkillObject* skillObject)
{
	for (int32 i = 0; i < m_skillWidgets.Num(); i++)
	{
		if (m_skillWidgets[i]->skillAsset == skillObject)
			return m_skillWidgets[i];
	}
	return nullptr;
}

void USkillTreeWidget::Save(int32 saveSlot)
{
	UDefaultGameInstance* inst = Cast<UDefaultGameInstance>(GetGameInstance());
	UCharacterSettings* settings = inst->GetCharacterSettings();

	if (saveSlot < 0 || saveSlot >= settings->characterSaves.Num())
	{
		JERROR("Invalid save slot");
		return;
	}

	settings->characterSaves[saveSlot].skillTreeState = GetState();
	inst->SaveSettings();
}
void USkillTreeWidget::Load(int32 saveSlot)
{
	UDefaultGameInstance* inst = Cast<UDefaultGameInstance>(GetGameInstance());
	UCharacterSettings* settings = inst->GetCharacterSettings();

	if (saveSlot < 0 || saveSlot >= settings->characterSaves.Num())
	{
		JERROR("Invalid save slot");
		return;
	}
	
	BuildFromState(settings->characterSaves[saveSlot].skillTreeState);
	ValidateSkillTree();
}

FIntPoint USkillTreeWidget::GetGridIndex(FVector2D in) const
{
	FVector2D result;
	UCanvasPanelSlot* tmpSlot = UWidgetLayoutLibrary::SlotAsCanvasSlot(m_skillTreeCanvas);
	float viewportScale = UWidgetLayoutLibrary::GetViewportScale(const_cast<USkillTreeWidget*>(this));
	FVector2D topLeft;
	float scale = 32.0f  * (m_skillTreeCanvas->RenderTransform.Scale.X / 2);
	float XScale = (scale * 1.73205f);
	float YScale = scale;
	if (tmpSlot)
	{
		FVector2D screenTopRight = FVector2D(GEngine->GameViewport->Viewport->GetSizeXY().X * tmpSlot->GetAnchors().Minimum.X, GEngine->GameViewport->Viewport->GetSizeXY().Y * tmpSlot->GetAnchors().Minimum.Y);
		FVector2D offset = tmpSlot->GetPosition() * viewportScale;
		FVector2D widgetTopLeft = ((tmpSlot->GetSize() * m_skillTreeCanvas->RenderTransform.Scale.X * viewportScale) / 2);
		topLeft = screenTopRight + offset - widgetTopLeft;
		topLeft /= viewportScale;
		//topLeft.X -= 16.0f;
	}

	float XPreFloor = (in.X - topLeft.X) / XScale;
	float YPreFloor = (in.Y - topLeft.Y) / YScale;
	int32 XIndex = FMath::FloorToInt(XPreFloor);
	int32 YIndex = FMath::FloorToInt(YPreFloor);
	/*FVector2D magicOffset = FVector2D(1.5, 4) * m_skillTreeCanvas->RenderTransform.Scale.X;*/
	bool odd = (XIndex % 2) == 1;
	if (odd)
		YIndex = roundToNearestOdd1(YIndex);
	else
		YIndex = roundToNearestEven1(YIndex);
	return FIntPoint(XIndex, (YIndex) / 2.0f);
}

void USkillTreeWidget::StartDragging(USkillWidget* widget)
{
	check(draggingWidget == nullptr);
	draggingWidget = widget;
	m_inputCapture->SetVisibility(ESlateVisibility::Visible);
}

void USkillTreeWidget::StopDragging(USkillWidget* widget)
{
	if(draggingWidget != widget)
	{
		GERROR("Calling StopDragging twice");
	}
	draggingWidget = nullptr;
	m_inputCapture->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
}

void USkillTreeWidget::UpdateTextInfo_Implementation()
{
}

void USkillTreeWidget::AddSkill(USkillWidget* a_skill, const TArray<FIntPoint>& a_points)
{
	for ( int32 i = 0; i < a_points.Num(); i++ )
	{
		placedSkillHexMap->Set( a_points[i].X, a_points[i].Y, true );

		tileMap.Add(a_points[i], a_skill);
	}

	UWorld* const world = GetWorld();
	if ( !world )
	{
		YWARNING( "Couldn't get the World." );
		return;
	}
	APlayerControllerBase* const controller = Cast<APlayerControllerBase>( GetOwningPlayer() );
	if ( !controller )
	{
		YWARNING( "Couldn't get the Player." );
		return;
	}
	controller->OnLearnSkill( a_skill->skillAsset );
	m_skillWidgets.Add(a_skill);
	m_skillClasses.Add(a_skill->skillAsset);

	// Trigger Callbacks
	NativeOnSkillTreeChanged(ESkillTreeChangeEvent::Added, a_skill->skillAsset);

	ValidateSkillTree();
}

void USkillTreeWidget::RemoveSkill( USkillWidget* a_skill, const TArray<FIntPoint>& a_points )
{
	m_skillClasses.Remove(a_skill->skillAsset);
	m_skillWidgets.Remove(a_skill);
	for ( int32 i = 0; i < a_points.Num(); i++ )
	{
		placedSkillHexMap->Set( a_points[i].X, a_points[i].Y, false );
		tileMap.Remove(a_points[i]);
	}

	UWorld* const world = GetWorld();
	if ( !world )
	{
		YWARNING( "Couldn't get the World." );
		return;
	}
	APlayerControllerBase* const controller = Cast<APlayerControllerBase>( GetOwningPlayer() );
	if ( !controller )
	{
		YWARNING( "Couldn't get the Player." );
		return;
	}
	controller->OnUnlearnSkill( a_skill->skillAsset );

	// Trigger Callbacks
	NativeOnSkillTreeChanged(ESkillTreeChangeEvent::Removed, a_skill->skillAsset);
}

USkillWidget* USkillTreeWidget::NewSkill( USkillWidget* a_skill, bool controller )
{
	return NewSkillFromAsset(a_skill->skillAsset, controller);
}

class USkillWidget* USkillTreeWidget::NewSkillFromAsset(UBaseSkillObject* skillAsset, bool controller /*= false*/)
{
	if(draggingWidget)
		return NULL;
	//a_skill->Disable();
	m_selectedSkill = CreateWidget<USkillWidget>(GetWorld(), WidgetTemplate);
	m_selectedSkill->skillAsset = skillAsset;
	m_selectedSkill->parent = this;
	m_selectedSkill->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
	m_selectedSkill->SetDragable(true, controller);
	m_selectedSkill->SetRenderScale(m_skillTreeCanvas->RenderTransform.Scale);
	m_skillTreeCanvas->GetParent()->AddChild(m_selectedSkill);
	m_selectedSkill->SetUserFocus(GetOwningPlayer());
	UCanvasPanelSlot* slot = Cast<UCanvasPanelSlot>(m_selectedSkill->Slot);
	if(slot)
	{
		slot->SetAutoSize(true);
		slot->SetAlignment(FVector2D(-0.5f, -0.5f));
	}

	if(controller)
	{
		// Set initial position
		m_selectedSkill->MoveSkillAbsolute(lastGridIndex);
	}

	return m_selectedSkill;
}

void USkillTreeWidget::CancelSkill(USkillWidget* a_skill)
{
	a_skill->RemoveFromParent();
	ValidateSkillTree();
}

void USkillTreeWidget::Show()
{
	m_shown = true;
	UpdateVisibility();
}

void USkillTreeWidget::Hide()
{
	m_shown = false;
	UpdateVisibility();
}

void USkillTreeWidget::UpdateLevel(float level)
{
	for (int32 i = 0; i < m_skillWidgets.Num(); i++)
	{
		m_skillWidgets[i]->UpdateLevel(level);
	}
	if (IsInteractable())
	{
		UpdateTextInfo();
	}
}

void USkillTreeWidget::UpdateInfo(float skilltreeHeight, float skilltreeOffset)
{
	for (int32 i = 0; i < m_skillWidgets.Num(); i++)
	{
		m_skillWidgets[i]->UpdateInfo(skilltreeHeight, skilltreeOffset);
	}
}

/*
void USkillTreeWidget::NativeTick(const FGeometry& MyGeometry, float DeltaTime)
{
	FVector2D minPos;
	FVector2D maxPos;
	FVector2D viewportPos;
	USlateBlueprintLibrary::LocalToViewport(this, MyGeometry, MyGeometry.GetLocalSize(), maxPos, viewportPos);
	USlateBlueprintLibrary::LocalToViewport(this, MyGeometry, FVector2D(0,0), minPos, viewportPos);
	FVector2D viewportSize = maxPos - minPos;
	for (int32 i = 0; i < m_skillWidgets.Num(); i++)
	{
		m_skillWidgets[i]->UpdateInfo(viewportSize.Y, minPos.Y);
	}
}
*/