/* Copyright UNmisterIZE(Sebastien Durocher) 2025 All Rights Reserved.

Project: NoiseMapGenerator Plugin
File: NoiseMapGeneratorModule.cpp
*/

#include "NoiseMapGeneratorModule.h"
#include "NoiseMapGenerator.h"
#include "NoiseMapGeneratorSettings.h"
#include "Widgets/Docking/SDockTab.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Input/SSpinBox.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Input/SEditableTextBox.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Text/STextBlock.h"
#include "ToolMenus.h"
#include "LevelEditor.h"
#include "Engine/Texture2D.h"
#include "ContentBrowserModule.h"
#include "IContentBrowserSingleton.h"
#include "Modules/ModuleManager.h"

static const FName NoiseMapGenTabName("NoiseMapGeneratorTab");

// Preview helper
static UTexture2D* CreateTransientTexture(int32 W, int32 H, const TArray<FColor>& Pixels)
{
    if (Pixels.Num() != W * H) return nullptr;
    UTexture2D* T = UTexture2D::CreateTransient(W, H, PF_B8G8R8A8);
    if (!T) return nullptr;
    T->SRGB = false;
    T->CompressionSettings = TC_Default;
    T->MipGenSettings = TMGS_NoMipmaps;

    FTexture2DMipMap& Mip = T->GetPlatformData()->Mips[0];
    void* Data = Mip.BulkData.Lock(LOCK_READ_WRITE);
    const int64 Bytes = (int64)W * H * sizeof(FColor);
    FMemory::Memcpy(Data, Pixels.GetData(), Bytes);
    Mip.BulkData.Unlock();
    T->UpdateResource();
    return T;
}

// Plugin UI setup
class SNoiseMapGeneratorWidget : public SCompoundWidget
{
public:
    SLATE_BEGIN_ARGS(SNoiseMapGeneratorWidget) {}
    SLATE_END_ARGS()

    void Construct(const FArguments&)
    {
        Settings = GetMutableDefault<UNoiseMapGeneratorSettings>();
        PreviewBrush = MakeShared<FSlateBrush>();

 ChildSlot
[
    SNew(SBorder)
    [
        SNew(SSplitter)
        
        + SSplitter::Slot().Value(0.33f)
        [
            SNew(SBorder)
            [
                SNew(SBox)
                .MinDesiredWidth(580.f)   
                [
                    SNew(SVerticalBox)
                    + SVerticalBox::Slot().AutoHeight().Padding(6)[ SNew(STextBlock).Text(FText::FromString("Noise Settings")) ]

                    + SVerticalBox::Slot().AutoHeight().Padding(6)[ MakeRowInt("Width",        Settings->Width,        8, 8192, 64) ]
                    + SVerticalBox::Slot().AutoHeight().Padding(6)[ MakeRowInt("Height",       Settings->Height,       8, 8192, 64) ]
                    + SVerticalBox::Slot().AutoHeight().Padding(6)[ MakeRowInt("Preview Tiling", Settings->TileRepeats,  1,   64,  1) ]
                    + SVerticalBox::Slot().AutoHeight().Padding(6)[ MakeRowInt("Seed",         Settings->Seed,        0, INT_MAX, 1) ]
                    + SVerticalBox::Slot().AutoHeight().Padding(6)[ MakeRowInt("Octaves",      Settings->Octaves,     1,   12,  1) ]
                    + SVerticalBox::Slot().AutoHeight().Padding(6)[ MakeRowFloat("Frequency",  Settings->Frequency,    0.001f,  2.0f, 0.005f) ]
                    + SVerticalBox::Slot().AutoHeight().Padding(6)[ MakeRowFloat("Lacunarity", Settings->Lacunarity,  1.f,    8.f,  0.1f) ]
                    + SVerticalBox::Slot().AutoHeight().Padding(6)[ MakeRowFloat("Gain",       Settings->Gain,        0.f,    1.f,  0.05f) ]
                    + SVerticalBox::Slot().AutoHeight().Padding(6)[ MakeRowBool("Normalize 0..1", Settings->bNormalize) ]

                    + SVerticalBox::Slot().AutoHeight().Padding(6)[ MakeRowMode() ]
                    + SVerticalBox::Slot().AutoHeight().Padding(6)[ MakeRowOutputMode() ]
                    + SVerticalBox::Slot().AutoHeight().Padding(6)[ MakeRowAlgorithm() ]

                    + SVerticalBox::Slot().AutoHeight().Padding(6)[ MakeRowText("Save Path (/Game/...)", Settings->OutputPath) ]
                    + SVerticalBox::Slot().AutoHeight().Padding(6)[ MakeRowText("Base Name",              Settings->BaseName) ]
                    + SVerticalBox::Slot().AutoHeight().Padding(6)[ MakeRowBool("Overwrite Existing",     Settings->bOverwrite) ]
                   // + SVerticalBox::Slot().AutoHeight().Padding(6)[ MakeRowBool("Generate Mips",          Settings->bGenerateMips) ]

                    + SVerticalBox::Slot().AutoHeight().Padding(6)
                    [
                        SNew(SHorizontalBox)
                        + SHorizontalBox::Slot().AutoWidth().Padding(0,0,6,0)
                        [ SNew(SButton).OnClicked(this, &SNoiseMapGeneratorWidget::OnGenerate)
                            [ SNew(STextBlock).Text(FText::FromString("Generate Preview")) ] ]
                        + SHorizontalBox::Slot().AutoWidth().Padding(0,0,6,0)
                        [ SNew(SButton).OnClicked(this, &SNoiseMapGeneratorWidget::OnBake)
                            [ SNew(STextBlock).Text(FText::FromString("Bake & Save")) ] ]
                        + SHorizontalBox::Slot().AutoWidth()
                        [ SNew(SButton).OnClicked(this, &SNoiseMapGeneratorWidget::OnQuit)
                            [ SNew(STextBlock).Text(FText::FromString("Quit")) ] ]
                    ]
                ]
            ]
        ]
        + SSplitter::Slot().Value(0.67f)
        [
            SNew(SBorder)
            [ SAssignNew(PreviewImage, SImage).Image(nullptr) ]
        ]
    ]
];

        Regenerate();
    }

private:
    // UI helpers
    TSharedRef<SWidget> MakeRowInt(const char* Label, int32& V, int32 Min, int32 Max, int32 Step)
    {
        return SNew(SHorizontalBox)
        + SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(0,0,8,0)[ SNew(STextBlock).Text(FText::FromString(Label)) ]
        + SHorizontalBox::Slot().FillWidth(1.f)
        [ SNew(SSpinBox<int32>).MinValue(Min).MaxValue(Max).Delta(Step)
            .Value_Lambda([&V]{ return V; })
            .OnValueChanged_Lambda([&V](int32 NV){ V = NV; }) ];
    }

