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

#include "ComboInputEditor.h"

#include "ComboActionGraph.h"
#include "ComboInputAssets.h"
#include "ToolMenuSection.h"

#include "AssetTypeActions/AssetTypeActions_DataAsset.h"
#include "Ed/AssetEditor_ComboActionGraph.h"
#include "Ed/EdComboActionGraphEdge.h"
#include "Ed/EdComboActionGraphNode.h"
#include "Ed/FComboActionGraphEditorCommands.h"
#include "Ed/SEdComboActionGraphEdge.h"
#include "Ed/SEdComboActionGraphNode.h"
#include "Interfaces/IPluginManager.h"
#include "Styling/SlateStyle.h"
#include "Styling/SlateStyleMacros.h"
#include "Styling/SlateStyleRegistry.h"

#include UE_INLINE_GENERATED_CPP_BY_NAME(ComboInputEditor)

#define LOCTEXT_NAMESPACE "FComboInputEditorModule"

EAssetTypeCategories::Type FComboInputEditorModule::ComboAssetsCategory;
EAssetTypeCategories::Type FComboInputEditorModule::ComboActionGraphCategory;


class FAssetTypeActions_ComboAction : public FAssetTypeActions_DataAsset
{
public:
	virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_ComboAction", "Combo Action"); }
	virtual uint32 GetCategories() override { return FComboInputEditorModule::GetInputAssetsCategory(); }
	virtual FColor GetTypeColor() const override { return FColor(255, 127, 255); }
	virtual FText GetAssetDescription(const FAssetData &AssetData) const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_ComboActionDesc", "An action that can be executed as part of a combo sequence. This is essentially a representation of an attack, and can be sent to the Animation Graph to play an attack animation and the like."); }
	virtual UClass *GetSupportedClass() const override { return UComboAction::StaticClass(); }
};

class FAssetTypeActions_ComboSequenceNode : public FAssetTypeActions_DataAsset
{
public:
	virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_ComboSequenceNode", "Combo Sequence Node"); }
	virtual uint32 GetCategories() override { return FComboInputEditorModule::GetInputAssetsCategory(); }
	virtual FColor GetTypeColor() const override { return FColor(255, 127, 255); }
	virtual FText GetAssetDescription(const FAssetData &AssetData) const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_ComboSequenceNodeDesc", "This represents a node in the combo graph, with each key in the ComboBranch being an input this node can react to, and each value containing the action to be executed next, and the node to activate after the action is complete."); }
	virtual UClass *GetSupportedClass() const override { return UComboSequenceNode::StaticClass(); }
};

class FAssetTypeActions_ComboInputAsset : public FAssetTypeActions_DataAsset
{
public:
	virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_ComboInputAsset", "Combo Input Asset"); }
	virtual uint32 GetCategories() override { return FComboInputEditorModule::GetInputAssetsCategory(); }
	virtual FColor GetTypeColor() const override { return FColor(255, 127, 255); }
	virtual FText GetAssetDescription(const FAssetData &AssetData) const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_ComboInputAssetDesc", "This maps a sequence of button inputs from EnhancedInput to a combo action that can be used to execute a sequence of moves. This gets sent from the input buffer subsystem to the player controller's ComboManagerComponent, which executes the associated action in the current ComboSequenceNode."); }
	virtual UClass *GetSupportedClass() const override { return UComboInputAsset::StaticClass(); }
};

class FAssetTypeActions_ComboActionGraph : public FAssetTypeActions_Base
{
public:
	virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_ComboActionGraph", "Combo Action Graph"); }
	virtual uint32 GetCategories() override { return FComboInputEditorModule::GetInputAssetsCategory(); }
	virtual FColor GetTypeColor() const override { return FColor(255, 127, 255); }
	virtual FText GetAssetDescription(const FAssetData &AssetData) const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_ComboActionGraphDesc", "This maps a sequence of button inputs from EnhancedInput to a combo action that can be used to execute a sequence of moves. This gets sent from the input buffer subsystem to the player controller's ComboManagerComponent, which executes the associated action in the current ComboSequenceNode."); }
	virtual UClass *GetSupportedClass() const override { return UComboActionGraph::StaticClass(); }

