#pragma once

#include "CoreMinimal.h"
#include "EdGraph/EdGraphPin.h"
#include "Engine/Blueprint.h"
#include "K2Node_CustomEvent.h"
#include "UObject/StrongObjectPtr.h"
#include "WingBasics.h"

struct FBPVariableDescription;
struct WingTokenizer;
class UObject;
class UK2Node_EditablePinBase;
class UK2Node_FunctionEntry;
struct FUserPinInfo;


class WingVariableList
{
public:
    struct Var
    {
        // Internal name.
        FName Name;

        // Type.  Must be a BlueprintType.
        FEdGraphPinType Type;

        // Default Value.
        FString DefaultValue;

        // When parsing a string, true if default was specified.
        bool DefaultSpecified = false;

        // Boolean flags.
        TSet<FName> Flags;

        // This is only populated if these variables are associated
        // with blueprint variables or local variables.
        FBPVariableDescription *BPVar = nullptr;

        // This is only populated if these variables are associated
        // with an editable pin base.
        TSharedPtr<FUserPinInfo> Pin = nullptr;
    };

public:
    // The actual list of variables.
    TArray<Var> Variables;

    // The list has a name, which is used for generating good messages.
    const TCHAR *ListName;

    // Constructor.
    WingVariableList(const TCHAR *MyListName) : ListName(MyListName) {}

    // Empty the variable list.
    void Empty() { Variables.Empty(); }
    
    // Add a variable.
    void Add(const Var &Var) { Variables.Add(Var); }

    // Return true if the variables are empty.
    bool IsEmpty() { return Variables.IsEmpty(); }

    // Print the variables to a string builder.
    void Print(WingOut Out);

    // Print compact: "type name,type name,..."
    void PrintCompact(WingOut Out);
    
    // Clear the BPVar and Pin fields.
    void ClearLinks();

    // Check the sanity of the vars in the array.  If allow
    // is false, then no variables are allowed in the array.
    bool CheckSanity(const TSet<FName> &GoodFlags, bool Allow, WingOut Errors);
};

class WingVariables
{
public:
    using Var = WingVariableList::Var;
    WingVariables() {}

    // The backing store.  Only one of these should be set.

    TStrongObjectPtr<UBlueprint> Blueprint;
    TStrongObjectPtr<UEdGraph> Graph;
    TStrongObjectPtr<UK2Node_CustomEvent> CustomEvent;

    // The Workspace.  At any given time, these may or may not contain
    // the same data as the backing store.

    WingVariableList BlueprintVariables{TEXT("Blueprint Variables")};
    WingVariableList LocalVariables{TEXT("Local Variables")};
    WingVariableList InputVariables{TEXT("Input Variables")};
    WingVariableList OutputVariables{TEXT("Output Variables")};

    // Configure the backing store.  On failure,
    // prints a message and returns false.

    bool SetBackingStore(UObject *Obj, WingOut Errors);

    // Clear the workspace.  Doesn't affect the backing store.

    void Empty();

    // Return true if there are any variables in the workspace.
    //
    bool IsEmpty();

    // Clear the BPVar and Pin fields.

    void ClearLinks();

    // Print the contents of the workspace.

    void Print(WingOut Out);

    // Parse variables.

    bool Parse(const TArray<FString> &Vars, bool NameOnly, WingOut Errors);

    // Load: clear the workspace, then
    // copy everything from the backing store into the workspace.

    void Load(WingOut Errors);

    // Check: make sure the contents of the workspace makes sense
    // given the type of backing store.

    bool Check(WingOut Errors);

    // Use the variables in the workspace to modify the backing store.

    bool Modify(WingOut Errors);

    // Create every variable in the workspace in the backing store.

    bool Create(WingOut Errors);

    // Remove every variable mentioned in the workspace from the backing store.

    bool Remove(WingOut Errors);

private:
    void LoadBlueprint();
    void LoadGraph();
    void LoadLocalVariables(UK2Node_EditablePinBase *Node);
    Var LoadBlueprintVariableDescription(FBPVariableDescription &Desc, UObject *CDO);
    Var LoadLocalVariableDescription(FBPVariableDescription &Desc);
    void LoadEditablePinBase(UK2Node_EditablePinBase *Node, WingVariableList &List);

    bool CheckBlueprint(WingOut Errors);
    bool CheckGraph(WingOut Errors);

    bool ModifyBlueprint(WingOut Errors);
    bool LinkBlueprintVariables(WingOut Errors);
    void ModifyBlueprintVariableFlags(Var &Input);
    bool ModifyBlueprintDefaults(WingOut Errors);

    bool ModifyGraph(WingOut Errors);

    bool CreateBlueprint(WingOut Errors);

    bool CreateGraph(WingOut Errors);

    bool RemoveBlueprint(WingOut Errors);
    bool RemoveGraph(WingOut Errors);

    void LoadCustomEvent();
    bool CheckCustomEvent(WingOut Errors);
    bool ModifyCustomEvent(WingOut Errors);
    bool CreateCustomEvent(WingOut Errors);
    bool RemoveCustomEvent(WingOut Errors);

    // Get the entry, output, and local variable nodes.  The
    // input and local node are guaranteed to be non-null.  The
    // output node is not guaranteed to be non-null unless you
    // pass CreateOutputNode=true.
    bool GetGraphNodes(
        UK2Node_EditablePinBase *&InputNode,
        UK2Node_EditablePinBase *&OutputNode,
        UK2Node_FunctionEntry *&LocalNode,
        bool CreateOutputNode,
        WingOut Errors);

    bool ModifyEditablePinBase(WingVariableList &List, UK2Node_EditablePinBase *Node, WingOut Errors);
    void AddUserPinInfo(const Var &V, EEdGraphPinDirection Dir, UK2Node_EditablePinBase *Node);

    bool ErrorNoBackingStore(WingOut Errors);

    bool ParseVariableFlags(WingTokenizer &Tok, TSet<FName> &Out, WingOut Errors);
    bool ParseOneVariable(WingTokenizer &Tok, FName &Kind, Var &V, bool NameOnly, WingOut Errors);
    WingVariableList *GetList(FName Name);
};
