/*******************************************************************************
The content of this file includes portions of the proprietary AUDIOKINETIC Wwise
Technology released in source code form as part of the game integration package.
The content of this file may not be used without valid licenses to the
AUDIOKINETIC Wwise Technology.
Note that the use of the game engine is subject to the Unreal(R) Engine End User
License Agreement at https://www.unrealengine.com/en-US/eula/unreal
 
License Usage
 
Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use
this file in accordance with the end user license agreement provided with the
software or, alternatively, in accordance with the terms contained
in a written agreement between you and Audiokinetic Inc.
Copyright (c) 2023 Audiokinetic Inc.
*******************************************************************************/

#include "AkWaapiUMG/Components/AkCheckBox.h"
#include "AkAudioDevice.h"
#include "Widgets/SNullWidget.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/Input/SCheckBox.h"
#include "AkWaapiUMG/Components/AkBoolPropertyToControlCustomization.h"
#include "AkWaapiUMG/Components/WwiseBoolPropertyDragDropOp.h"
#include "AkWaapiUtils.h"
#include "AkWaapiBlueprints/AkWaapiCalls.h"
#include "Async/Async.h"
#include "AkWaapiUMG/Components/WwiseUmgDragDropOp.h"
#include "WaapiPicker/SWaapiPicker.h"

#if WITH_EDITOR
#include "Modules/ModuleManager.h"
#include "PropertyEditorModule.h"
#endif

#define LOCTEXT_NAMESPACE "AkWaapiUMG"

////////////////////////////////////////////////
// SCheckBoxDropHandler
////////////////////////////////////////////////

/** Drag-drop zone for adding a Wwise item or a bool property to the CheckBox */
class SCheckBoxDropHandler : public SCompoundWidget
{
public:
	SLATE_BEGIN_ARGS(SCheckBoxDropHandler) {}
		SLATE_DEFAULT_SLOT(FArguments, Content)
		SLATE_EVENT(FOnDrop, OnDrop)
	SLATE_END_ARGS()

	void Construct(const FArguments& InArgs)
	{
		OnDropDelegate = InArgs._OnDrop;

		this->ChildSlot
			[
				SNew(SBorder)
				[
					InArgs._Content.Widget
				]
			];
	}

	FReply OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override
	{
		if (OnDropDelegate.IsBound())
		{
			return OnDropDelegate.Execute(MyGeometry, DragDropEvent);
		}
		return FReply::Handled();
	}

private:
	FOnDrop OnDropDelegate;
};

/////////////////////////////////////////////////////
// UAkCheckBox

UAkCheckBox::UAkCheckBox(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	SCheckBox::FArguments SlateDefaults;
	WidgetStyle = *SlateDefaults._Style;

	CheckedState = ECheckBoxState::Unchecked;

	HorizontalAlignment = SlateDefaults._HAlign;

	IsFocusable = true;
}

void UAkCheckBox::BeginDestroy()
{
	if (SubscriptionIdNameChanged != 0)
	{
		bool isUnsubscribed;
		UAkWaapiCalls::Unsubscribe(FAkWaapiSubscriptionId(SubscriptionIdNameChanged), isUnsubscribed);
		if (isUnsubscribed)
			SubscriptionIdNameChanged = 0;
	}
	if (SubscriptionId != 0)
	{
		bool isUnsubscribed;
		UAkWaapiCalls::Unsubscribe(FAkWaapiSubscriptionId(SubscriptionId), isUnsubscribed);
		if (isUnsubscribed)
			SubscriptionId = 0;
	}

	Super::BeginDestroy();
}

void UAkCheckBox::ReleaseSlateResources(bool bReleaseChildren)
{
	Super::ReleaseSlateResources(bReleaseChildren);

	MyCheckbox.Reset();
}

TSharedRef<SWidget> UAkCheckBox::RebuildWidget()
{
#if WITH_EDITOR
	FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
	PropertyModule.RegisterCustomPropertyTypeLayout("AkBoolPropertyToControl", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FAkBoolPropertyToControlCustomization::MakeInstance));
	PropertyModule.NotifyCustomizationModuleChanged();
