#include "WingComponent.h"
#include "WingTypes.h"
#include "WingUtils.h"
#include "Engine/SCS_Node.h"
#include "Engine/SimpleConstructionScript.h"
#include "Components/SceneComponent.h"
#include "GameFramework/Actor.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Engine/InheritableComponentHandler.h"

UActorComponent *UWingComponent::FindComponentInCDO(UClass *Class, FName Name)
{
	if (!Class) return nullptr;
	AActor* CDO = Cast<AActor>(Class->GetDefaultObject());
	if (!CDO) return nullptr;
	return FindObjectFast<UActorComponent>(CDO, Name);
}

USCS_Node* UWingComponent::FindSCSNodeByName(UBlueprint *BP, FName Name)
{
	for (UBlueprint *WalkBP : WingUtils::GetAncestorBlueprints(BP))
	{
		if (!WalkBP->SimpleConstructionScript) continue;
		USCS_Node* Node = WalkBP->SimpleConstructionScript->FindSCSNode(Name);
		if (Node != nullptr) return Node;
	}
	return nullptr;
}

UWingComponent::FoundComponent UWingComponent::FindComponent(UBlueprint *BP, FName Name)
{
	UClass* NativeClass = FBlueprintEditorUtils::FindFirstNativeClass(BP->ParentClass);
	FoundComponent Result;
	Result.Name = Name;
	Result.SCS = FindSCSNodeByName(BP, Name);
	Result.Native = FindComponentInCDO(NativeClass, Name);
	return Result;
}

bool UWingComponent::CheckExists(UWingComponent::FoundComponent FC, WingOut Errors)
{
	if ((FC.SCS == nullptr) && (FC.Native == nullptr))
	{
		Errors.Printf(TEXT("Cannot find component: %s\n"), *WingUtils::ExternalizeID(FC.Name));
		return false;
	}
	return true;
}

bool UWingComponent::CheckValidParent(UWingComponent::FoundComponent FC, WingOut Errors)
{
	if (FC.SCS && FC.Native)
	{
		Errors.Printf(TEXT("Weirdness, two components named %s\n"),
			*WingUtils::ExternalizeID(FC.Name));
		return false;
	}
	if ((FC.SCS == nullptr) && (FC.Native == nullptr))
	{
		Errors.Printf(TEXT("No such parent component: %s\n"),
			*WingUtils::ExternalizeID(FC.Name));
		return false;
	}
	if (FC.Native && (!Cast<USceneComponent>(FC.Native)))
	{
		Errors.Printf(TEXT("Not a SceneComponent, so cannot be parent: %s\n"),
			*WingUtils::ExternalizeID(FC.Name));
		return false;
	}
	return true;
}

bool UWingComponent::CheckNoSuchComponent(FoundComponent FC, WingOut Errors)
{
	if (FC.SCS || FC.Native)
	{
		Errors.Printf(TEXT("A component named %s already exists"),
			*WingUtils::ExternalizeID(FC.Name));
		return false;
	}
	return true;
}

bool UWingComponent::CheckNotNative(FoundComponent FC, const TCHAR *Action, WingOut Errors)
{
	if (FC.Native != nullptr)
	{
		Errors.Printf(TEXT("Component %s is native, cannot %s native components"),
			*WingUtils::ExternalizeID(FC.Name), Action);
		return false;
	}
	return true;
}

bool UWingComponent::CheckOwnedByBlueprint(FoundComponent FC, UBlueprint *BP, WingOut Errors)
{
	if (FC.SCS == nullptr)
	{
		Errors.Printf(TEXT("Component %s is native, cannot be manipulated in the editor\n"),
			*WingUtils::ExternalizeID(FC.Name));
		return false;
	}
	if (FC.SCS->GetSCS() != BP->SimpleConstructionScript)
	{
		Errors.Printf(TEXT("Component %s belongs to blueprint %s, edit that blueprint instead"),
			*WingUtils::ExternalizeID(FC.Name), *WingUtils::FormatName(FC.SCS->GetSCS()->GetBlueprint()));
		return false;
	}
	return true;
}

bool UWingComponent::CheckValidComponentClass(UClass *Class, WingOut Errors)
{
	if (!Class->IsChildOf(UActorComponent::StaticClass()))
	{
		Errors.Printf(TEXT("Class does not derive from ActorComponent: %s\n"),
			*WingUtils::FormatName(Class));
		return false;
	}
	return true;
}


void UWingComponent::AddChildNode(UBlueprint *BP, USCS_Node *NewNode, FoundComponent Parent)
{
	if (Parent.SCS)
	{
		if (Parent.SCS->GetSCS() == BP->SimpleConstructionScript)
		{
			Parent.SCS->AddChildNode(NewNode);
		}
		else
		{
			NewNode->SetParent(Parent.SCS);
			BP->SimpleConstructionScript->AddNode(NewNode);
		}
	}
	else
	{
		NewNode->SetParent(Cast<USceneComponent>(Parent.Native));
		BP->SimpleConstructionScript->AddNode(NewNode);
	}
}

