// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.

#include "K2Node_ComboAction.h"

#include "AssetRegistry/AssetRegistryModule.h"
#include "BlueprintActionDatabaseRegistrar.h"
#include "BlueprintEditorSettings.h"
#include "BlueprintNodeSpawner.h"
#include "ComboInputAssets.h"
#include "ComboInputTriggers.h"
#include "EdGraphSchema_K2_Actions.h"
#include "Editor.h"
#include "EditorCategoryUtils.h"
#include "GraphEditorSettings.h"
#include "K2Node_AssignmentStatement.h"
#include "K2Node_ComboActionEvent.h"
#include "K2Node_TemporaryVariable.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "KismetCompiler.h"
#include "Misc/PackageName.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "Modules/ModuleManager.h"
#include "Styling/AppStyle.h"

#include UE_INLINE_GENERATED_CPP_BY_NAME(K2Node_ComboAction)

#define LOCTEXT_NAMESPACE "K2Node_ComboAction"

#define COMBO_ACTION_OBJECT_PIN_NAME	TEXT("ComboAction")


void ForEachEventPinName(TFunctionRef<void(EComboActionTriggerEvent Event, FName PinName)> PinLambda)
{
	UEnum *EventEnum = StaticEnum<EComboActionTriggerEvent>();
	for (int32 i = 0; i < EventEnum->NumEnums() - 1; ++i)
	{
		if (!EventEnum->HasMetaData(TEXT("Hidden"), i))
		{
			PinLambda(EComboActionTriggerEvent(EventEnum->GetValueByIndex(i)), *EventEnum->GetNameStringByIndex(i));
		}
	}
}

void UK2Node_ComboAction::AllocateDefaultPins()
{
	const UEdGraphSchema_K2 *Schema = GetDefault<UEdGraphSchema_K2>();

	this->AdvancedPinDisplay = ENodeAdvancedPins::Hidden;

	ForEachEventPinName([this](EComboActionTriggerEvent Event, FName PinName)
	{
		static const UEnum *EventEnum = StaticEnum<EComboActionTriggerEvent>();

		UEdGraphPin *NewPin = this->CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, PinName);
		NewPin->PinToolTip = EventEnum->GetToolTipTextByIndex(EventEnum->GetIndexByValue(static_cast<uint8>(Event))).ToString();
		NewPin->bAdvancedView = (Event > EComboActionTriggerEvent::Activated);
	});
	
	UEdGraphPin *ComboActionPin = this->CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Object, UComboAction::StaticClass(), COMBO_ACTION_OBJECT_PIN_NAME);
	ComboActionPin->bAdvancedView = true;

	Schema->SetPinAutogeneratedDefaultValueBasedOnType(ComboActionPin);

	if (this->ComboAction)
	{
		ComboActionPin->DefaultObject = const_cast<UObject *>(Cast<UObject>(this->ComboAction));
		ComboActionPin->DefaultValue = this->ComboAction->GetName();
		Schema->ConstructBasicPinTooltip(*ComboActionPin, LOCTEXT("ComboActionPinDescription", "The combo action that caused this event to fire"), ComboActionPin->PinToolTip);
	}

	Super::AllocateDefaultPins();
}

FLinearColor UK2Node_ComboAction::GetNodeTitleColor() const
{
	return GetDefault<UGraphEditorSettings>()->EventNodeTitleColor;
}

FName UK2Node_ComboAction::GetActionName() const
{
	return this->ComboAction ? this->ComboAction->ActionName : FName();
}

FText UK2Node_ComboAction::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
	if (TitleType == ENodeTitleType::MenuTitle)
	{
		return FText::FromName(this->GetActionName());
	}
	else if (this->CachedNodeTitle.IsOutOfDate(this))
	{
		FFormatNamedArguments Args;
		Args.Add(TEXT("ComboActionName"), FText::FromName(this->GetActionName()));

		FText LocFormat = LOCTEXT("ComboAction_Name", "ComboAction {ComboActionName}");
		// FText::Format() is slow, so we cache this to save on performance
		this->CachedNodeTitle.SetCachedText(FText::Format(LocFormat, Args), this);
	}

	return this->CachedNodeTitle;
}

FText UK2Node_ComboAction::GetTooltipText() const
{
	if (CachedTooltip.IsOutOfDate(this))
	{
		// FText::Format() is slow, so we cache this to save on performance
		CachedTooltip.SetCachedText(FText::Format(NSLOCTEXT("K2Node", "ComboAction_Tooltip", "Event for when the combo action {0} is activated or released.\nBoth execution pins might fire on the same frame if an action\nwas buffered during a release action, but \"Activated\" will\nalways happen before \"Released\"."), FText::FromName(this->ComboAction->GetFName())), this);
	}
	return CachedTooltip;
}