#endif//WITH_EDITOR

	MyCheckbox = SNew(SCheckBox)
		.OnCheckStateChanged(BIND_UOBJECT_DELEGATE(FOnCheckStateChanged, SlateOnCheckStateChangedCallback))
		.Style(&WidgetStyle)
		.HAlign(HorizontalAlignment)
		.IsFocusable(IsFocusable)
		;

	if (GetChildrenCount() > 0)
	{
		MyCheckbox->SetContent(GetContentSlot()->Content ? GetContentSlot()->Content->TakeWidget() : SNullWidget::NullWidget);
	}

	return
		SNew(SCheckBoxDropHandler)
		.OnDrop(FOnDrop::CreateUObject(this, &UAkCheckBox::OnDropHandler))
		[
			SNew(SHorizontalBox)
			+ SHorizontalBox::Slot()
			.AutoWidth()
			.Padding(FMargin(3.0f, 3.0f))
			.HAlign(HAlign_Center)
			.VAlign(VAlign_Center)
			[
				MyCheckbox.ToSharedRef()
			]
			+ SHorizontalBox::Slot()
			.HAlign(HAlign_Left)
			.VAlign(VAlign_Center)
			[
				SNew(SVerticalBox)
				+ SVerticalBox::Slot()
				.AutoHeight()
				.Padding(5.0f, 0.0f, 0.0f, 0.0f)
				.HAlign(HAlign_Left)
				[
					SNew(STextBlock)
					.Text(TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateUObject(this, &UAkCheckBox::GetAkItemControlled)))
				]
				+ SVerticalBox::Slot()
				.AutoHeight()
				.Padding(5.0f, 0.0f, 0.0f, 0.0f)
				.HAlign(HAlign_Left)
				[
					SNew(STextBlock)
					.Text(TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateUObject(this, &UAkCheckBox::GetAkBoolProperty)))
				]
			]
		];
}

void UAkCheckBox::SynchronizeProperties()
{
	Super::SynchronizeProperties();

	MyCheckbox->SetStyle(&WidgetStyle);
	MyCheckbox->SetIsChecked(PROPERTY_BINDING(ECheckBoxState, CheckedState) );

	ItemToControl.ItemPath = ItemToControl.ItemPicked.ItemPath;

	FGuid ItemId;
	if (!ItemToControl.ItemPicked.ItemId.IsEmpty())
	{
		if(FGuid::ParseExact(ItemToControl.ItemPicked.ItemId, EGuidFormats::DigitsWithHyphensInBraces, ItemId))
		{
			SetAkItemId(ItemId);
		}
	}
	SetAkBoolProperty(ThePropertyToControl.ItemProperty);
}

void UAkCheckBox::SetAkItemControlled(const FString& Item)
{
	ItemControlled = Item;
}

FText UAkCheckBox::GetAkItemControlled()
{
	return FText::FromString(TEXT("Item : ") + ItemControlled);
}

void UAkCheckBox::SetAkItemId(const FGuid& ItemId)
{
	if (ItemId.IsValid())
	{
		IdItemToControl = ItemId.ToString(EGuidFormats::DigitsWithHyphensInBraces);
			
		TSharedPtr<FJsonObject> getResult;
		if (!SWaapiPicker::CallWaapiGetInfoFrom(WwiseWaapiHelper::ID, IdItemToControl, getResult, {}))
		{
			return;
		}
		TArray<TSharedPtr<FJsonValue>> StructJsonArray = getResult->GetArrayField(WwiseWaapiHelper::RETURN);
		if (StructJsonArray.Num() > 0)
		{
			const TSharedPtr<FJsonObject>& ItemInfoObj = StructJsonArray[0]->AsObject();
			SetAkItemControlled(ItemInfoObj->GetStringField(WwiseWaapiHelper::NAME));
		}
#if AK_SUPPORT_WAAPI
		/** UnSubscribe to object renamed to be notified from Wwise using WAAPI, so we can maintain the name of the item controlled up to date dynamically. */
		// Connect to Wwise Authoring on localhost.
		if (SubscriptionIdNameChanged != 0)
		{
			bool isUnsubscribed;
			UAkWaapiCalls::Unsubscribe(FAkWaapiSubscriptionId(SubscriptionIdNameChanged), isUnsubscribed);
            if (isUnsubscribed)
                SubscriptionIdNameChanged = 0;
		}
		TSharedPtr<FJsonObject> outJsonResult;
		/** Subscribe to object renamed-created-deleted and removed to be notified from Wwise using WAAPI, so we can maintain the Wise picker up to date dynamically. */
		// Connect to Wwise Authoring on localhost.
		FAkWaapiClient* waapiClient = FAkWaapiClient::Get();
		if (waapiClient)
		{
			auto wampEventCallback = WampEventCallback::CreateLambda([this](uint64_t id, TSharedPtr<FJsonObject> in_UEJsonObject)
			{
				AsyncTask(ENamedThreads::GameThread, [this, in_UEJsonObject]
				{
					SetAkItemControlled(in_UEJsonObject->GetStringField(WwiseWaapiHelper::NEW_NAME));
				});
			});
			// Construct the options Json object : Getting parent infos.
			TSharedRef<FJsonObject> in_options = MakeShareable(new FJsonObject());
			// Subscribe to object renamed-created-deleted and removed notifications.
			waapiClient->Subscribe(ak::wwise::core::object::nameChanged, in_options, wampEventCallback, SubscriptionIdNameChanged, outJsonResult);
		}
		SynchronizePropertyWithWwise();
#endif
	}
}