	virtual void OpenAssetEditor(const TArray<UObject*> &InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor = TSharedPtr<IToolkitHost>()) override
	{
		const EToolkitMode::Type Mode = EditWithinLevelEditor.IsValid() ? EToolkitMode::WorldCentric : EToolkitMode::Standalone;
		for (auto ObjIt = InObjects.CreateConstIterator(); ObjIt; ObjIt++)
		{
			if (UComboActionGraph *Graph = Cast<UComboActionGraph>(*ObjIt))
			{
				TSharedRef<FAssetEditor_ComboActionGraph> NewGraphEditor(new FAssetEditor_ComboActionGraph());
				NewGraphEditor->InitComboActionGraphAssetEditor(Mode, EditWithinLevelEditor, Graph);
			}
		}
	}
};

class FComboActionGraphPanelNodeFactory : public FGraphPanelNodeFactory
{
public:
	virtual TSharedPtr<class SGraphNode> CreateNode(UEdGraphNode *Node) const override
	{

		if (UEdComboActionGraphNode *EdGraphNode = Cast<UEdComboActionGraphNode>(Node))
		{
			return SNew(SEdComboActionGraphNode, EdGraphNode);
		}
		else if (UEdComboActionGraphEdge *EdGraphEdge = Cast<UEdComboActionGraphEdge>(Node))
		{
			return SNew(SEdComboActionGraphEdge, EdGraphEdge);
		}
		return nullptr;
	}
};


UComboAction_Factory::UComboAction_Factory(const FObjectInitializer &ObjectInitializer)
	: Super(ObjectInitializer)
{
	this->SupportedClass = UComboAction::StaticClass();
	this->bEditAfterNew = true;
	this->bCreateNew = true;
}

UObject *UComboAction_Factory::FactoryCreateNew(UClass *Class, UObject *InParent, FName Name, EObjectFlags Flags, UObject *Context, FFeedbackContext *Warn)
{
	if (this->ComboActionClass != nullptr)
	{
		return NewObject<UComboAction>(InParent, this->ComboActionClass, Name, Flags | EObjectFlags::RF_Transactional, Context);
	}
	else
	{
		check(Class->IsChildOf(UComboAction::StaticClass()));
		return NewObject<UComboAction>(InParent, Class, Name, Flags | EObjectFlags::RF_Transactional, Context);
	}
}


UComboSequenceNode_Factory::UComboSequenceNode_Factory(const FObjectInitializer &ObjectInitializer)
	: Super(ObjectInitializer)
{
	this->SupportedClass = UComboSequenceNode::StaticClass();
	this->bEditAfterNew = true;
	this->bCreateNew = true;
}

UObject *UComboSequenceNode_Factory::FactoryCreateNew(UClass *Class, UObject *InParent, FName Name, EObjectFlags Flags, UObject *Context, FFeedbackContext *Warn)
{
	if (this->ComboSequenceNodeClass != nullptr)
	{
		return NewObject<UComboSequenceNode>(InParent, this->ComboSequenceNodeClass, Name, Flags | EObjectFlags::RF_Transactional, Context);
	}
	else
	{
		check(Class->IsChildOf(UComboSequenceNode::StaticClass()));
		return NewObject<UComboSequenceNode>(InParent, Class, Name, Flags | EObjectFlags::RF_Transactional, Context);
	}
}


UComboInputAsset_Factory::UComboInputAsset_Factory(const FObjectInitializer &ObjectInitializer)
	: Super(ObjectInitializer)
{
	this->SupportedClass = UComboInputAsset::StaticClass();
	this->bEditAfterNew = true;
	this->bCreateNew = true;
}

UObject *UComboInputAsset_Factory::FactoryCreateNew(UClass *Class, UObject *InParent, FName Name, EObjectFlags Flags, UObject *Context, FFeedbackContext *Warn)
{
	if (this->ComboInputAssetClass != nullptr)
	{
		return NewObject<UComboInputAsset>(InParent, this->ComboInputAssetClass, Name, Flags | EObjectFlags::RF_Transactional, Context);
	}
	else
	{
		check(Class->IsChildOf(UComboInputAsset::StaticClass()));
		return NewObject<UComboInputAsset>(InParent, Class, Name, Flags | EObjectFlags::RF_Transactional, Context);
	}
}


UComboActionGraph_Factory::UComboActionGraph_Factory(const FObjectInitializer &ObjectInitializer)
	: Super(ObjectInitializer)
{
	this->SupportedClass = UComboActionGraph::StaticClass();
	this->bEditAfterNew = true;
	this->bCreateNew = true;
}