FSlateIcon UK2Node_ComboAction::GetIconAndTint(FLinearColor &OutColor) const
{
	static FSlateIcon Icon(FAppStyle::GetAppStyleSetName(), "GraphEditor.Event_16x");
	return Icon;
}

bool UK2Node_ComboAction::IsCompatibleWithGraph(UEdGraph const *Graph) const
{
	// This node expands into event nodes and must be placed in a Ubergraph
	EGraphType const GraphType = Graph->GetSchema()->GetGraphType(Graph);
	bool bIsCompatible = (GraphType == EGraphType::GT_Ubergraph);

	if (bIsCompatible)
	{
		UBlueprint *Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(Graph);

		UEdGraphSchema_K2 const *K2Schema = Cast<UEdGraphSchema_K2>(Graph->GetSchema());
		bool const bIsConstructionScript = (K2Schema != nullptr) ? UEdGraphSchema_K2::IsConstructionScript(Graph) : false;

		bIsCompatible = (Blueprint != nullptr) && Blueprint->SupportsInputEvents() && !bIsConstructionScript && Super::IsCompatibleWithGraph(Graph);
	}
	return bIsCompatible;
}

UObject *UK2Node_ComboAction::GetJumpTargetForDoubleClick() const
{
	return const_cast<UObject *>(Cast<UObject>(this->ComboAction));
}

void UK2Node_ComboAction::JumpToDefinition() const
{
	GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(this->GetJumpTargetForDoubleClick());
}

void UK2Node_ComboAction::ValidateNodeDuringCompilation(class FCompilerResultsLog &MessageLog) const
{
	Super::ValidateNodeDuringCompilation(MessageLog);

	if (!this->ComboAction)
	{
		MessageLog.Error(*LOCTEXT("ComboAction_ErrorFmt", "ComboActionEvent references invalid 'null' action for @@").ToString(), this);
		return;
	}
}

void UK2Node_ComboAction::ExpandNode(FKismetCompilerContext &CompilerContext, UEdGraph *SourceGraph)
{
	Super::ExpandNode(CompilerContext, SourceGraph);

	const UEdGraphSchema_K2 *Schema = CompilerContext.GetSchema();

	// Create a temporary variable to copy Key in to
	UK2Node_TemporaryVariable *ComboActionObjectVar = CompilerContext.SpawnIntermediateNode<UK2Node_TemporaryVariable>(this, SourceGraph);
	ComboActionObjectVar->VariableType.PinCategory = UEdGraphSchema_K2::PC_Object;
	ComboActionObjectVar->VariableType.PinSubCategoryObject = UComboAction::StaticClass();
	ComboActionObjectVar->AllocateDefaultPins();

	// Establish active pins
	struct ActivePinData
	{
		ActivePinData(UEdGraphPin *InPin, EComboActionTriggerEvent InTriggerEvent) : Pin(InPin), TriggerEvent(InTriggerEvent){}
		UEdGraphPin *Pin;
		EComboActionTriggerEvent TriggerEvent;
	};

	TArray<ActivePinData> ActivePins; 
	ForEachEventPinName([this, &ActivePins, &CompilerContext](EComboActionTriggerEvent Event, FName PinName)
	{
		UEdGraphPin *InputActionPin = this->FindPin(PinName, EEdGraphPinDirection::EGPD_Output);
		if (InputActionPin && InputActionPin->LinkedTo.Num() > 0)
		{
			ActivePins.Add(ActivePinData(InputActionPin, Event));
		}
	});

	for(const ActivePinData &PinData : ActivePins)
	{
		UEdGraphPin *ComboActionPin = PinData.Pin;

		if (ComboActionPin->LinkedTo.Num() > 0)
		{
			// Create the combo action event
			UK2Node_ComboActionEvent *ComboActionEvent = CompilerContext.SpawnIntermediateEventNode<UK2Node_ComboActionEvent>(this, ComboActionPin, SourceGraph);
			ComboActionEvent->CustomFunctionName = FName(*FString::Printf(TEXT("ComboActionEvent_%s_%s"), *this->GetActionName().ToString(), *ComboActionEvent->GetName()));
			ComboActionEvent->ComboAction = this->ComboAction;
			ComboActionEvent->TriggerEvent = PinData.TriggerEvent; 
			ComboActionEvent->EventReference.SetExternalDelegateMember(FName(TEXT("ComboActionHandlerDynamicSignature__DelegateSignature")));
			ComboActionEvent->bInternalEvent = true;
			ComboActionEvent->AllocateDefaultPins();

			// Create assignment nodes to assign the key
			UK2Node_AssignmentStatement *ComboActionObjectInitialize = CompilerContext.SpawnIntermediateNode<UK2Node_AssignmentStatement>(this, SourceGraph);
			ComboActionObjectInitialize->AllocateDefaultPins();
			Schema->TryCreateConnection(ComboActionObjectVar->GetVariablePin(), ComboActionObjectInitialize->GetVariablePin());
			Schema->TryCreateConnection(ComboActionObjectInitialize->GetValuePin(), ComboActionEvent->FindPinChecked(COMBO_ACTION_OBJECT_PIN_NAME));

			// Connect the events to the assign key nodes
			Schema->TryCreateConnection(Schema->FindExecutionPin(*ComboActionEvent, EGPD_Output), ComboActionObjectInitialize->GetExecPin());

			// Move the original event connections to the then pin of the key assign
			CompilerContext.MovePinLinksToIntermediate(*ComboActionPin, *ComboActionObjectInitialize->GetThenPin());

			// Move the original event variable connections to the intermediate nodes
			CompilerContext.MovePinLinksToIntermediate(*this->FindPin(COMBO_ACTION_OBJECT_PIN_NAME), *ComboActionObjectVar->GetVariablePin());
		}
	}
}

