// Copyright (c) Microsoft Corporation. 
// Licensed under the MIT license.

#include "SAllowMorphTargetsList.h"
#include "SAllowMorphTargetsWidget.h"
#include "SAllowMorphTargetsMeshView.h"
#include "Engine/SkeletalMesh.h"
#include "Animation/MorphTarget.h"
#include "Misc/ScopedSlowTask.h"
#include "LODRecipe.h"

#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Views/STableRow.h"
#include "Widgets/Layout/SScrollBorder.h"
#include "AssetRegistryModule.h"

#define LOCTEXT_NAMESPACE "MorphTargetList"

static const FName MorphTargetColumnName("Morph Target");
static const FName RemoveMorphTargetColumnName("Remap");

FMorpTargetItem::FMorpTargetItem(UMorphTarget* InMorphTarget, ULODRecipe* InLODRecipeBeingEdited)
	: MorphTarget(InMorphTarget)
	, LODRecipeBeingEdited(InLODRecipeBeingEdited)
{}

class SMorphTargetWidgetItem : public SMultiColumnTableRow<FMorpTargetItemPtr>
{
public:
	SLATE_BEGIN_ARGS(SMorphTargetWidgetItem) {}
	SLATE_END_ARGS()

	void Construct(
		const FArguments& InArgs, 
		const TSharedRef<STableViewBase>& InOwnerTable, 
		FMorpTargetItemPtr InListItem, 
		TSharedPtr<SListView<FMorpTargetItemPtr>> InMorphTargetListView)
	{
		Item = InListItem;
		WeakMorphTargetListView = InMorphTargetListView;
		SMultiColumnTableRow< FMorpTargetItemPtr >::Construct(
			FSuperRowType::FArguments()
			, InOwnerTable);
	}
private:

	virtual TSharedRef<SWidget> GenerateWidgetForColumn(const FName& ColumnName) override
	{
		const UMorphTarget* MorphTarget = Item->MorphTarget.Get();
		if (ColumnName == MorphTargetColumnName)
		{
			return
				SNew(SBox)
				.Padding(FMargin(4.0f, 2.0f))
				[
					SNew(STextBlock)
					.Text(FText::FromString(MorphTarget->GetFName().ToString()))
				];
		}
		else
		{
			int32 NumLODs = Item->LODRecipeBeingEdited->GetNumLODs();
			for (int32 LODIndex = 1; LODIndex < NumLODs; ++LODIndex)
			{
				FString LODHeader = FString::Format(TEXT("LOD {0}"), { LODIndex });
				if (ColumnName == FName(LODHeader))
				{
					return
						SNew(SCheckBox)
						.Padding(FMargin(4.0f, 2.0f))
						.IsEnabled(this, &SMorphTargetWidgetItem::IsAllowMorphTargetsChecked, LODIndex)
						.IsChecked(this, &SMorphTargetWidgetItem::IsIncludeMorphTargetChecked, MorphTarget, LODIndex)
						.OnCheckStateChanged(this, &SMorphTargetWidgetItem::OnCheckedChanged, LODIndex);
						
				}
			}
			return SNew(STextBlock).Text(LOCTEXT("UnknownColumn", "Unknown Column"));
		}
	}

	void OnCheckedChanged(ECheckBoxState NewCheckedState, int32 LODIndex)
	{
		const TArray<FMorpTargetItemPtr>& SelectedMorphTargets = WeakMorphTargetListView.Pin()->GetSelectedItems();
		const bool bIsItemSelected = WeakMorphTargetListView.Pin()->IsItemSelected(Item);
		if (NewCheckedState == ECheckBoxState::Unchecked)
		{
			if (bIsItemSelected)
			{
				for (const auto& SelectedMorphTarget : SelectedMorphTargets)
				{
					SelectedMorphTarget->LODRecipeBeingEdited->RemoveMorphTargetInLOD(SelectedMorphTarget->MorphTarget.Get(), LODIndex);
				}
			}
			else
			{
				Item->LODRecipeBeingEdited->RemoveMorphTargetInLOD(Item->MorphTarget.Get(), LODIndex);
			}
		}
		else
		{
			if (bIsItemSelected)
			{
				for (const auto& SelectedMorphTarget : SelectedMorphTargets)
				{
					SelectedMorphTarget->LODRecipeBeingEdited->IncludeMorphTargetInLOD(SelectedMorphTarget->MorphTarget.Get(), LODIndex);
				}
			}
			else
			{
				Item->LODRecipeBeingEdited->IncludeMorphTargetInLOD(Item->MorphTarget.Get(), LODIndex);
			}
		}
	}
	