UObject *UComboActionGraph_Factory::FactoryCreateNew(UClass *Class, UObject *InParent, FName Name, EObjectFlags Flags, UObject *Context, FFeedbackContext *Warn)
{
	if (this->ComboActionGraphClass != nullptr)
	{
		return NewObject<UComboActionGraph>(InParent, this->ComboActionGraphClass, Name, Flags | EObjectFlags::RF_Transactional, Context);
	}
	else
	{
		check(Class->IsChildOf(UComboActionGraph::StaticClass()));
		return NewObject<UComboActionGraph>(InParent, Class, Name, Flags | EObjectFlags::RF_Transactional, Context);
	}
}


FComboInputSlateStyle::FComboInputSlateStyle() : FSlateStyleSet("ComboInputEditor")
{
	this->SetParentStyleName(FAppStyle::GetAppStyleSetName());

	const FString &PluginSlateDir = IPluginManager::Get().FindPlugin("ComboInput")->GetBaseDir();

	this->SetContentRoot(PluginSlateDir / TEXT("Resources/Slate"));
	this->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate"));

	// Combo Input Editor icons
	static const FVector2D Icon12(12.0f, 12.0f);
	static const FVector2D Icon16(16.0f, 16.0f);
	static const FVector2D Icon40(40.0f, 40.0f);
	static const FVector2D Icon64(64.0f, 64.0f);

	this->Set("ComboInputIcon_Small", new IMAGE_BRUSH_SVG("Icons/ComboInput_16", Icon16));
	this->Set("ComboInputIcon_Large", new IMAGE_BRUSH_SVG("Icons/ComboInput_64", Icon64));

	this->Set("ClassIcon.ComboAction", new IMAGE_BRUSH_SVG("Icons/ComboAction_16", Icon16));
	this->Set("ClassThumbnail.ComboAction", new IMAGE_BRUSH_SVG("Icons/ComboAction_64", Icon64));

	this->Set("ClassIcon.ComboSequenceNode", new IMAGE_BRUSH_SVG("Icons/ComboSequenceNode_16", Icon16));
	this->Set("ClassThumbnail.ComboSequenceNode", new IMAGE_BRUSH_SVG("Icons/ComboSequenceNode_64", Icon64));

	this->Set("ClassIcon.ComboInputAsset", new IMAGE_BRUSH_SVG("Icons/ComboInputAsset_16", Icon16));
	this->Set("ClassThumbnail.ComboInputAsset", new IMAGE_BRUSH_SVG("Icons/ComboInputAsset_64", Icon64));

	this->Set("ClassIcon.ComboActionGraph", new IMAGE_BRUSH_SVG("Icons/ComboSequenceNode_16", Icon16));
	this->Set("ClassThumbnail.ComboActionGraph", new IMAGE_BRUSH_SVG("Icons/ComboSequenceNode_64", Icon64));

	// Combo Action Graph editor brushes
	{
		this->SetContentRoot(PluginSlateDir / TEXT("Resources/Slate/Editor"));
		this->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate/Editor"));

		this->Set("MDSStyleSet.AutoArrange.small", new IMAGE_BRUSH(TEXT("AutoArrangeIcon"), Icon16));
		this->Set("MDSStyleSet.AutoArrange", new IMAGE_BRUSH(TEXT("AutoArrangeIcon"), Icon40));
		this->Set("MDSStyleSet.AutoArrange.large", new IMAGE_BRUSH(TEXT("AutoArrangeIcon"), Icon64));

		this->Set("MDSStyleSet.GraphSettings.small", new IMAGE_BRUSH(TEXT("GraphSettings"), Icon16));
		this->Set("MDSStyleSet.GraphSettings", new IMAGE_BRUSH(TEXT("GraphSettings"), Icon40));
		this->Set("MDSStyleSet.GraphSettings.large", new IMAGE_BRUSH(TEXT("GraphSettings"), Icon64));

		this->Set("MDSStyleSet.ValidateGraph.small", new IMAGE_BRUSH(TEXT("ValidateGraph"), Icon16));
		this->Set("MDSStyleSet.ValidateGraph", new IMAGE_BRUSH(TEXT("ValidateGraph"), Icon40));
		this->Set("MDSStyleSet.ValidateGraph.large", new IMAGE_BRUSH(TEXT("ValidateGraph"), Icon64));

		this->Set("MDSStyleSet.Graph.NodeOverlay", new BOX_BRUSH(TEXT("NodeOverlay"), FMargin(8.0f / 64.0f, 3.0f / 32.0f, 0, 0)));
		this->Set("MDSStyleSet.Graph.PinDocksOverlay", new BOX_BRUSH(TEXT("PinDocksOverlay"), FMargin(8.0f / 64.0f, 3.0f / 32.0f, 0, 0)));

		this->Set("MDSStyleSet.Graph.SimpleArrow", new IMAGE_BRUSH(TEXT("SimpleArrow"), Icon16));
		this->Set("MDSStyleSet.Graph.HollowArrow", new IMAGE_BRUSH(TEXT("HollowArrow"), Icon16));
		this->Set("MDSStyleSet.Graph.FancyArrow", new IMAGE_BRUSH(TEXT("FancyArrow"), Icon16));
		this->Set("MDSStyleSet.Graph.Bubble", new IMAGE_BRUSH(TEXT("Bubble"), Icon16));

		this->Set("MDSStyleSet.Node.SoftEdges", new BOX_BRUSH(TEXT("NodeSoftCorners"), FMargin(16.f / 64.f, 25.f / 64.f, 16.f / 64.f, 16.f / 64.f)));
		this->Set("MDSStyleSet.Node.HardEdges", new BOX_BRUSH(TEXT("NodeHardCorners"), FMargin(16.f / 64.f, 25.f / 64.f, 16.f / 64.f, 16.f / 64.f)));

		this->Set("MDSStyleSet.Node.TextSoftEdges", new BOX_BRUSH(TEXT("TextSoftCorners"), FMargin(16.f / 64.f, 25.f / 64.f, 16.f / 64.f, 16.f / 64.f)));
		this->Set("MDSStyleSet.Node.TextHardEdges", new BOX_BRUSH(TEXT("TextHardCorners"), FMargin(16.f / 64.f, 25.f / 64.f, 16.f / 64.f, 16.f / 64.f)));

		this->Set("MDSStyleSet.Node.IndexCircle", new IMAGE_BRUSH(TEXT("IndexIcon"), Icon16));

		this->Set("MDSStyleSet.Icon.OK", new IMAGE_BRUSH(TEXT("OKIcon"), Icon16));
		this->Set("MDSStyleSet.Icon.Error", new IMAGE_BRUSH(TEXT("ErroIcon"), Icon16));
		this->Set("MDSStyleSet.Icon.BulletPoint", new IMAGE_BRUSH(TEXT("CircleBox"), Icon16));

		this->Set("MDSStyleSet.Graph.CornerImage", new IMAGE_BRUSH(TEXT("Icon128"), FVector2D(128.0f, 128.0f)));

		this->Set("MDSStyleSet.Icon.Browse", new IMAGE_BRUSH(TEXT("BrowseIcon"), Icon12));
		this->Set("MDSStyleSet.Icon.Edit", new IMAGE_BRUSH(TEXT("EditIcon"), Icon12));

		this->Set("MDSStyleSet.Buttons.Documentation", new IMAGE_BRUSH(TEXT("Documentation"), FVector2D(200.0f, 70.0f)));
		this->Set("MDSStyleSet.Buttons.Documentation.small", new IMAGE_BRUSH(TEXT("DocumentationIcon"), Icon12));

		this->Set("MDSStyleSet.Node.Icon.large", new IMAGE_BRUSH(TEXT("DialogueNodeIcon"), Icon64));
		this->Set("MDSStyleSet.Node.Icon", new IMAGE_BRUSH(TEXT("DialogueNodeIcon"), Icon16));
		this->Set("MDSStyleSet.Node.Icon.small", new IMAGE_BRUSH(TEXT("DialogueNodeIcon"), Icon12));

		this->Set("MDSStyleSet.Icon.Close", new IMAGE_BRUSH(TEXT("CloseIcon"), Icon12));
		this->Set("MDSStyleSet.Icon.SupportDiscord", new IMAGE_BRUSH(TEXT("DiscordIcon"), Icon12));
		this->Set("MDSStyleSet.Icon.HeartIcon", new IMAGE_BRUSH(TEXT("HeartIcon"), Icon12));
		this->Set("MDSStyleSet.Icon.UBIcon", new IMAGE_BRUSH(TEXT("UnrealBucketIcon"), Icon12));
		this->Set("MDSStyleSet.Icon.MoneyIcon", new IMAGE_BRUSH(TEXT("MoneyIcon"), Icon12));

		this->Set("MDSStyleSet.Buttons.Style", FButtonStyle()
			.SetNormal(BOX_BRUSH("RoundedSelection_16x", 4.0f / 16.0f, FLinearColor(1, 1, 1, 0.1f)))
			.SetHovered(BOX_BRUSH("RoundedSelection_16x", 4.0f / 16.0f, FLinearColor(1, .55f, 0, 0.2f)))
			.SetPressed(BOX_BRUSH("RoundedSelection_16x", 4.0f / 16.0f, FLinearColor(1, .55f, 0, 0.4f))));
		const FScrollBarStyle ScrollBar = GetWidgetStyle<FScrollBarStyle>("ScrollBar");

		FTextBlockStyle NormalText = FTextBlockStyle()
			.SetFont(DEFAULT_FONT("Regular", FCoreStyle::RegularTextSize))
			.SetColorAndOpacity(FSlateColor::UseForeground())
			.SetShadowOffset(FVector2D::ZeroVector)
			.SetShadowColorAndOpacity(FLinearColor::Black)
			.SetHighlightColor(FLinearColor(0.02f, 0.3f, 0.0f))
			.SetHighlightShape(BOX_BRUSH("TextBlockHighlightShape", FMargin(3.f / 8.f)));

		FTextBlockStyle NodeTitle = FTextBlockStyle(NormalText)
			.SetFont(DEFAULT_FONT("Bold", 14))
			.SetColorAndOpacity(FLinearColor(230.0f / 255.0f, 230.0f / 255.0f, 230.0f / 255.0f))
			.SetShadowOffset(FVector2D(2, 2))
			.SetShadowColorAndOpacity(FLinearColor(0.f, 0.f, 0.f, 0.7f));
		this->Set("MDSStyleSet.NodeTitle", NodeTitle);

		FEditableTextBoxStyle NodeTitleEditableText = FEditableTextBoxStyle()
			.SetFont(NormalText.Font)
			.SetBackgroundImageNormal(BOX_BRUSH("TextBox", FMargin(4.0f / 16.0f)))
			.SetBackgroundImageHovered(BOX_BRUSH("TextBox_Hovered", FMargin(4.0f / 16.0f)))
			.SetBackgroundImageFocused(BOX_BRUSH("TextBox_Hovered", FMargin(4.0f / 16.0f)))
			.SetBackgroundImageReadOnly(BOX_BRUSH("TextBox_ReadOnly", FMargin(4.0f / 16.0f)))
			.SetScrollBarStyle(ScrollBar);
		this->Set("MDSStyleSet.NodeTitleEditableText", NodeTitleEditableText);

		this->Set("MDSStyleSet.NodeTitleInlineEditableText", FInlineEditableTextBlockStyle()
			.SetTextStyle(NodeTitle)
			.SetEditableTextBoxStyle(NodeTitleEditableText)
		);
	}
}