const FGuid UAkCheckBox::GetAkItemId() const
{
	FGuid ItemId;
	if (!IdItemToControl.IsEmpty())
	{
		FGuid::ParseExact(IdItemToControl, EGuidFormats::DigitsWithHyphensInBraces, ItemId);
	}
	return ItemId;
}

void UAkCheckBox::SetAkBoolProperty(const FString& BoolProperty)
{
	BoolPropertyToControl = BoolProperty;
	SynchronizePropertyWithWwise();
}

const FString UAkCheckBox::GetAkProperty() const
{
	return BoolPropertyToControl;
}

FText UAkCheckBox::GetAkBoolProperty() const
{
	return FText::FromString(TEXT("Property : ") + GetAkProperty());
}

void UAkCheckBox::OnSlotAdded(UPanelSlot* InSlot)
{
	// Add the child to the live slot if it already exists
	if ( MyCheckbox.IsValid() )
	{
		MyCheckbox->SetContent(InSlot->Content ? InSlot->Content->TakeWidget() : SNullWidget::NullWidget);
	}
}

void UAkCheckBox::SynchronizePropertyWithWwise()
{
	if (!IdItemToControl.IsEmpty() && !BoolPropertyToControl.IsEmpty())
	{
		TSharedPtr<FJsonObject> ItemInfoResult;
		if (CallWappiGetPropertySate(IdItemToControl, BoolPropertyToControl, ItemInfoResult))
		{
			bool result = false;

			if (ItemInfoResult.Get()->TryGetBoolField(WwiseWaapiHelper::AT + BoolPropertyToControl, result))
			{
				SetIsChecked(result);
			}
		}

        if (SubscriptionId != 0)
        {
            bool isUnsubscribed;
            UAkWaapiCalls::Unsubscribe(FAkWaapiSubscriptionId(SubscriptionId), isUnsubscribed);
            if (isUnsubscribed)
                SubscriptionId = 0;
        }
		TSharedPtr<FJsonObject> outJsonResult;

		auto wampEventCallback = WampEventCallback::CreateLambda([this](uint64_t id, TSharedPtr<FJsonObject> in_UEJsonObject)
		{
			bool result = false;
			if (in_UEJsonObject->TryGetBoolField(WwiseWaapiHelper::NEW, result))
			{
				SetIsChecked(result);
			}
		});
		SubscribeToPropertyStateChange(IdItemToControl, BoolPropertyToControl, wampEventCallback, SubscriptionId, outJsonResult);
	}
}

void UAkCheckBox::OnSlotRemoved(UPanelSlot* InSlot)
{
	// Remove the widget from the live slot if it exists.
	if ( MyCheckbox.IsValid() )
	{
		MyCheckbox->SetContent(SNullWidget::NullWidget);
	}
}

bool UAkCheckBox::IsPressed() const
{
	if ( MyCheckbox.IsValid() )
	{
		return MyCheckbox->IsPressed();
	}

	return false;
}

bool UAkCheckBox::IsChecked() const
{
	if ( MyCheckbox.IsValid() )
	{
		return MyCheckbox->IsChecked();
	}

	return ( CheckedState == ECheckBoxState::Checked );
}

ECheckBoxState UAkCheckBox::GetCheckedState() const
{
	if ( MyCheckbox.IsValid() )
	{
		return MyCheckbox->GetCheckedState();
	}

	return CheckedState;
}