bool UWingComponent::AddComponent(UBlueprint *BP, UClass *Class, UWingComponentRef *Parent, FName Name, WingOut Errors)
{
	if (BP->SimpleConstructionScript == nullptr)
	{
		Errors.Printf(TEXT("This blueprint cannot contain components.\n"));
		return false;
	}
	TSet<FName> Names = WingUtils::GetAllInUseNames(BP);
	if (Names.Contains(Name))
	{
		Errors.Printf(TEXT("There is already a variable or component named %s in %s.\n"),
			*WingUtils::ExternalizeID(Name), *WingUtils::FormatName(Class));
		return false;
	}
	FoundComponent ExistingComponent = FindComponent(BP, Name);
	if (!CheckNoSuchComponent(ExistingComponent, Errors)) return false;
	FoundComponent ParentComponent = FindComponent(BP, Parent->VariableName);
	if (!CheckValidParent(ParentComponent, Errors)) return false;
	if (!CheckValidComponentClass(Class, Errors)) return false;

	USCS_Node *NewNode = BP->SimpleConstructionScript->CreateNode(Class, Name);
	if (NewNode == nullptr)
	{
		Errors.Printf(TEXT("Could not create new component %s of class %s, unknown reason.\n"),
			*WingUtils::ExternalizeID(Name), *WingUtils::FormatName(Class));
		return false;
	}

	AddChildNode(BP, NewNode, ParentComponent);
	return true;
}

bool UWingComponent::ReparentComponent(UWingComponentRef *Ref, UWingComponentRef *Parent, WingOut Errors)
{
	FoundComponent ParentComponent = FindComponent(Ref->BP, Parent->VariableName);
	if (!CheckValidParent(ParentComponent, Errors)) return false;
	FoundComponent ThisComponent = FindComponent(Ref->BP, Ref->VariableName);
	if (!CheckExists(ThisComponent, Errors)) return false;
	if (!CheckNotNative(ThisComponent, TEXT("reparent"), Errors)) return false;
	if (!CheckOwnedByBlueprint(ThisComponent, Ref->BP, Errors)) return false;

	if (ParentComponent.SCS && ParentComponent.SCS->IsChildOf(ThisComponent.SCS))
	{
		Errors.Printf(TEXT("Cannot parent a component to itself or its own child."));
		return false;
	}

	Ref->BP->SimpleConstructionScript->RemoveNode(ThisComponent.SCS);
	AddChildNode(Ref->BP, ThisComponent.SCS, ParentComponent);
	return true;
}

bool UWingComponent::DeleteComponent(UWingComponentRef *Ref, WingOut Errors)
{
	FoundComponent ThisComponent = FindComponent(Ref->BP, Ref->VariableName);
	if (!CheckExists(ThisComponent, Errors)) return false;
	if (!CheckNotNative(ThisComponent, TEXT("delete"), Errors)) return false;
	if (!CheckOwnedByBlueprint(ThisComponent, Ref->BP, Errors)) return false;
	Ref->BP->SimpleConstructionScript->RemoveNodeAndPromoteChildren(ThisComponent.SCS);
	return true;
}

TMap<FName, FName> UWingComponent::CalculateParentNames(USimpleConstructionScript *Script)
{
	TMap<FName, FName> ParentNames;
	for (USCS_Node* Node : Script->GetAllNodes())
	{
		ParentNames.Add(Node->GetVariableName(), Node->ParentComponentOrVariableName);
	}
	for (USCS_Node* Parent : Script->GetAllNodes())
	{
		for (USCS_Node *Child : Parent->GetChildNodes())
		{
			ParentNames[Child->GetVariableName()] = Parent->GetVariableName();
		}
	}
	return ParentNames;
}

UActorComponent* UWingComponent::GetImmutableTemplate(const UWingComponentRef *Ref)
{
	FoundComponent FC = FindComponent(Ref->BP, Ref->VariableName);
	if (FC.Native) return FC.Native;
	if (FC.SCS == nullptr) return nullptr;
	FComponentKey Key(FC.SCS);

	for (UBlueprint *WalkBP : WingUtils::GetAncestorBlueprints(Ref->BP, false))
	{
		if (FC.SCS->GetSCS()->GetBlueprint() == WalkBP) return FC.SCS->ComponentTemplate;
		UInheritableComponentHandler* ICH = WalkBP->GetInheritableComponentHandler(/*bCreateIfNecessary=*/ false);
		if (!ICH) continue;
		UActorComponent* Override = ICH->GetOverridenComponentTemplate(Key);
		if (Override) return Override;
	}
	return nullptr;
}