void UK2Node_ComboAction::GetMenuActions(FBlueprintActionDatabaseRegistrar &ActionRegistrar) const
{
	auto CustomizeComboActionNodeLambda = [](UEdGraphNode *NewNode, bool bIsTemplateNode, TWeakObjectPtr<const UComboAction> Action)
	{
		UK2Node_ComboAction *ComboActionNode = CastChecked<UK2Node_ComboAction>(NewNode);
		ComboActionNode->ComboAction = Action.Get();
	};

	// Do a first time registration using the node's class to pull in all existing actions
	if (ActionRegistrar.IsOpenForRegistration(GetClass()))
	{
		IAssetRegistry &AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();

		static bool bRegisterOnce = true;
		if (bRegisterOnce)
		{
			bRegisterOnce = false;
			if (AssetRegistry.IsLoadingAssets())
			{
				AssetRegistry.OnFilesLoaded().AddLambda([]()
				{
					FBlueprintActionDatabase::Get().RefreshClassActions(StaticClass());
				});
			}
		}

		TArray<FAssetData> ActionAssets;
		AssetRegistry.GetAssetsByClass(UComboAction::StaticClass()->GetClassPathName(), ActionAssets, true);
		for (const FAssetData &ActionAsset : ActionAssets)
		{
			UBlueprintNodeSpawner *NodeSpawner = UBlueprintNodeSpawner::Create(GetClass());
			check(NodeSpawner != nullptr);

			if (FPackageName::GetPackageMountPoint(ActionAsset.PackageName.ToString()) != NAME_None)
			{
				if (const UComboAction *Action = Cast<const UComboAction>(ActionAsset.GetAsset()))
				{
					NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(CustomizeComboActionNodeLambda, TWeakObjectPtr<const UComboAction>(Action));
					ActionRegistrar.AddBlueprintAction(Action, NodeSpawner);
				}
			}
		}
	}
	else if (const UComboAction *Action = Cast<const UComboAction>(ActionRegistrar.GetActionKeyFilter()))
	{
		// If this is a specific UComboAction asset update it.
		UBlueprintNodeSpawner *NodeSpawner = UBlueprintNodeSpawner::Create(GetClass());
		check(NodeSpawner != nullptr);

		NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(CustomizeComboActionNodeLambda, TWeakObjectPtr<const UComboAction>(Action));
		ActionRegistrar.AddBlueprintAction(Action, NodeSpawner);
	}
}

FText UK2Node_ComboAction::GetMenuCategory() const
{
	static FNodeTextCache CachedCategory;
	if (CachedCategory.IsOutOfDate(this))
	{
		// FText::Format() is slow, so we cache this to save on performance
		CachedCategory.SetCachedText(FEditorCategoryUtils::BuildCategoryString(FCommonEditorCategory::Input, LOCTEXT("ActionMenuCategory", "Combo Action Events")), this);	// TODO: Rename Action Events once old action system is removed
	}
	return CachedCategory;
}

FBlueprintNodeSignature UK2Node_ComboAction::GetSignature() const
{
	FBlueprintNodeSignature NodeSignature = Super::GetSignature();
	NodeSignature.AddKeyValue(GetActionName().ToString());

	return NodeSignature;
}

TSharedPtr<FEdGraphSchemaAction> UK2Node_ComboAction::GetEventNodeAction(const FText &ActionCategory)
{
	// TODO: Custom EdGraphSchemaAction required?
	TSharedPtr<FEdGraphSchemaAction_K2InputAction> EventNodeAction = MakeShareable(new FEdGraphSchemaAction_K2InputAction(ActionCategory, GetNodeTitle(ENodeTitleType::EditableTitle), GetTooltipText(), 0));
	EventNodeAction->NodeTemplate = this;
	return EventNodeAction;
}

#undef LOCTEXT_NAMESPACE