void UAkCheckBox::SetIsChecked(bool InIsChecked)
{
	CheckedState = InIsChecked ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
	if ( MyCheckbox.IsValid() )
	{
		MyCheckbox->SetIsChecked(PROPERTY_BINDING(ECheckBoxState, CheckedState));
	}
}

void UAkCheckBox::SetCheckedState(ECheckBoxState InCheckedState)
{
	CheckedState = InCheckedState;
	if ( MyCheckbox.IsValid() )
	{
		MyCheckbox->SetIsChecked(PROPERTY_BINDING(ECheckBoxState, CheckedState));
	}
}

void UAkCheckBox::SlateOnCheckStateChangedCallback(ECheckBoxState NewState)
{
	CheckedState = NewState;

	if (ItemControlled.IsEmpty() || BoolPropertyToControl.IsEmpty() || IdItemToControl.IsEmpty())
	{
		UE_LOG(LogAkAudio, Log, TEXT("No item or property to control"));
		return;
	}
	// Construct the arguments Json object : setting configs
	TSharedRef<FJsonObject> args = MakeShareable(new FJsonObject());
	{
		args->SetStringField(WwiseWaapiHelper::OBJECT, IdItemToControl);
		args->SetStringField(WwiseWaapiHelper::PROPERTY, BoolPropertyToControl);
		args->SetBoolField(WwiseWaapiHelper::VALUE, IsChecked()); // this gives us a range of volume from [-96 to 12]
	}

	// Construct the options Json object;
	TSharedRef<FJsonObject> options = MakeShareable(new FJsonObject());

	// Connect to Wwise Authoring on localhost.
	FAkWaapiClient* waapiClient = FAkWaapiClient::Get();
	if (waapiClient)
	{
#if AK_SUPPORT_WAAPI
		TSharedPtr<FJsonObject> outJsonResult;
		// Request data from Wwise using WAAPI
		if (waapiClient->Call(ak::wwise::core::object::setProperty, args, options, outJsonResult))
		{
		}
		else
		{
			UE_LOG(LogAkAudio, Log, TEXT("Call Failed"));
			return;
		}
#endif
	}
	else
	{
		UE_LOG(LogAkAudio, Log, TEXT("Unable to connect to localhost"));
		return;
	}

	// Choosing to treat Undetermined as Checked
	const bool bWantsToBeChecked = NewState != ECheckBoxState::Unchecked;
	AkOnCheckStateChanged.Broadcast(bWantsToBeChecked);
}

FReply UAkCheckBox::OnDropHandler(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent)
{
	FReply HandledState = FReply::Unhandled();
	TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
	if (Operation.IsValid())
	{
		if (Operation->IsOfType<FWwiseUmgDragDropOp>())
		{
			const auto& AssetDragDropOp = StaticCastSharedPtr<FWwiseUmgDragDropOp>(Operation);
			if (AssetDragDropOp.IsValid())
			{
				const auto& WwiseAssets = AssetDragDropOp->GetWiseItems();
				if (WwiseAssets.Num() && WwiseAssets[0].IsValid())
				{
					if (OnItemDropped.IsBound())
					{
						OnItemDropped.Broadcast(WwiseAssets[0]->ItemId);
					}
					else
					{
						SetAkItemId(WwiseAssets[0]->ItemId);
					}
					HandledState = FReply::Handled();
				}
			}
		}
		else if (Operation->IsOfType<FWwiseBoolPropertyDragDropOp>())
		{
			const auto& AssetDragDropOp = StaticCastSharedPtr<FWwiseBoolPropertyDragDropOp>(Operation);
			if (AssetDragDropOp.IsValid())
			{
				const auto& WwiseAssets = AssetDragDropOp->GetWiseProperties();
				if (WwiseAssets.Num() && WwiseAssets[0].IsValid())
				{
					if (OnPropertyDropped.IsBound())
					{
						OnPropertyDropped.Broadcast(*WwiseAssets[0].Get());
					}
					else
					{
						SetAkBoolProperty(*WwiseAssets[0].Get());
					}
					HandledState = FReply::Handled();
				}
			}
		}
	}
    return HandledState;
}


#if WITH_EDITOR

const FText UAkCheckBox::GetPaletteCategory()
{
	return LOCTEXT("Wwise", "Wwise");
}

#endif

/////////////////////////////////////////////////////

#undef LOCTEXT_NAMESPACE