	ECheckBoxState IsIncludeMorphTargetChecked(const UMorphTarget* MorphTarget, const int32 LODIndex) const
	{
		return (Item->LODRecipeBeingEdited->IsMorphTargetRemovedInLOD(MorphTarget, LODIndex) ? ECheckBoxState::Unchecked : ECheckBoxState::Checked);
	}

	bool IsAllowMorphTargetsChecked(const int32 LODIndex) const
	{
		return Item->LODRecipeBeingEdited->IsMorphTargetsAllowedInLOD(LODIndex);
	}

	FMorpTargetItemPtr Item;
	TWeakPtr<SListView<FMorpTargetItemPtr>> WeakMorphTargetListView;
};

void SAllowMorphTargetsList::Construct(const FArguments& Args, const TSharedRef<SAllowMorphTargetsWidget> Owner)
{
	OwnerWidget = Owner;
	bIsActiveTimerRegistered = false;
	OnPropertyChangedDelegateHandle = FCoreUObjectDelegates::OnObjectPropertyChanged.AddSP(this, &SAllowMorphTargetsList::OnPropertyChanged);
	
	RebuildMorphTargetList();

	ListHeaders = SNew(SHeaderRow);
	MainContent = SNew(SVerticalBox);

	MorphTargetListView =
		SNew(SListView<FMorpTargetItemPtr>)
		.SelectionMode(ESelectionMode::Multi)
		.ListItemsSource(&MorphTargetListItems)
		.OnGenerateRow(this, &SAllowMorphTargetsList::OnGenerateRow)
		.HeaderRow(ListHeaders.ToSharedRef());

	ChildSlot
	[
			MainContent.ToSharedRef()
	];

	RefreshColumns();
	RefreshMainContent();
}

SAllowMorphTargetsList::~SAllowMorphTargetsList()
{
	FCoreUObjectDelegates::OnObjectPropertyChanged.Remove(OnPropertyChangedDelegateHandle);
	OnPropertyChangedDelegateHandle.Reset();
}

// Called when a property on the specified object is modified
void SAllowMorphTargetsList::OnPropertyChanged(UObject* ObjectBeingModified, FPropertyChangedEvent& PropertyChangedEvent)
{
	if (ULODRecipe* LODRecipe = Cast<ULODRecipe>(ObjectBeingModified))
	{
		const FName PropertyName(PropertyChangedEvent.Property->GetFName());
		if (PropertyName == GET_MEMBER_NAME_CHECKED(ULODRecipe, NumLODs))
		{
			RefreshColumns();
		}
	}
}

void SAllowMorphTargetsList::Refresh()
{
	if (!bIsActiveTimerRegistered)
	{
		bIsActiveTimerRegistered = true;
		RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &SAllowMorphTargetsList::TriggerRebuildMorphTargetList));
	}
}

void SAllowMorphTargetsList::RefreshColumns()
{
	ListHeaders->ClearColumns();
	auto CreateColumnHeader = [](const FName ColumnName)
	{
		const FMargin Padding(4.0f);
		return
			SHeaderRow::FColumn::FArguments()
			.HeaderContentPadding(Padding)
			.ColumnId(ColumnName)
			[
				SNew(STextBlock)
				.Text(FText::FromName(ColumnName))
				.Font(FCoreStyle::GetDefaultFontStyle("Bold", 10))
			];
	};

	ListHeaders->AddColumn(CreateColumnHeader(MorphTargetColumnName));

	int32 NumLODs = OwnerWidget.Pin()->GetLODRecipe()->GetNumLODs();
	for (int32 LODIndex = 1; LODIndex < NumLODs; ++LODIndex)
	{
		FName LODHeader = FName(FString::Format(TEXT("LOD {0}"), { LODIndex }));
		ListHeaders->AddColumn(CreateColumnHeader(LODHeader));
	}
}

