#include "WingActorComponent.h"
#include "WingServer.h"
#include "WingTypes.h"
#include "WingUtils.h"
#include "Engine/Blueprint.h"
#include "Engine/SCS_Node.h"
#include "Engine/SimpleConstructionScript.h"
#include "Components/ActorComponent.h"
#include "Components/SceneComponent.h"
#include "GameFramework/Actor.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Engine/InheritableComponentHandler.h"

UActorComponent *UWingComponentReference::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* UWingComponentReference::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;
}

UWingComponentReference::FoundComponent UWingComponentReference::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 UWingComponentReference::CheckExists(UWingComponentReference::FoundComponent FC)
{
	if ((FC.SCS == nullptr) && (FC.Native == nullptr))
	{
		UWingServer::Printf(TEXT("Cannot find component: %s\n"), *WingUtils::ExternalizeID(FC.Name));
		return false;
	}
	return true;
}

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

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

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

bool UWingComponentReference::CheckOwnedByBlueprint(FoundComponent FC, UBlueprint *BP)
{
	if ((FC.SCS == nullptr) || (FC.SCS->GetSCS() != BP->SimpleConstructionScript))
	{
		UWingServer::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 UWingComponentReference::CheckValidComponentClass(UClass *Class)
{
	if (!Class->IsChildOf(UActorComponent::StaticClass()))
	{
		UWingServer::Printf(TEXT("Class does not derive from ActorComponent: %s\n"),
			*WingUtils::FormatName(Class));
		return false;
	}
	return true;
}


void UWingComponentReference::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 UWingComponentReference::AddComponent(UBlueprint *BP, UClass *Class, UWingComponentReference *Parent, FName Name)
{
	TSet<FName> Names;
	FBlueprintEditorUtils::GetClassVariableList(BP, Names);
	if (Names.Contains(Name))
	{
		UWingServer::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)) return false;
	FoundComponent ParentComponent = FindComponent(BP, Parent->VariableName);
	if (!CheckValidParent(ParentComponent)) return false;
	if (!CheckValidComponentClass(Class)) return false;

	USCS_Node *NewNode = BP->SimpleConstructionScript->CreateNode(Class, Name);
	if (NewNode == nullptr)
	{
		UWingServer::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 UWingComponentReference::ReparentComponent(UWingComponentReference *Parent)
{
	FoundComponent ParentComponent = FindComponent(BP, Parent->VariableName);
	if (!CheckValidParent(ParentComponent)) return false;
	FoundComponent ThisComponent = FindComponent(BP, VariableName);
	if (!CheckExists(ThisComponent)) return false;
	if (!CheckNotNative(ThisComponent, TEXT("reparent"))) return false;
	if (!CheckOwnedByBlueprint(ThisComponent, BP)) return false;

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

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

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

TMap<FName, FName> UWingComponentReference::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* UWingComponentReference::GetImmutableTemplate() const
{
	if (Native) return FindComponentInCDO(BP->GeneratedClass, VariableName);
	USCS_Node *Node = FindSCSNodeByName(BP, VariableName);
	if (Node == nullptr) return nullptr;
	FComponentKey Key(Node);

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

UActorComponent* UWingComponentReference::GetMutableTemplate() const
{
	if (Native) return FindComponentInCDO(BP->GeneratedClass, VariableName);
	USCS_Node* Node = FindSCSNodeByName(BP, VariableName);
	if (!Node) return nullptr;
	if (Node->GetSCS()->GetBlueprint() == BP) return Node->ComponentTemplate;

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

TArray<UWingComponentReference*> UWingComponentReference::GetAll(UBlueprint* BP)
{
	TArray<UWingComponentReference*> 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)
			{
				UWingComponentReference* Ref = NewObject<UWingComponentReference>();
				Ref->BP = BP;
				Ref->VariableName = Comp->GetFName();
				Ref->TypeName = UWingTypes::TypeToText(Comp->GetClass());
				if (USceneComponent* Scene = Cast<USceneComponent>(Comp))
					if (USceneComponent* AttachParent = Scene->GetAttachParent())
						Ref->ParentName = WingUtils::ExternalizeID(AttachParent->GetFName());
				Ref->Native = true;
				Ref->Inherited = true;
				Result.Add(Ref);
			}
		}
	}

	// 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())
		{
			UWingComponentReference* Ref = NewObject<UWingComponentReference>();
			Ref->BP = BP;
			Ref->VariableName = Node->GetVariableName();
			Ref->TypeName = UWingTypes::TypeToText(Node->ComponentClass);
			Ref->ParentName = WingUtils::ExternalizeID(ParentNames[Node->GetVariableName()]);
			Ref->Native = false;
			Ref->Inherited = (WalkBP != BP);
			Result.Add(Ref);
		}
	}

	return Result;
}