    TSharedRef<SWidget> MakeRowFloat(const char* Label, float& V, float Min, float Max, float Step)
    {
        return SNew(SHorizontalBox)
        + SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(0,0,8,0)[ SNew(STextBlock).Text(FText::FromString(Label)) ]
        + SHorizontalBox::Slot().FillWidth(1.f)
        [ SNew(SSpinBox<float>).MinValue(Min).MaxValue(Max).Delta(Step)
            .Value_Lambda([&V]{ return V; })
            .OnValueChanged_Lambda([&V](float NV){ V = NV; }) ];
    }

    TSharedRef<SWidget> MakeRowBool(const char* Label, bool& B)
    {
        return SNew(SHorizontalBox)
        + SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(0,0,8,0)[ SNew(STextBlock).Text(FText::FromString(Label)) ]
        + SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center)
        [ SNew(SCheckBox)
            .IsChecked_Lambda([&B]{ return B ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; })
            .OnCheckStateChanged_Lambda([&B](ECheckBoxState S){ B = (S == ECheckBoxState::Checked); }) ];
    }

    TSharedRef<SWidget> MakeRowText(const char* Label, FString& T)
    {
        return SNew(SHorizontalBox)
        + SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(0,0,8,0)[ SNew(STextBlock).Text(FText::FromString(Label)) ]
        + SHorizontalBox::Slot().FillWidth(1.f)
        [ SNew(SEditableTextBox)
            .Text_Lambda([&T]{ return FText::FromString(T); })
            .OnTextCommitted_Lambda([&T](const FText& New, ETextCommit::Type){ T = New.ToString(); }) ];
    }

    TSharedRef<SWidget> MakeRowMode()
    {
        return SNew(SHorizontalBox)
        + SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(0,0,8,0)[ SNew(STextBlock).Text(FText::FromString("Mode")) ]
        + SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center)
        [ SNew(SCheckBox)
            .OnCheckStateChanged_Lambda([this](ECheckBoxState S){ Settings->Mode = (S == ECheckBoxState::Checked) ? ENoiseMode::FBM : ENoiseMode::Perlin; Regenerate(); })
            .IsChecked_Lambda([this]{ return Settings->Mode == ENoiseMode::FBM ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; })
            .Content()[ SNew(STextBlock).Text(FText::FromString("FBM (unchecked = Perlin)")) ] ];
    }

    TSharedRef<SWidget> MakeRowOutputMode()
    {
        return SNew(SHorizontalBox)
        + SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(0,0,8,0)[ SNew(STextBlock).Text(FText::FromString("Output")) ]
        + SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center)
        [ SNew(SCheckBox)
            .OnCheckStateChanged_Lambda([this](ECheckBoxState S){ Settings->OutputMode = (S == ECheckBoxState::Checked) ? ENoiseOutputMode::SplitFirst3Octaves : ENoiseOutputMode::Grayscale; Regenerate(); })
            .IsChecked_Lambda([this]{ return Settings->OutputMode == ENoiseOutputMode::SplitFirst3Octaves ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; })
            .Content()[ SNew(STextBlock).Text(FText::FromString("Split 3 Octaves to RGB")) ] ];
    }

    // ---------------------------
    static FString AlgoToString(ENoiseAlgorithm A)
    {
        switch (A)
        {
        case ENoiseAlgorithm::Perlin:     return TEXT("Perlin");
        case ENoiseAlgorithm::Worley:     return TEXT("Worley (Cellular)");
        case ENoiseAlgorithm::RidgedFBM:  return TEXT("Ridged FBM");
        default:                          return TEXT("Perlin");
        }
    }

    TSharedRef<SWidget> OnGenerateAlgoWidget(TSharedPtr<ENoiseAlgorithm> InItem)
    {
        return SNew(STextBlock).Text(FText::FromString(AlgoToString(*InItem)));
    }

    void OnAlgoChanged(TSharedPtr<ENoiseAlgorithm> NewSel, ESelectInfo::Type)
    {
        if (!NewSel.IsValid()) return;
        Settings->Algorithm = *NewSel;
        SelectedAlgo = NewSel;
        Regenerate();
    }

    FText GetAlgoCurrentText() const
    {
        return FText::FromString(AlgoToString(Settings->Algorithm));
    }

    TSharedRef<SWidget> MakeRowAlgorithm()
    {
        // Populate items once if empty
        if (AlgoItems.Num() == 0)
        {
            AlgoItems.Add(MakeShared<ENoiseAlgorithm>(ENoiseAlgorithm::Perlin));
            AlgoItems.Add(MakeShared<ENoiseAlgorithm>(ENoiseAlgorithm::Worley));
            AlgoItems.Add(MakeShared<ENoiseAlgorithm>(ENoiseAlgorithm::RidgedFBM));

            // pick current
            for (const auto& It : AlgoItems)
            {
                if (*It == Settings->Algorithm)
                {
                    SelectedAlgo = It;
                    break;
                }
            }
            if (!SelectedAlgo.IsValid())
            {
                SelectedAlgo = AlgoItems[0];
            }
        }

        return SNew(SHorizontalBox)
        + SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(0,0,8,0)
          [ SNew(STextBlock).Text(FText::FromString("Algorithm")) ]
        + SHorizontalBox::Slot().FillWidth(1.f)
          [
            SNew(SComboBox<TSharedPtr<ENoiseAlgorithm>>)
            .OptionsSource(&AlgoItems)
            .InitiallySelectedItem(SelectedAlgo)
            .OnGenerateWidget(this, &SNoiseMapGeneratorWidget::OnGenerateAlgoWidget)
            .OnSelectionChanged(this, &SNoiseMapGeneratorWidget::OnAlgoChanged)
            [
                SNew(STextBlock).Text_Lambda([this]{ return GetAlgoCurrentText(); })
            ]
          ];
    }
  
    TArray<TSharedPtr<ENoiseAlgorithm>> AlgoItems;
    TSharedPtr<ENoiseAlgorithm> SelectedAlgo;

    FReply OnGenerate()
    {
        Regenerate();
        return FReply::Handled();
    }

    FReply OnBake()
    {
        FNoiseBakeSettings B;
        B.Width         = Settings->Width;
        B.Height        = Settings->Height;
        B.TileRepeats   = Settings->TileRepeats;
        B.Seed          = Settings->Seed;
        B.Octaves       = Settings->Octaves;
        B.Frequency     = Settings->Frequency;
        B.Lacunarity    = Settings->Lacunarity;
        B.Gain          = Settings->Gain;
        B.bNormalize    = Settings->bNormalize;
        B.Mode          = Settings->Mode;
        B.OutputMode    = Settings->OutputMode;
        B.OutputPath    = Settings->OutputPath;
        B.BaseName      = Settings->BaseName;
        B.bOverwrite    = Settings->bOverwrite;
        //B.bGenerateMips = Settings->bGenerateMips;
        B.Algorithm     = Settings->Algorithm;

        // Calls GeneratePixels then bakes with the same flags as preview helper.
        FNoiseMapGenerator::BakeNoiseTexture(B);

        // Open content browser at location
        {
            FString FolderPathStr = Settings->OutputPath;
            if (!FolderPathStr.StartsWith(TEXT("/Game")))
            {
                FolderPathStr = TEXT("/Game");
            }

            TArray<FString> Folders;
            Folders.Add(FolderPathStr);

            IContentBrowserSingleton& CB =
                FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser").Get();
            CB.SyncBrowserToFolders(Folders);
        }

        return FReply::Handled();
    }
    
    FReply OnQuit()
    {
        TSharedPtr<SDockTab> Tab = FGlobalTabmanager::Get()->FindExistingLiveTab(NoiseMapGenTabName);
        if (Tab.IsValid())
        {
            Tab->RequestCloseTab();
        }
        return FReply::Handled();
    }
    
    void Regenerate()
    {
        FNoiseBakeSettings B;
        B.Width=Settings->Width; B.Height=Settings->Height; B.TileRepeats=Settings->TileRepeats; B.Seed=Settings->Seed;
        B.Octaves=Settings->Octaves; B.Frequency=Settings->Frequency; B.Lacunarity=Settings->Lacunarity; B.Gain=Settings->Gain;
        B.bNormalize=Settings->bNormalize; B.Mode=Settings->Mode; B.OutputMode=Settings->OutputMode;
        B.OutputPath=Settings->OutputPath; B.BaseName=Settings->BaseName; B.bOverwrite=Settings->bOverwrite; //B.bGenerateMips=Settings->bGenerateMips;
        B.Algorithm     = Settings->Algorithm;
    
        TArray<FColor> Pixels; int32 W=0, H=0;
        if (FNoiseMapGenerator::GeneratePixels(B,Settings->TileRepeats, Pixels, W, H))
        {
            if (UTexture2D* T = CreateTransientTexture(W, H, Pixels))
            {
                if (!PreviewBrush.IsValid()) PreviewBrush = MakeShareable(new FSlateBrush());
                PreviewBrush->ImageSize = FVector2D(W, H);
                PreviewBrush->SetResourceObject(T);
                PreviewImage->SetImage(PreviewBrush.Get());
            }
        }
    };
    
