// Project Lab - NHTV Igad

#include "UnrealProject.h"
#include "DefaultPlayerController.h"
#include "IngameHUD.h"
#include "AbilityInfo.h"
#include "AbilityState.h"
#include "NetworkPlayer.h"
#include "HealthBar.h"
#include "CreatureSpawn.h"
#include "PlayerSlot.h"
#include "DefaultGameState.h"
#include "DefaultPlayerState.h"
#include "MiniMapWidget.h"
#include "ToolTipWidget.h"
#include "CombatText/CombatTextWidget.h"
#include "SpellInfoDisplay.h"
#include "ButtonBarSwitcher.h"
#include "BaseSkillObject.h"
#include "WidgetLayoutLibrary.h"
#include "NetworkGhost.h"
#include "TouhouBoss.h"
#include "MiniBossCreature.h"
#include "StatBar.h"
#include <algorithm>

static UClass* healthBarWidgetClass;
static UClass* playerSlotWidgetClass;

UIngameHUD::UIngameHUD(const FObjectInitializer& init)
	:Super(init)
{
	ConstructorHelpers::FClassFinder<UHealthBar> HealthBarWidgetCF(TEXT("/Game/Assets/GUI/WEEGEE_HealthBar"));
	healthBarWidgetClass = HealthBarWidgetCF.Class;

	miniMap = nullptr;
}

void UIngameHUD::NativeConstruct()
{
	m_myTeam = 0;
	m_character = nullptr;
	Super::NativeConstruct();

	m_spellInfoDisplay = Cast<USpellInfoDisplay>(WidgetTree->FindWidget("SpellInfoDisplay"));
	miniMap = Cast<UMiniMapWidget>(WidgetTree->FindWidget("Minimap"));
	toolTips = Cast<UToolTipWidget>(WidgetTree->FindWidget("ToolTipLayer"));
	combatText = Cast<UCombatTextWidget>(WidgetTree->FindWidget("CombatTextLayer"));
	buttonBarSwitcher = Cast<UButtonBarSwitcher>(WidgetTree->FindWidget("ButtonBarSwitcher"));
	healthBarLayer = Cast<UCanvasPanel>(WidgetTree->FindWidget("HealthBarLayer"));

	// Find stat display items
	m_playerStatDisplay.hpBar = Cast<UStatBar>(WidgetTree->FindWidget("HP"));
	m_playerStatDisplay.manaBar = Cast<UStatBar>(WidgetTree->FindWidget("Mana"));
	m_playerStatDisplay.expBar = Cast<UExperienceBar>(WidgetTree->FindWidget("Exp"));
	m_allyStatDisplay.hpBar = Cast<UStatBar>(WidgetTree->FindWidget("HP1"));
	m_allyStatDisplay.manaBar = Cast<UStatBar>(WidgetTree->FindWidget("Mana1"));
	m_allyStatDisplay.name = Cast<UTextBlock>(WidgetTree->FindWidget("Name1"));
	m_InitStatDisplay(m_playerStatDisplay);
	m_InitStatDisplay(m_allyStatDisplay);

	check(healthBarLayer);
}
void UIngameHUD::NativeDestruct()
{
	Super::NativeDestruct();
}
void UIngameHUD::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{
	Super::NativeTick(MyGeometry, InDeltaTime);
	auto p = m_healthBars.CreateIterator();
	while (p)
	{
		m_UpdateHealthBar(p.Key(), p.Value());
		++p;
	}

	ADefaultPlayerState* ps =  Cast<ADefaultPlayerState>(GetOwningPlayer()->PlayerState);
	if (IsValid(ps))
	{
		m_UpdateStatDisplay(ps, m_playerStatDisplay);
		m_UpdateStatDisplay(ps->teamMate, m_allyStatDisplay);
	}

	//UWorld* world = GetWorld();
	//check(world);
	//ADefaultGameState* state = Cast<ADefaultGameState>(world->GetGameState());
}