void SAllowMorphTargetsList::RefreshMainContent()
{
	// Reset main content
	MainContent->ClearChildren();

	FSkeletalMeshItemPtr SelectedSkeletalMesh = OwnerWidget.Pin()->GetSelectedSkeletalMesh();

	if (SelectedSkeletalMesh.IsValid())
	{		
		// Show morph target list
		if (SelectedSkeletalMesh->GetNumMorphTargets() > 0)
		{
			MainContent->AddSlot()
				[
					MorphTargetListView.ToSharedRef()
				];
		}
		else
		{
			MainContent->AddSlot()
				[
					SNew(STextBlock)
					.Text(FText::FromString(TEXT("Selected skeletal mesh does not have any morph targets")))
				];
		}
	}
	else
	{
		MainContent->AddSlot()
			[
				SNew(STextBlock)
				.Text(FText::FromString(TEXT("Select skeletal mesh to view morph targets")))
			];
	}
}

void SAllowMorphTargetsList::RebuildMorphTargetList()
{
	FSkeletalMeshItemPtr SelectedSkeletalMesh = OwnerWidget.Pin()->GetSelectedSkeletalMesh();
	MorphTargetListItems.Reset();

	if (SelectedSkeletalMesh.IsValid())
	{
		FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
		FAssetData SkeletalMeshData = AssetRegistryModule.Get().GetAssetByObjectPath(SelectedSkeletalMesh->GetObjectPath());
		
		USkeletalMesh* SkeletalMesh = nullptr;
		if (!SkeletalMeshData.IsAssetLoaded())
		{
			FString SlowTaskDescription = FString::Format(TEXT("Loading Skeletal Mesh {0}..."), { SelectedSkeletalMesh->GetSkeletalMesh().GetAssetName() });
			FScopedSlowTask SlowTask(0, FText::FromString(SlowTaskDescription));
			SlowTask.MakeDialog();
			SkeletalMesh = Cast<USkeletalMesh>(SkeletalMeshData.FastGetAsset(true));
		}
		else
			SkeletalMesh = Cast<USkeletalMesh>(SkeletalMeshData.FastGetAsset());
		
		if (SelectedSkeletalMesh->GetNumMorphTargets() > 0)
		{
			ULODRecipe* LODRecipeBeingEdited = OwnerWidget.Pin()->GetLODRecipe();
			MorphTargetListItems.Reserve(SkeletalMesh->GetMorphTargets().Num());
			for (TObjectPtr<UMorphTarget> MorphTarget : SkeletalMesh->GetMorphTargets())
			{
				MorphTargetListItems.Add(MakeShareable(new FMorpTargetItem(MorphTarget, LODRecipeBeingEdited)));
			}
		}
	}

	// Update the list widget
	if (MorphTargetListView.IsValid())
	{
		MorphTargetListView->RequestListRefresh();
	}
}

EActiveTimerReturnType SAllowMorphTargetsList::TriggerRebuildMorphTargetList(double InCurrentTime, float InDeltaTime)
{
	RebuildMorphTargetList();
	RefreshMainContent();
	bIsActiveTimerRegistered = false;
	return EActiveTimerReturnType::Stop;
}

TSharedRef<ITableRow> SAllowMorphTargetsList::OnGenerateRow(FMorpTargetItemPtr Item, const TSharedRef<STableViewBase>& OwnerTable)
{
	return
		SNew(SMorphTargetWidgetItem, OwnerTable, Item, MorphTargetListView);
}

#undef LOCTEXT_NAMESPACE