private:
    UNoiseMapGeneratorSettings* Settings = nullptr;
    TSharedPtr<SImage>        PreviewImage;
    TSharedPtr<FSlateBrush>   PreviewBrush;
};

void FNoiseMapGeneratorModule::StartupModule()
{
    if (FGlobalTabmanager::Get()->HasTabSpawner(NoiseMapGenTabName))
    {
        FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(NoiseMapGenTabName);
    }

    FGlobalTabmanager::Get()->RegisterNomadTabSpawner(
        NoiseMapGenTabName,
        FOnSpawnTab::CreateLambda([](const FSpawnTabArgs&)
        {
            return SNew(SDockTab)
                .TabRole(ETabRole::NomadTab)
                [
                    SNew(SNoiseMapGeneratorWidget)
                ];
        })
    )
    .SetDisplayName(FText::FromString("Noise Map Generator"))
    .SetMenuType(ETabSpawnerMenuType::Hidden);

    if (UToolMenus::IsToolMenuUIEnabled())
    {
        RegisterMenus();
    }
    else
    {
        UToolMenus::RegisterStartupCallback(
            FSimpleMulticastDelegate::FDelegate::CreateRaw(
                this, &FNoiseMapGeneratorModule::RegisterMenus));
    }
}


void FNoiseMapGeneratorModule::ShutdownModule()
{
    if (FGlobalTabmanager::Get()->HasTabSpawner(NoiseMapGenTabName))
    {
        FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(NoiseMapGenTabName);
    }
    UToolMenus::UnregisterOwner(this); 
}


void FNoiseMapGeneratorModule::OpenWindow()
{
    FGlobalTabmanager::Get()->TryInvokeTab(NoiseMapGenTabName);
}

void FNoiseMapGeneratorModule::RegisterMenus()
{
    FToolMenuOwnerScoped OwnerScoped(this);

    if (UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Window"))
    {
        // Insert a custom section at the top.
        FToolMenuSection& TopSection = Menu->AddSection(
            "NoiseMapGeneratorSection",
            FText::GetEmpty(),
            FToolMenuInsert(NAME_None, EToolMenuInsertType::First) 
        );

        TopSection.AddMenuEntry(
            "OpenNoiseMapGenerator",
            FText::FromString("Noise Map Generator"),
            FText::FromString("Open the Noise Map Generator window"),
            FSlateIcon(),
            FUIAction(FExecuteAction::CreateRaw(this, &FNoiseMapGeneratorModule::OpenWindow))
        );
    }
}

IMPLEMENT_MODULE(FNoiseMapGeneratorModule, NoiseMapGenerator)