void UIngameHUD::NativeOnAssignCharacter(class ANetworkPossessable* pawn /*= nullptr*/, TArray<FIngameSkillTreeSkill> skillsetPrototype /*= TArray<FIngameSkillTreeSkill>()*/)
{
	m_character = pawn;

	// Cast to ghost or player
	ANetworkPlayer* player = Cast<ANetworkPlayer>(pawn);
	ANetworkGhost* ghost = Cast<ANetworkGhost>(pawn);
	if(ghost)
	{
		// Grey out buttons in ghost mode
		buttonBarSwitcher->SetGlobalEnable(false);
	}
	else if(player)
	{
		// Enable buttons back in player mode
		buttonBarSwitcher->SetGlobalEnable(true);

		// Set skill prototypes
		if(buttonBarSwitcher)
		{
			for(int32 i = 0; i < skillsetPrototype.Num(); i++)
			{
				// Don't assign buttons for passive skills
				if(skillsetPrototype[i].selectedEffect->passive)
					continue;

				switch(skillsetPrototype[i].abilityType)
				{
				case 0:
					buttonBarSwitcher->AssignSkillButton(skillsetPrototype[i], false);
					break;
				case 1:
					buttonBarSwitcher->AssignSkillButton(skillsetPrototype[i], true);
					break;
				case 2:
					if(skillsetPrototype[i].selectedEffect && !skillsetPrototype[i].selectedEffect->passive)
					{
						GERROR("Non-passive skill added to skill with shape type == passive, " +
							skillsetPrototype[i].skillObject->GetName() + " <= " +
							skillsetPrototype[i].selectedEffect->GetName());
					}
				}
			}
		}
		m_myTeam = player->GetTeam();
	}

	OnAssignCharacter(pawn);
}

void UIngameHUD::OnLevelUp(TArray<FIngameSkillTreeSkill> updatedSkills)
{
	if (m_spellInfoDisplay && GetPlayer())
		m_spellInfoDisplay->OnLevelUp(GetPlayer(), updatedSkills);

	if (buttonBarSwitcher)
	{
		for (int32 i = 0; i < updatedSkills.Num(); i++)
		{
			buttonBarSwitcher->SetSkillButtonEnabled(updatedSkills[i], true);
		}
	}
}

void UIngameHUD::UpdateCooldowns(TArray<class AAbilityState*> abilityStates, ANetworkPlayer* player)
{
	if (buttonBarSwitcher)
		buttonBarSwitcher->UpdateCooldowns(abilityStates, player);
}

void UIngameHUD::OnCharacterCreated(ANetworkCharacter* character)
{
	if (m_healthBars.Find(character))
	{
		// When OnCharacterCreated is called twice
		GWWARNING(L"OnCharacterCreated called more thatn once");
		return;
	}
	UHealthBar* widget = CreateWidget<UHealthBar>(GetOwningPlayer(), healthBarWidgetClass);
	check(widget);
	healthBarLayer->AddChild(widget);
	m_healthBars.Add(character, widget);
	m_UpdateHealthBar(character, widget);
}
void UIngameHUD::OnCharacterDestroyed(ANetworkCharacter* character)
{
	auto it = m_healthBars.Find(character);
	if (it)
	{
		(*it)->RemoveFromViewport();
		m_healthBars.Remove(character);
	}
	else
	{
		// When OnCharacterDestroyed is called twice
		GWWARNING(L"OnCharacterDestroyed called more than once");
	}
}

ANetworkPlayer* UIngameHUD::GetTeamMate() const
{
	if (Cast<ANetworkPlayer>(m_character))
	{
		ANetworkPlayer* teamMate = Cast<ANetworkPlayer>(m_character)->GetTeamMate();
		return teamMate;
	}
	return nullptr;
}
ANetworkPlayer* UIngameHUD::GetPlayer() const
{
	return Cast<ANetworkPlayer>(m_character);
}
ANetworkGhost* UIngameHUD::GetGhost() const
{
	return Cast<ANetworkGhost>(m_character);
}

class ADefaultPlayerState* UIngameHUD::GetPlayerState() const
{
	if (!m_character)
		return nullptr;
	return Cast<ADefaultPlayerState>(m_character->PlayerState);
}

