// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2020.

#include "SFMODEventEditorPanel.h"
#include "FMODStudioModule.h"
#include "FMODUtils.h"
#include "Input/Reply.h"
#include "Widgets/Input/SNumericEntryBox.h"
#include "Widgets/Layout/SExpandableArea.h"
#include "EditorStyle/Public/EditorStyleSet.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Layout/SScrollBox.h"
#include "fmod_studio.hpp"

#define LOCTEXT_NAMESPACE "FMODEventEditor"

SFMODEventEditorPanel::~SFMODEventEditorPanel()
{
}

void SFMODEventEditorPanel::Construct(const FArguments &InArgs)
{
    FMODEventEditorPtr = InArgs._FMODEventEditor;

    FMOD::Studio::EventDescription *EventDescription = FMODEventEditorPtr.Pin()->GetEventDescription();

    TSharedRef<SBorder> ToolbarBorder = ConstructToolbar(EventDescription);
    TSharedRef<SExpandableArea> InfoArea = ConstructInfo(EventDescription);
    TSharedRef<SExpandableArea> ParametersArea = ConstructParameters(EventDescription);
    TSharedRef<SExpandableArea> UserPropertiesArea = ConstructUserProperties(EventDescription);

    TSharedRef<SVerticalBox> ChildWidget = SNew(SVerticalBox) + SVerticalBox::Slot().AutoHeight().Padding(0.0f, 3.0f)[InfoArea] +
                                           SVerticalBox::Slot().AutoHeight().Padding(0.0f, 3.0f)[ParametersArea] +
                                           SVerticalBox::Slot().AutoHeight().Padding(0.0f, 3.0f)[UserPropertiesArea];

    ChildSlot[SNew(SVerticalBox) + SVerticalBox::Slot().AutoHeight().Padding(0.0f, 3.0f)[ToolbarBorder] +
              SVerticalBox::Slot().FillHeight(
                  1.0f)[SNew(SScrollBox) +
                        SScrollBox::Slot().Padding(0.0f)[SNew(SVerticalBox) + SVerticalBox::Slot().AutoHeight().Padding(0.0f)[ChildWidget]]]];
}

TSharedRef<SBorder> SFMODEventEditorPanel::ConstructToolbar(FMOD::Studio::EventDescription *EventDescription)
{
    float MinDistance = 0.0f;
    float MaxDistance = 0.0f;
    int32 EventLengthMS = 0;
    bool bIsOneshot = false, bIsStream = false, bIs3D = false;
    if (EventDescription != nullptr)
    {
        EventDescription->getMinimumDistance(&MinDistance);
        EventDescription->getMaximumDistance(&MaxDistance);
        EventDescription->getLength(&EventLengthMS);
        EventDescription->isOneshot(&bIsOneshot);
        EventDescription->isStream(&bIsStream);
        EventDescription->is3D(&bIs3D);
    }

    const FTimespan EventLength = FTimespan::FromMilliseconds((double)EventLengthMS);
    const FString EventLengthString =
        EventLength.GetHours() <= 0 ? EventLength.ToString(TEXT("%m:%s.%f")) : EventLength.ToString(TEXT("%h:%m:%s.%f"));

    const FText RadiusText =
        FText::Format(LOCTEXT("RadiusFormat", "Distance Attenuation: {0}m to {1}m"), FText::AsNumber(MinDistance), FText::AsNumber(MaxDistance));
    const FText LengthText = FText::Format(LOCTEXT("LengthFormat", "Length: {0}"), FText::FromString(EventLengthString));

    FText EventInfoText;
    if (bIs3D && bIsOneshot)
    {
        EventInfoText = FText::Format(LOCTEXT("RadiusLengthFormat", "{0}  -  {1}"), RadiusText, LengthText);
    }
    else if (!bIs3D && bIsOneshot)
    {
        EventInfoText = LengthText;
    }
    else if (bIs3D && !bIsOneshot)
    {
        EventInfoText = RadiusText;
    }

    return SNew(SBorder)
        .BorderImage(FEditorStyle::Get().GetBrush("ToolPanel.GroupBorder"))
        .Padding(6.0f)
        .Content()[SNew(SHorizontalBox) +
                   SHorizontalBox::Slot()
                       .AutoWidth()
                       .Padding(0.0f, 0.0f, 2.0f, 0.0f)
                       .VAlign(VAlign_Center)
                       .HAlign(HAlign_Left)[SNew(SButton)
                                                .VAlign(VAlign_Center)
                                                .Text(LOCTEXT("Play", "Play"))
                                                .ContentPadding(4)
                                                .OnClicked(this, &SFMODEventEditorPanel::OnClickedPlay)] +
                   SHorizontalBox::Slot()
                       .AutoWidth()
                       .Padding(2.0f, 0.0f)
                       .VAlign(VAlign_Center)
                       .HAlign(HAlign_Left)
                           [SNew(SButton).Text(LOCTEXT("Pause", "Pause")).ContentPadding(4).OnClicked(this, &SFMODEventEditorPanel::OnClickedPause)] +
                   SHorizontalBox::Slot()
                       .AutoWidth()
                       .Padding(2.0f, 0.0f)
                       .VAlign(VAlign_Center)
                       .HAlign(HAlign_Left)[SNew(SButton)
                                                .VAlign(VAlign_Center)
                                                .Text(LOCTEXT("Stop", "Stop"))
                                                .ContentPadding(4)
                                                .OnClicked(this, &SFMODEventEditorPanel::OnClickedStop)] +
                   SHorizontalBox::Slot()
                       .FillWidth(1.0f)
                       .Padding(2.0f, 0.0f)
                       .VAlign(VAlign_Center)
                       .HAlign(HAlign_Right)[SNew(STextBlock).Text(EventInfoText)]];
}