UActorComponent* UWingComponent::GetMutableTemplate(const UWingComponentRef *Ref)
{
	FoundComponent FC = FindComponent(Ref->BP, Ref->VariableName);
	if (FC.Native) return FC.Native;
	if (!FC.SCS) return nullptr;
	if (FC.SCS->GetSCS()->GetBlueprint() == Ref->BP) return FC.SCS->ComponentTemplate;

	FComponentKey Key(FC.SCS);
	UInheritableComponentHandler* ICH = Ref->BP->GetInheritableComponentHandler(/*bCreateIfNecessary=*/ true);
	if (!ICH) return nullptr;
	UActorComponent* Override = ICH->GetOverridenComponentTemplate(Key);
	if (!Override) Override = ICH->CreateOverridenComponentTemplate(Key);
	return Override;
}

TArray<TStrongObjectPtr<UWingComponentRef>> UWingComponent::GetAll(UBlueprint* BP)
{
	TArray<TStrongObjectPtr<UWingComponentRef>> Result;
	if (!BP) return Result;

	// Find the native ancestor class
	UClass* NativeClass = FBlueprintEditorUtils::FindFirstNativeClass(BP->ParentClass);

	// Native components from the native ancestor's CDO
	if (NativeClass)
	{
		if (AActor* CDO = Cast<AActor>(NativeClass->GetDefaultObject()))
		{
			TArray<UActorComponent*> NativeComponents;
			CDO->GetComponents(NativeComponents);
			for (UActorComponent* Comp : NativeComponents)
			{
				UWingComponentRef* Ref = NewObject<UWingComponentRef>();
				Ref->BP = BP;
				Ref->VariableName = Comp->GetFName();
				Result.Emplace(Ref);
			}
		}
	}

	// Visit all SCS nodes, oldest first.
	for (UBlueprint *WalkBP : WingUtils::GetAncestorBlueprints(BP, true))
	{
		if (WalkBP->SimpleConstructionScript == nullptr) continue;
		for (USCS_Node* Node : WalkBP->SimpleConstructionScript->GetAllNodes())
		{
			UWingComponentRef* Ref = NewObject<UWingComponentRef>();
			Ref->BP = BP;
			Ref->VariableName = Node->GetVariableName();
			Result.Emplace(Ref);
		}
	}

	return Result;
}

void UWingComponent::PrintComponentInfo(const FString& TypeName, const FString& VarName, const FString& ParentName, bool Inherited, WingOut Out)
{
	Out.Printf(TEXT("  %s %s"), *TypeName, *VarName);
	if (!ParentName.IsEmpty())
		Out.Printf(TEXT("  [parent: %s]"), *ParentName);
	if (Inherited)
		Out.Print(TEXT("  [inherited]"));
	Out.Print(TEXT("\n"));
}

void UWingComponent::PrintAll(UBlueprint* BP, WingOut Out)
{
	if (!BP) return;

	// Find the native ancestor class
	UClass* NativeClass = FBlueprintEditorUtils::FindFirstNativeClass(BP->ParentClass);
	
	// Native components from the native ancestor's CDO
	if (NativeClass)
	{
		if (AActor* CDO = Cast<AActor>(NativeClass->GetDefaultObject()))
		{
			TArray<UActorComponent*> NativeComponents;
			CDO->GetComponents(NativeComponents);
			for (UActorComponent* Comp : NativeComponents)
			{
				FString ParentName;
				if (USceneComponent* Scene = Cast<USceneComponent>(Comp))
					if (USceneComponent* AttachParent = Scene->GetAttachParent())
						ParentName = WingUtils::ExternalizeID(AttachParent->GetFName());
				PrintComponentInfo(UWingTypes::TypeToText(Comp->GetClass()),
					WingUtils::FormatName(Comp), ParentName, true, Out);
			}
		}
	}

	// Visit all SCS nodes, oldest first.
	for (UBlueprint *WalkBP : WingUtils::GetAncestorBlueprints(BP, true))
	{
		if (WalkBP->SimpleConstructionScript == nullptr) continue;
		TMap<FName, FName> ParentNames = CalculateParentNames(WalkBP->SimpleConstructionScript);
		for (USCS_Node* Node : WalkBP->SimpleConstructionScript->GetAllNodes())
		{
			FString ParentName = WingUtils::ExternalizeID(ParentNames[Node->GetVariableName()]);
			PrintComponentInfo(UWingTypes::TypeToText(Node->ComponentClass),
				WingUtils::ExternalizeID(Node->GetVariableName()),
				ParentName, WalkBP != BP, Out);
		}
	}
}