void UIngameHUD::m_UpdateHealthBar(class ANetworkCharacter* character, class UHealthBar* bar)
{
	APlayerController* pc = GetOwningPlayer();
	if (!IsValid(pc)) return;

	FVector screenPos;
	const float height = character->GetCapsuleComponent()->GetScaledCapsuleHalfHeight();
	const FVector position = character->GetActorLocation();
	const FVector worldLocation = FVector(position.X, position.Y, position.Z + height);

	// Check if is approximately in camera view
	const FVector forward = pc->PlayerCameraManager->GetCameraRotation().RotateVector(FVector(1.0f, 0.0f, 0.0f));
	const FVector dir = (worldLocation - pc->PlayerCameraManager->GetCameraLocation()).GetSafeNormal();
	float dot = FVector::DotProduct(forward, dir);
	if (dot < 0.01f)
	{
		bar->SetVisibility(ESlateVisibility::Hidden);
		return;
	}
	bar->SetVisibility(ESlateVisibility::Visible);

	pc->ProjectWorldLocationToScreenWithDistance(FVector(position.X, position.Y, position.Z + height), screenPos);
	const float viewportScale = UWidgetLayoutLibrary::GetViewportScale(healthBarLayer);

	uint32 otherTeam = character->GetTeam();

	// Check witch bar style to use
	bool useExtendedBarStyle = character->playerName.Len() > 0;
	if(useExtendedBarStyle)
	{
		bar->SetStyle(0);
		bar->UpdateMana(character->GetMana(), character->GetMaxMana(), character->GetBlockedMana());
		bar->SetName(character->playerName);
	}
	else
	{
		bar->SetStyle(1);
		bar->SetName("");
	}
	bar->UpdateAlignment(otherTeam == m_myTeam);
	bar->UpdateHealth(character->GetHealth(), character->GetMaxHealth());

	// Set the icon next to the bar
	EMinimapIcon icon = EMinimapIcon::None;
	if(character->IsA<ATouhouBoss>())
	{
		icon = EMinimapIcon::Crown;
	}
	else if(character->IsA<AMiniBossCreature>())
	{
		icon = EMinimapIcon::MiniBoss;
	}
	bar->SetIcon(icon);

	// Set the on-screen position
	UCanvasPanelSlot* asSlot = Cast<UCanvasPanelSlot>(bar->Slot);
	if (IsValid(asSlot))
	{
		asSlot->SetAutoSize(true);
		asSlot->SetAlignment(FVector2D(0.5f, 1));
		const FVector2D setPos = FVector2D(screenPos.X, screenPos.Y) / viewportScale;
		asSlot->SetPosition(FVector2D(FPlatformMath::RoundToFloat(setPos.X), FPlatformMath::RoundToFloat(setPos.Y)));
	}
}

void UIngameHUD::m_UpdateStatDisplay(class ADefaultPlayerState* state, const FStatDisplay& sd)
{
	ANetworkPlayer* player = state ? state->character : nullptr;
	if(sd.hpBar)
	{
		if(!player)
			sd.hpBar->SetStat(0.0f, true);
		else
		{
			sd.hpBar->SetStat(player->GetHealth(), player->GetMaxHealth(), true);
		}
	}
	if(sd.manaBar)
	{
		if(!player)
		{
			sd.manaBar->SetBlocked(0.0f);
			sd.manaBar->SetStat(0, 1, true);
		}
		else
		{
			sd.manaBar->SetStat(player->GetMana(), player->GetMaxMana(), true);
			sd.manaBar->SetBlocked((float)player->GetBlockedMana() / (float)player->GetMaxMana());
		}
	}

	if(sd.expBar)
	{
		if(state)
		{
			if(state->GetLevel() == state->GetMaxLevel())
				sd.expBar->Update(1.0f, state->GetMaxLevel(), true);
			else
				sd.expBar->Update((float)state->GetExperience() / (float)state->GetExperienceToLevel(), state->GetLevel(), false);
		}
		else
		{
			sd.expBar->Update(0.0f, 0, false);
		}
	}
	if(sd.name)
	{
		if(state)
		{
			state->UpdatePersona();
			sd.name->SetText(FText::FromString(state->nickname));
		}
		else
		{
			sd.name->SetText(FText());
		}
	}
	if(sd.avatar && state)
	{
		sd.avatar->SetBrushFromTexture(state->avatar);
	}
}

void UIngameHUD::m_InitStatDisplay(const FStatDisplay& sd)
{
	if(sd.hpBar)
	{
		sd.hpBar->SetBarColor(EBarColor::GreenGradient);
	}
	if(sd.manaBar)
	{
		sd.manaBar->SetBarColor(EBarColor::BlueGradient);
	}
}