void AddTextField(TSharedRef<SVerticalBox> &InfoBox, const TCHAR *Name, const FText &Value)
{
    InfoBox->AddSlot().Padding(
        4.0f, 3.0f)[SNew(SHorizontalBox) + SHorizontalBox::Slot().FillWidth(0.3f)[SNew(STextBlock).Text(FText::FromString(Name))] +
                    SHorizontalBox::Slot()[SNew(SEditableText).Text(Value).IsReadOnly(true)]];
}

void AddBoolField(TSharedRef<SVerticalBox> &InfoBox, const TCHAR *Name, bool bValue)
{
    AddTextField(InfoBox, Name, bValue ? LOCTEXT("True", "True") : LOCTEXT("False", "False"));
}

void AddFloatField(TSharedRef<SVerticalBox> &InfoBox, const TCHAR *Name, float Value)
{
    AddTextField(InfoBox, Name, FText::AsNumber(Value));
}

TSharedRef<SExpandableArea> MakeBox(TSharedRef<SVerticalBox> &InfoBox, const FText &Value)
{
    return SNew(SExpandableArea)
        .AreaTitle(Value)
        .InitiallyCollapsed(false)
        .BodyContent()[SNew(SBorder).BorderImage(FCoreStyle::Get().GetBrush("NoBorder")).Padding(4.0f).Content()[InfoBox]];
}

TSharedRef<SExpandableArea> SFMODEventEditorPanel::ConstructInfo(FMOD::Studio::EventDescription *EventDescription)
{
    TSharedRef<SVerticalBox> InfoBox = SNew(SVerticalBox);

    if (EventDescription != nullptr)
    {
        FString EventPath = FMODUtils::GetPath(EventDescription);
        FGuid Guid = FMODUtils::GetID(EventDescription);

        int Length = 0.0f;
        float MinDist = 0.0f;
        float MaxDist = 0.0f;
        EventDescription->getLength(&Length);
        EventDescription->getMinimumDistance(&MinDist);
        EventDescription->getMaximumDistance(&MaxDist);

        bool bOneShot = false;
        bool bStream = false;
        bool b3D = false;
        EventDescription->isOneshot(&bOneShot);
        EventDescription->isStream(&bStream);
        EventDescription->is3D(&b3D);

        AddTextField(InfoBox, TEXT("Path"), FText::FromString(EventPath));
        AddTextField(InfoBox, TEXT("Guid"), FText::FromString(Guid.ToString(EGuidFormats::DigitsWithHyphensInBraces)));
        AddBoolField(InfoBox, TEXT("OneShot"), bOneShot);
        AddBoolField(InfoBox, TEXT("Streaming"), bStream);
        AddBoolField(InfoBox, TEXT("3D"), b3D);

        AddFloatField(InfoBox, TEXT("Length"), static_cast<float>(Length));
        if (b3D)
        {
            AddFloatField(InfoBox, TEXT("Min Dist"), MinDist);
            AddFloatField(InfoBox, TEXT("Max Dist"), MaxDist);
        }
    }

    return MakeBox(InfoBox, LOCTEXT("EventInfo", "Event Info"));
}

