//
// This class implements the layout calculatations for a radial
// menu.
//

#pragma once

#include "CoreMinimal.h"
#include "Components/Widget.h"
#include "Fonts/SlateFontInfo.h"
#include "RadialMenu.generated.h"

class SRadialMenu;
class UTexture2D;


USTRUCT(BlueprintType)
struct FRadialMenuConfig
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, Category="RadialMenu")
    TArray<FString> MenuItems;

    UPROPERTY(EditAnywhere, Category="RadialMenu")
    FSlateFontInfo Font;

    UPROPERTY(EditAnywhere, Category="RadialMenu")
    float InnerRadius = 20.0f;

    UPROPERTY(EditAnywhere, Category="RadialMenu")
    float MinSpoke = 20.0f;

    UPROPERTY(EditAnywhere, Category="RadialMenu")
    float Spread = 20.0f;

    UPROPERTY(EditAnywhere, Category="RadialMenu")
    float LineThickness = 4.0f;

    UPROPERTY(EditAnywhere, Category="RadialMenu")
    FLinearColor ItemColor = FLinearColor::White;

    UPROPERTY(EditAnywhere, Category="RadialMenu")
    TObjectPtr<UTexture2D> DotTexture;

    UPROPERTY(EditAnywhere, Category="RadialMenu")
    float DotRadius = 3.0f;

    UPROPERTY(EditAnywhere, Category="RadialMenu")
    TObjectPtr<UTexture2D> Teardrop;

    UPROPERTY(EditAnywhere, Category="RadialMenu")
    float TeardropRadius = 0.0f;

    UPROPERTY(EditAnywhere, Category="RadialMenu")
    int32 SelectedItem = -1;

    UPROPERTY(EditAnywhere, Category="RadialMenu")
    FLinearColor SelectedColor = FLinearColor::Yellow;

    UPROPERTY(EditAnywhere, Category="RadialMenu")
    float SelectedThickness = 2.0f;
};


USTRUCT(BlueprintType)
struct FRadialMenuItem
{
    GENERATED_BODY()

    UPROPERTY(BlueprintReadOnly)
    FVector2D Point1 = {0,0};

    UPROPERTY(BlueprintReadOnly)
    FVector2D Point2 = {0,0};

    UPROPERTY(BlueprintReadOnly)
    FVector2D Point3 = {0,0};

    UPROPERTY(BlueprintReadOnly)
    bool RightSide = false;

    UPROPERTY(BlueprintReadOnly)
    FVector2D TextSize = {0,0};

    static TArray<FRadialMenuItem> Calculate(const FRadialMenuConfig &Config);

    // Like Calculate, but only produces the unit-vector direction of
    // each spoke.  Cheaper to evaluate when only the spoke directions
    // are needed (e.g. for hit-testing a pointer against the wheel).
    static TArray<FVector2D> CalculateDirections(int32 NumItems);

private:
    using View = TArrayView<FRadialMenuItem>;

    // Give the unit vector for the selected spoke. NSide is
    // the number of spokes on the side of the wheel that
    // we're calculating, and NTotal is the total number of
    // spokes on both sides. The spokes on a given side are
    // organized top-to-bottom, and the angle between the
    // spokes is always equal to (1/NTotal) of the circle.
    static FVector2D SpokeVector(int32 I, int32 NSide, int32 NTotal);

    // Populate Point1 and Point2, these are the
    // endpoints of the spoke segment.  Spokes are
    // designed to always be long enough to make room
    // for MinSpoke, but also to make enough room to
    // keep the menu items from overlapping.
    static void CalculateSpokes(View V, int32 TotalItems, float ItemHeight, float InnerRadius, float MinSpoke);
    
    // Search for the widest spoke, and return its X coordinate.
    static double WidestSpoke(View V);

    // Populate Point3, this is the endpoint of the spread
    // line that goes horizontal.
    static void CalculateSpread(View V, double Offset);

    // Flip everything in the specified view horizontally.
    static void FlipHorizontal(View V);
};


UCLASS(BlueprintType, Blueprintable)
class URadialMenuWidget : public UWidget
{
    GENERATED_BODY()

public:
    UFUNCTION(BlueprintCallable)
    void SetMenuItems(const TArray<FString>& NewMenuItems);

    UFUNCTION(BlueprintCallable)
    void ClearMenuItems() { SetMenuItems({}); }

    UFUNCTION(BlueprintCallable)
    void SetSelectedItem(int32 NewSelectedItem);

    // Returns true if the selected item changed.
    UFUNCTION(BlueprintCallable)
    void AddPointer(FVector2D Direction, float Scale);

    UFUNCTION(BlueprintCallable)
    void SetPointer(FVector2D Direction, float Scale);

    UFUNCTION(BlueprintCallable)
    FVector2D GetPointer() const { return PointerVector; }

    UFUNCTION(BlueprintCallable)
    FString GetSelectedMenuItem() const;

protected:
    UPROPERTY(EditAnywhere)
    FRadialMenuConfig Config;

    TArray<FVector2D> Directions;

    FVector2D PointerVector = {0,0};

    virtual TSharedRef<SWidget> RebuildWidget() override;
    virtual void ReleaseSlateResources(bool bReleaseChildren) override;
    virtual void SynchronizeProperties() override;

    TSharedPtr<SRadialMenu> MySlateWidget;
};