void FComboInputEditorModule::StartupModule()
{
	// Register combo action asset
	IAssetTools &AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
	FComboInputEditorModule::ComboAssetsCategory = AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("Combo Input")), LOCTEXT("ComboInputAssetsCategory", "Combo Input"));
	this->RegisterAssetTypeActions(AssetTools, MakeShareable(new FAssetTypeActions_ComboAction));
	this->RegisterAssetTypeActions(AssetTools, MakeShareable(new FAssetTypeActions_ComboSequenceNode));
	this->RegisterAssetTypeActions(AssetTools, MakeShareable(new FAssetTypeActions_ComboInputAsset));

	// Make a new style set for Combo Input, which will register any custom icons for the types in this plugin
	this->ComboInputEditorStyleSet = MakeShared<FComboInputSlateStyle>();
	FSlateStyleRegistry::RegisterSlateStyle(*this->ComboInputEditorStyleSet.Get());

	// Combo Action Graph
	{
		this->ComboActionGraphPanelNodeFactory = MakeShareable(new FComboActionGraphPanelNodeFactory());
		FEdGraphUtilities::RegisterVisualNodeFactory(this->ComboActionGraphPanelNodeFactory);

		// Register asset actions
		this->ComboActionGraphAssetActions = MakeShared<FAssetTypeActions_ComboActionGraph>();
		this->RegisterAssetTypeActions(AssetTools, this->ComboActionGraphAssetActions.ToSharedRef());
	}

	FComboActionGraphEditorCommands::Register();
}

void FComboInputEditorModule::ShutdownModule()
{
	FComboActionGraphEditorCommands::Unregister();
}

#undef LOCTEXT_NAMESPACE

IMPLEMENT_MODULE(FComboInputEditorModule, ComboInputEditorModule)