TSharedRef<SExpandableArea> SFMODEventEditorPanel::ConstructParameters(FMOD::Studio::EventDescription *EventDescription)
{
    auto EventEditor = FMODEventEditorPtr.Pin();
    TSharedRef<SVerticalBox> ParametersBox = SNew(SVerticalBox);

    FNumberFormattingOptions Options;
    Options.MinimumFractionalDigits = 1;

    if (EventDescription != nullptr)
    {
        int32 ParameterCount;
        EventDescription->getParameterDescriptionCount(&ParameterCount);
        for (int32 ParamIdx = 0; ParamIdx < ParameterCount; ParamIdx++)
        {
            FMOD_STUDIO_PARAMETER_DESCRIPTION Parameter;
            EventDescription->getParameterDescriptionByIndex(ParamIdx, &Parameter);

            EventEditor->AddParameter(Parameter.id, Parameter.minimum);

            const FString ParameterName = Parameter.type == FMOD_STUDIO_PARAMETER_GAME_CONTROLLED ? FString(UTF8_TO_TCHAR(Parameter.name)) :
                                                                                                    FMODUtils::ParameterTypeToString(Parameter.type);
            const FText ToolTipText = FText::Format(LOCTEXT("ParameterTooltipFormat", "{0} (Min Value: {1} - Max Value: {2})"),
                FText::FromString(ParameterName), FText::AsNumber(Parameter.minimum, &Options), FText::AsNumber(Parameter.maximum, &Options));

            ParametersBox->AddSlot().Padding(4.0f,
                2.0f)[SNew(SHorizontalBox).ToolTipText(ToolTipText) +
                      SHorizontalBox::Slot().FillWidth(0.3f)[SNew(STextBlock).Text(FText::FromString(ParameterName))] +
                      SHorizontalBox::Slot().MaxWidth(200.0f)[SNew(SNumericEntryBox<float>)
                                                                  .Value(this, &SFMODEventEditorPanel::GetParameterValue, Parameter.id)
                                                                  .OnValueChanged(this, &SFMODEventEditorPanel::OnParameterValueChanged, Parameter.id)
                                                                  .AllowSpin(true)
                                                                  .MinValue(Parameter.minimum)
                                                                  .MaxValue(Parameter.maximum)
                                                                  .MinSliderValue(Parameter.minimum)
                                                                  .MaxSliderValue(Parameter.maximum)
                                                                  .Delta(0.01f)]];
        }
    }

    return MakeBox(ParametersBox, LOCTEXT("EventParameters", "Event Parameters"));
}

TSharedRef<SExpandableArea> SFMODEventEditorPanel::ConstructUserProperties(FMOD::Studio::EventDescription *EventDescription)
{
    TSharedRef<SVerticalBox> UserPropertiesBox = SNew(SVerticalBox);

    if (EventDescription != nullptr)
    {
        int32 UserPropertyCount;
        EventDescription->getUserPropertyCount(&UserPropertyCount);
        for (int32 PropertyIdx = 0; PropertyIdx < UserPropertyCount; PropertyIdx++)
        {
            FMOD_STUDIO_USER_PROPERTY UserProperty;
            EventDescription->getUserPropertyByIndex(PropertyIdx, &UserProperty);

            FText PropertyText;
            switch (UserProperty.type)
            {
                case FMOD_STUDIO_USER_PROPERTY_TYPE_INTEGER:
                    PropertyText = FText::AsNumber(UserProperty.intvalue);
                    break;
                case FMOD_STUDIO_USER_PROPERTY_TYPE_BOOLEAN:
                    PropertyText = UserProperty.boolvalue ? LOCTEXT("True", "True") : LOCTEXT("False", "False");
                    break;
                case FMOD_STUDIO_USER_PROPERTY_TYPE_FLOAT:
                    PropertyText = FText::AsNumber(UserProperty.floatvalue);
                    break;
                case FMOD_STUDIO_USER_PROPERTY_TYPE_STRING:
                    PropertyText = FText::FromString(UTF8_TO_TCHAR(UserProperty.stringvalue));
                    break;
            }

            FString UserName(UTF8_TO_TCHAR(UserProperty.name));
            AddTextField(UserPropertiesBox, *UserName, PropertyText);
        }
    }

    return MakeBox(UserPropertiesBox, LOCTEXT("EventUserProperties", "Event User Properties"));
}

FReply SFMODEventEditorPanel::OnClickedPlay()
{
    FMODEventEditorPtr.Pin()->PlayEvent();
    return FReply::Handled();
}

FReply SFMODEventEditorPanel::OnClickedStop()
{
    FMODEventEditorPtr.Pin()->StopEvent();
    return FReply::Handled();
}

FReply SFMODEventEditorPanel::OnClickedPause()
{
    FMODEventEditorPtr.Pin()->PauseEvent();
    return FReply::Handled();
}

void SFMODEventEditorPanel::OnParameterValueChanged(float NewValue, FMOD_STUDIO_PARAMETER_ID ParameterId)
{
    FMODEventEditorPtr.Pin()->SetParameterValue(ParameterId, NewValue);
}

TOptional<float> SFMODEventEditorPanel::GetParameterValue(FMOD_STUDIO_PARAMETER_ID ParameterId) const
{
    return FMODEventEditorPtr.Pin()->GetParameterValue(ParameterId);
}

#undef LOC_NAMESPACE
