#include "VentuzDataGetterNode.h"

#include "KismetCompiler.h"
#include "BlueprintActionDatabaseRegistrar.h"
#include "BlueprintNodeSpawner.h"
#include "K2Node_CallFunction.h"
#include "AssetRegistryModule.h"
#include "BlueprintFieldNodeSpawner.h"
#include "Engine/UserDefinedStruct.h"
#include "VentuzBPEvent.h"
#include "K2Node_MakeStruct.h"
#include "K2Node_BreakStruct.h"
#include "UserDefinedStructure/UserDefinedStructEditorData.h"
#include "Kismet/KismetMathLibrary.h"
#include "ToolMenu.h"
#include "DetailLayoutBuilder.h"
#include "DetailCategoryBuilder.h"
#include "DetailWidgetRow.h"

#define LOCTEXT_NAMESPACE "UVentuzGetDataNode"

FName FNameFromVentuzType( const VentuzType type )
{
    switch ( type )
    {
    case VentuzType::Float:
        return UEdGraphSchema_K2::PC_Float;
    case VentuzType::Int:
        return UEdGraphSchema_K2::PC_Int;
    case VentuzType::Bool:
        return UEdGraphSchema_K2::PC_Boolean;
    case VentuzType::String:
        return UEdGraphSchema_K2::PC_String;
    case VentuzType::FloatArray:
        return UEdGraphSchema_K2::PC_Float;
    case VentuzType::IntArray:
        return UEdGraphSchema_K2::PC_Int;
    case VentuzType::BoolArray:
        return UEdGraphSchema_K2::PC_Boolean;
    case VentuzType::StringArray:
        return UEdGraphSchema_K2::PC_String;
    default:
        return UEdGraphSchema_K2::PC_Wildcard;
    }
}

FEdGraphPinType FEdGraphPinTypeFromVentuzType( const VentuzType type )
{
    bool isArray = type == VentuzType::FloatArray || type == VentuzType::StringArray || type == VentuzType::IntArray || type == VentuzType::BoolArray;

    return FEdGraphPinType( FNameFromVentuzType( type ), NAME_None, nullptr, isArray ? EPinContainerType::Array : EPinContainerType::None, false, FEdGraphTerminalType() );
}

FName FullPinNameFromVentuzField( const FVentuzNodeField& field )
{
    bool isArray = field.fieldType == VentuzType::FloatArray || field.fieldType == VentuzType::StringArray || field.fieldType == VentuzType::IntArray || field.fieldType == VentuzType::BoolArray;

    return FName( field.fieldName + TEXT( "_" ) + FNameFromVentuzType( field.fieldType ).ToString() + ( isArray ? TEXT( "Array" ) : TEXT( "" ) ) );
}

//////////////////////////////////////////////////////////////////////////
// Common Ventuz Sync Node Customization Code (for the sync button)

TSharedRef<IDetailCustomization> FVentuzE2ESyncNodeDetails::MakeInstance()
{
    return MakeShareable( new FVentuzE2ESyncNodeDetails );
}

void FVentuzE2ESyncNodeDetails::CustomizeDetails( IDetailLayoutBuilder& DetailLayout )
{
    const TArray< TWeakObjectPtr<UObject> >& SelectedObjects = DetailLayout.GetSelectedObjects();
    for ( int32 ObjectIndex = 0; ObjectIndex < SelectedObjects.Num(); ++ObjectIndex )
    {
        const TWeakObjectPtr<UObject>& CurrentObject = SelectedObjects[ ObjectIndex ];
        if ( CurrentObject.IsValid() )
        {
            UVentuzE2ESyncNodeBase* CurrentCaptureActor = Cast<UVentuzE2ESyncNodeBase>( CurrentObject.Get() );
            if ( CurrentCaptureActor != NULL )
            {
                getDataNode = CurrentCaptureActor;
                break;
            }
        }
    }

    DetailLayout.EditCategory( "Custom Data Model Sync" )
        .AddCustomRow( NSLOCTEXT( "CustomDataModelSyncDetails", "SyncVentuzNode", "Sync Node Fields" ) )
        .NameContent()
        [
            SNew( STextBlock )
            .Font( IDetailLayoutBuilder::GetDetailFont() )
        .Text( NSLOCTEXT( "CustomDataModelSyncDetails", "SyncVentuzNode", "Sync Node Fields" ) )
        ]
    .ValueContent()
        .MaxDesiredWidth( 125.f )
        .MinDesiredWidth( 125.f )
        [
            SNew( SButton )
            .ContentPadding( 2 )
        .VAlign( VAlign_Center )
        .HAlign( HAlign_Center )
        .OnClicked( this, &FVentuzE2ESyncNodeDetails::OnSyncNodeFields )
        [
            SNew( STextBlock )
            .Font( IDetailLayoutBuilder::GetDetailFont() )
        .Text( NSLOCTEXT( "CustomDataModelSyncDetails", "DoSyncVentuzNode", "Do Sync" ) )
        ]
        ];
}

void FVentuzE2ESyncNodeDetails::CustomizeDetails( const TSharedPtr<IDetailLayoutBuilder>& DetailBuilder )
{
    cachedDetailBuilder = DetailBuilder;
    CustomizeDetails( *DetailBuilder );
}

FReply FVentuzE2ESyncNodeDetails::OnSyncNodeFields()
{
    if ( getDataNode.IsValid() )
        getDataNode->SyncFields();

    return FReply::Handled();
}

FString UVentuzE2ESyncNodeBase::FixName(int index, const FVentuzNodeField& field)
{
#if WITH_EDITOR

    if (field.fieldName.IsEmpty())
    {
        UEnum* pEnum = FindObject<UEnum>(ANY_PACKAGE, TEXT("VentuzType"), true);
        return FString::Printf(TEXT("%s_%d"), *pEnum->GetAuthoredNameStringByIndex((int)field.fieldType),index);
    }
 #endif

    FString name = field.fieldName;

    if (!isalpha( name[0] ) && name[0]!='_')
        name = FString(TEXT("_")).Append( name );

    for (int x=1; x<name.Len(); x++)
        if (!isalnum(name[x]) && name[x]!='_')
            name[x]='_';

    return name;
}

//////////////////////////////////////////////////////////////////////////
// Ventuz -> UE4 Custom model sync node

FText UVentuzE2EGetData::GetNodeTitle( ENodeTitleType::Type TitleType ) const
{
    return LOCTEXT( "GetVentuzE2EGetDataTitle", "Get Data From Ventuz E2E Node" );
}

FText UVentuzE2EGetData::GetTooltipText() const
{
    return LOCTEXT( "GetVentuzE2ENode_Tooltip", "Adds a node that can be used to retrieve multiple values from Ventuz" );
}

FText UVentuzE2EGetData::GetMenuCategory() const
{
    return LOCTEXT( "VentuzGetDataNode_MenuCategory", "Ventuz" );
}

FSlateIcon UVentuzE2EGetData::GetIconAndTint( FLinearColor& OutColor ) const
{
    return FSlateIcon( "EditorStyle", "Kismet.AllClasses.VariableIcon" );
}

void UVentuzE2EGetData::GetMenuActions( FBlueprintActionDatabaseRegistrar& ActionRegistrar ) const
{
    Super::GetMenuActions( ActionRegistrar );

    UClass* Action = GetClass();

    if ( ActionRegistrar.IsOpenForRegistration( Action ) )
    {
        UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create( GetClass() );
        check( Spawner != nullptr );
        ActionRegistrar.AddBlueprintAction( Action, Spawner );
    }
}

void UVentuzE2EGetData::AllocateDefaultPins()
{
    const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();

    CreatePin( EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute );
    CreatePin( EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then );

    UEdGraphPin* InVentuzNodeNamePin = CreatePin( EGPD_Input, UEdGraphSchema_K2::PC_String, TEXT( "Node" ) );
    K2Schema->TrySetDefaultValue( *InVentuzNodeNamePin, "Ventuz Node" );

    UEdGraphPin* OutValidPin = CreatePin( EGPD_Output, UEdGraphSchema_K2::PC_Boolean, TEXT( "IsValid" ) );
    K2Schema->SetPinAutogeneratedDefaultValueBasedOnType( OutValidPin );

    for ( auto& field : fromVentuzFields )
    {
        /*
                FEdGraphPinType pinType;
                pinType.PinCategory = FNameFromVentuzType( field.fieldType );
                pinType.
        */

        //field.pin = CreatePin( EGPD_Output, FNameFromVentuzType( field.fieldType ), FullPinNameFromVentuzField( field ) );
        field.pin = CreatePin(EGPD_Output, FEdGraphPinTypeFromVentuzType(field.fieldType), FullPinNameFromVentuzField( field ) );
        field.pin->PinFriendlyName = FText::FromString( field.fieldName );
    }

    Super::AllocateDefaultPins();
}

void UVentuzE2EGetData::ExpandNode( class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph )
{
    Super::ExpandNode( CompilerContext, SourceGraph );

    auto* execPin = GetExecPin();
    if ( !execPin )
        return;

    auto* thenPin = FindPin( UEdGraphSchema_K2::PN_Then, EGPD_Output );
    if ( !thenPin )
        return;

    auto* outValidPin = FindPin( TEXT( "IsValid" ), EGPD_Output );
    if ( !outValidPin )
        return;

    auto* nodePin = FindPin( TEXT( "Node" ), EGPD_Input );
    if ( !nodePin )
        return;

    UFunction* getBoolValueFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "GetVentuzBoolValueByNodeName" ) ) );
    UFunction* getIntValueFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "GetVentuzIntValueByNodeName" ) ) );
    UFunction* getFloatValueFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "GetVentuzFloatValueByNodeName" ) ) );
    UFunction* getStringValueFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "GetVentuzStringValueByNodeName" ) ) );
    UFunction* getBoolArrayFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "GetVentuzBoolArrayByNodeName" ) ) );
    UFunction* getIntArrayFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "GetVentuzIntArrayByNodeName" ) ) );
    UFunction* getFloatArrayFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "GetVentuzFloatArrayByNodeName" ) ) );
    UFunction* getStringArrayFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "GetVentuzStringArrayByNodeName" ) ) );
    UFunction* boolAndValueFunc = UKismetMathLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "BooleanAND" ) ) );

    UEdGraphPin* lastThen = nullptr;
    UEdGraphPin* lastAndOut = nullptr;

    for ( const auto& fieldData : fromVentuzFields )
    {
        if ( fieldData.direction == VentuzCommunicationDirection::ToVentuz )
            continue;

        auto* field = FindPin( FullPinNameFromVentuzField( fieldData ), EGPD_Output );
        if ( !field )
            continue;

        UFunction* calledFunction = nullptr;

        switch ( fieldData.fieldType )
        {
        case VentuzType::Float:
            calledFunction = getFloatValueFunc;
            break;
        case VentuzType::Int:
            calledFunction = getIntValueFunc;
            break;
        case VentuzType::Bool:
            calledFunction = getBoolValueFunc;
            break;
        case VentuzType::String:
            calledFunction = getStringValueFunc;
            break;
        case VentuzType::FloatArray:
            calledFunction = getFloatArrayFunc;
            break;
        case VentuzType::IntArray:
            calledFunction = getIntArrayFunc;
            break;
        case VentuzType::BoolArray:
            calledFunction = getBoolArrayFunc;
            break;
        case VentuzType::StringArray:
            calledFunction = getStringArrayFunc;
            break;
        default:
            return;
            break;
        }

        auto* getVentuzDataNode = CompilerContext.SpawnIntermediateNode< UK2Node_CallFunction >( this, SourceGraph );
        CompilerContext.MessageLog.NotifyIntermediateObjectCreation( getVentuzDataNode, this );
        getVentuzDataNode->SetFromFunction( calledFunction );
        getVentuzDataNode->AllocateDefaultPins();

        auto* nodeNamePin = getVentuzDataNode->FindPin( TEXT( "nodeName" ), EGPD_Input );
        auto* fieldNamePin = getVentuzDataNode->FindPin( TEXT( "fieldName" ), EGPD_Input );
        auto* availablePin = getVentuzDataNode->FindPin( TEXT( "isAvailable" ), EGPD_Output );
        auto* callResultPin = getVentuzDataNode->FindPin( UEdGraphSchema_K2::PN_ReturnValue, EGPD_Output );

        if ( !nodeNamePin || !fieldNamePin || !availablePin || !callResultPin )
            continue;;

        // connect function call parameters

        // nodeName
        CompilerContext.CopyPinLinksToIntermediate( *nodePin, *nodeNamePin );
        // fieldName
        fieldNamePin->DefaultValue = fieldData.fieldName;
        // result
        CompilerContext.MovePinLinksToIntermediate( *field, *callResultPin );

        if ( !lastThen )
        {
            CompilerContext.MovePinLinksToIntermediate( *execPin, *getVentuzDataNode->GetExecPin() );
            lastAndOut = availablePin;
        }
        else
        {
            lastThen->MakeLinkTo( getVentuzDataNode->GetExecPin() );

            // build AND network

            auto* andNode = CompilerContext.SpawnIntermediateNode< UK2Node_CallFunction >( this, SourceGraph );
            CompilerContext.MessageLog.NotifyIntermediateObjectCreation( andNode, this );
            andNode->SetFromFunction( boolAndValueFunc );
            andNode->AllocateDefaultPins();

            auto* andResultPin = andNode->FindPin( UEdGraphSchema_K2::PN_ReturnValue, EGPD_Output );

            UEdGraphPin* aPin = andNode->FindPin( TEXT( "A" ), EGPD_Input );
            UEdGraphPin* bPin = andNode->FindPin( TEXT( "B" ), EGPD_Input );

            lastAndOut->MakeLinkTo( aPin );
            availablePin->MakeLinkTo( bPin );

            lastAndOut = andResultPin;
        }

        lastThen = getVentuzDataNode->GetThenPin();
    }


    if ( lastThen )
        CompilerContext.MovePinLinksToIntermediate( *thenPin, *lastThen );

    if ( lastAndOut )
        CompilerContext.MovePinLinksToIntermediate( *outValidPin, *lastAndOut );

    BreakAllNodeLinks();
}

void UVentuzE2EGetData::SyncFields()
{
    if ( !IVentuzPlugin::IsAvailable() )
        return;
    FVentuzPlugin& plugin = FModuleManager::LoadModuleChecked<FVentuzPlugin>( "VentuzPlugin" );

    plugin.RequestFieldListUpdate( nodeInVentuzToSyncFrom, this );
}

void UVentuzE2EGetData::PreEditChange( FProperty* PropertyThatWillChange )
{
    Super::PreEditChange( PropertyThatWillChange );
    fieldsBackup = fromVentuzFields;
}

void UVentuzE2EGetData::PostEditChangeProperty( struct FPropertyChangedEvent& PropertyChangedEvent )
{
    Super::PostEditChangeProperty( PropertyChangedEvent );

    const FName PropertyName = ( PropertyChangedEvent.Property != NULL ) ? PropertyChangedEvent.Property->GetFName() : NAME_None;
    if ( PropertyName == GET_MEMBER_NAME_CHECKED( UVentuzE2EGetData, fromVentuzFields ) )
    {
        for (int x=0; x<fromVentuzFields.Num(); x++)
             fromVentuzFields[x].fieldName = FixName(x, fromVentuzFields[x]);

        ReconstructNode();
    }
}

void UVentuzE2EGetData::PostEditChangeChainProperty( struct FPropertyChangedChainEvent& PropertyChangedEvent )
{
    Super::PostEditChangeChainProperty( PropertyChangedEvent );

    const FName PropertyName = ( PropertyChangedEvent.Property != NULL ) ? PropertyChangedEvent.Property->GetFName() : NAME_None;
    if ( PropertyName == GET_MEMBER_NAME_CHECKED( FVentuzNodeField, fieldType ) || PropertyName == GET_MEMBER_NAME_CHECKED( FVentuzNodeField, fieldName ) )
    {
        for (int x = 0; x < fromVentuzFields.Num(); x++)
            fromVentuzFields[x].fieldName = FixName(x, fromVentuzFields[x]);

        if ( fromVentuzFields.Num() != fieldsBackup.Num() )
        {
            ReconstructNode();
            return;
        }

        for ( int x = 0; x < fromVentuzFields.Num(); x++ )
        {
            if ( fromVentuzFields[ x ].fieldType == fieldsBackup[ x ].fieldType )
                continue;

            // type changed
            if ( fromVentuzFields[ x ].pin )
            {
                fromVentuzFields[ x ].pin->bOrphanedPin = true;
                fromVentuzFields[ x ].pin->bNotConnectable = true;
            }
        }

        ReconstructNode();
    }
}

bool UVentuzE2EGetData::ShouldShowNodeProperties() const
{
    return true;
}

void UVentuzE2EGetData::UpdateNodeFields( const std::vector<Ventuz::UE4::FieldDescriptor>& fieldList )
{
    fromVentuzFields.Empty();

    for ( const auto& field : fieldList )
    {
        if ( !field.fromVentuz )
            continue;

        FVentuzNodeFieldFromVentuz newField;
        newField.direction = VentuzCommunicationDirection::FromVentuz;
        newField.fieldType = (VentuzType)field.type;
        newField.fieldName = FString( UTF8_TO_TCHAR( field.fieldName.c_str() ) );
        fromVentuzFields.Add( newField );
    }

    ReconstructNode();
}

//////////////////////////////////////////////////////////////////////////
// UE4 -> Ventuz Custom model sync node

FText UVentuzE2ESendData::GetNodeTitle( ENodeTitleType::Type TitleType ) const
{
    return LOCTEXT( "GetVentuzE2ESendDataTitle", "Send Data To Ventuz E2E Node" );
}

FText UVentuzE2ESendData::GetTooltipText() const
{
    return LOCTEXT( "SendVentuzE2ENode_Tooltip", "Adds a node that can be used to send multiple values to Ventuz" );
}

FText UVentuzE2ESendData::GetMenuCategory() const
{
    return LOCTEXT( "VentuzGetDataNode_MenuCategory", "Ventuz" );
}

FSlateIcon UVentuzE2ESendData::GetIconAndTint( FLinearColor& OutColor ) const
{
    return FSlateIcon( "EditorStyle", "Kismet.AllClasses.VariableIcon" );
}

void UVentuzE2ESendData::GetMenuActions( FBlueprintActionDatabaseRegistrar& ActionRegistrar ) const
{
    Super::GetMenuActions( ActionRegistrar );

    UClass* Action = GetClass();

    if ( ActionRegistrar.IsOpenForRegistration( Action ) )
    {
        UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create( GetClass() );
        check( Spawner != nullptr );
        ActionRegistrar.AddBlueprintAction( Action, Spawner );
    }
}

void UVentuzE2ESendData::AllocateDefaultPins()
{
    const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();

    CreatePin( EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute );
    CreatePin( EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then );

    UEdGraphPin* InVentuzNodeNamePin = CreatePin( EGPD_Input, UEdGraphSchema_K2::PC_String, TEXT( "Node" ) );
    K2Schema->TrySetDefaultValue( *InVentuzNodeNamePin, "Ventuz Node" );

    for ( auto& field : toVentuzFields )
    {
        //field.pin = CreatePin( EGPD_Input, FNameFromVentuzType( field.fieldType ), FullPinNameFromVentuzField( field ) );
        field.pin = CreatePin( EGPD_Input, FEdGraphPinTypeFromVentuzType( field.fieldType ), FullPinNameFromVentuzField( field ) );
        field.pin->PinFriendlyName = FText::FromString( field.fieldName );
    }

    Super::AllocateDefaultPins();
}

void UVentuzE2ESendData::ExpandNode( class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph )
{
    Super::ExpandNode( CompilerContext, SourceGraph );

    auto* execPin = GetExecPin();
    if ( !execPin )
        return;

    auto* thenPin = FindPin( UEdGraphSchema_K2::PN_Then, EGPD_Output );
    if ( !thenPin )
        return;

    auto* nodePin = FindPin( TEXT( "Node" ), EGPD_Input );
    if ( !nodePin )
        return;

    UFunction* sendBoolValueFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "SendVentuzBoolValueByNodeName" ) ) );
    UFunction* sendIntValueFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "SendVentuzIntValueByNodeName" ) ) );
    UFunction* sendFloatValueFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "SendVentuzFloatValueByNodeName" ) ) );
    UFunction* sendStringValueFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "SendVentuzStringValueByNodeName" ) ) );
    UFunction* sendBoolArrayFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "SendVentuzBoolArrayByNodeName" ) ) );
    UFunction* sendIntArrayFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "SendVentuzIntArrayByNodeName" ) ) );
    UFunction* sendFloatArrayFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "SendVentuzFloatArrayByNodeName" ) ) );
    UFunction* sendStringArrayFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "SendVentuzStringArrayByNodeName" ) ) );
    UEdGraphPin* lastThen = nullptr;

    for ( auto& fieldData : toVentuzFields )
    {
        if ( fieldData.direction == VentuzCommunicationDirection::FromVentuz )
            continue;

        auto* inputPin = FindPin( FullPinNameFromVentuzField( fieldData ), EGPD_Input );
        if ( !inputPin )
            continue;

        UFunction* calledFunction = nullptr;

        switch ( fieldData.fieldType )
        {
        case VentuzType::Float:
            calledFunction = sendFloatValueFunc;
            break;
        case VentuzType::Int:
            calledFunction = sendIntValueFunc;
            break;
        case VentuzType::Bool:
            calledFunction = sendBoolValueFunc;
            break;
        case VentuzType::String:
            calledFunction = sendStringValueFunc;
            break;
        case VentuzType::FloatArray:
            calledFunction = sendFloatArrayFunc;
            break;
        case VentuzType::IntArray:
            calledFunction = sendIntArrayFunc;
            break;
        case VentuzType::BoolArray:
            calledFunction = sendBoolArrayFunc;
            break;
        case VentuzType::StringArray:
            calledFunction = sendStringArrayFunc;
            break;
        default:
            return;
            break;
        }

        auto* sendVentuzDataNode = CompilerContext.SpawnIntermediateNode< UK2Node_CallFunction >( this, SourceGraph );
        CompilerContext.MessageLog.NotifyIntermediateObjectCreation( sendVentuzDataNode, this );
        sendVentuzDataNode->SetFromFunction( calledFunction );
        sendVentuzDataNode->AllocateDefaultPins();

        auto* nodeNamePin = sendVentuzDataNode->FindPin( TEXT( "nodeName" ), EGPD_Input );
        auto* fieldNamePin = sendVentuzDataNode->FindPin( TEXT( "fieldName" ), EGPD_Input );
        auto* valuePin = sendVentuzDataNode->FindPin( TEXT( "value" ), EGPD_Input );

        if ( !nodeNamePin || !fieldNamePin || !valuePin )
            continue;

        // connect function call parameters

        // nodeName
        CompilerContext.CopyPinLinksToIntermediate( *nodePin, *nodeNamePin );
        // fieldName
        fieldNamePin->DefaultValue = fieldData.fieldName;
        // value
        CompilerContext.MovePinLinksToIntermediate( *inputPin, *valuePin );

        // execution flow
        if ( !lastThen )
            CompilerContext.MovePinLinksToIntermediate( *execPin, *sendVentuzDataNode->GetExecPin() );
        else
            lastThen->MakeLinkTo( sendVentuzDataNode->GetExecPin() );

        lastThen = sendVentuzDataNode->GetThenPin();
    }


    if ( lastThen )
        CompilerContext.MovePinLinksToIntermediate( *thenPin, *lastThen );

    BreakAllNodeLinks();
}

bool UVentuzE2ESendData::ShouldShowNodeProperties() const
{
    return true;
}

void UVentuzE2ESendData::SyncFields()
{
    if ( !IVentuzPlugin::IsAvailable() )
        return;
    FVentuzPlugin& plugin = FModuleManager::LoadModuleChecked<FVentuzPlugin>( "VentuzPlugin" );

    plugin.RequestFieldListUpdate( nodeInVentuzToSyncFrom, this );
}

void UVentuzE2ESendData::PreEditChange( FProperty* PropertyThatWillChange )
{
    Super::PreEditChange( PropertyThatWillChange );
    fieldsBackup = toVentuzFields;
}

void UVentuzE2ESendData::PostEditChangeProperty( struct FPropertyChangedEvent& PropertyChangedEvent )
{
    Super::PostEditChangeProperty( PropertyChangedEvent );

    const FName PropertyName = ( PropertyChangedEvent.Property != NULL ) ? PropertyChangedEvent.Property->GetFName() : NAME_None;
    if ( PropertyName == GET_MEMBER_NAME_CHECKED( UVentuzE2ESendData, toVentuzFields ) )
    {
        for (int x = 0; x < toVentuzFields.Num(); x++)
            toVentuzFields[x].fieldName = FixName(x, toVentuzFields[x]);

        ReconstructNode();
    }
}

void UVentuzE2ESendData::PostEditChangeChainProperty( struct FPropertyChangedChainEvent& PropertyChangedEvent )
{
    Super::PostEditChangeChainProperty( PropertyChangedEvent );

    const FName PropertyName = ( PropertyChangedEvent.Property != NULL ) ? PropertyChangedEvent.Property->GetFName() : NAME_None;
    if ( PropertyName == GET_MEMBER_NAME_CHECKED( FVentuzNodeField, fieldType ) || PropertyName == GET_MEMBER_NAME_CHECKED( FVentuzNodeField, fieldName ) )
    {
        for (int x = 0; x < toVentuzFields.Num(); x++)
            toVentuzFields[x].fieldName = FixName(x, toVentuzFields[x]);

        if ( toVentuzFields.Num() != fieldsBackup.Num() )
        {
            ReconstructNode();
            return;
        }

        for ( int x = 0; x < toVentuzFields.Num(); x++ )
        {
            if ( toVentuzFields[ x ].fieldType == fieldsBackup[ x ].fieldType )
                continue;

            // type changed
            if ( toVentuzFields[ x ].pin )
            {
                toVentuzFields[ x ].pin->bOrphanedPin = true;
                toVentuzFields[ x ].pin->bNotConnectable = true;
            }
        }

        ReconstructNode();
    }
}

void UVentuzE2ESendData::UpdateNodeFields( const std::vector<Ventuz::UE4::FieldDescriptor>& fieldList )
{
    toVentuzFields.Empty();

    for ( const auto& field : fieldList )
    {
        if ( field.fromVentuz )
            continue;

        FVentuzNodeFieldToVentuz newField;
        newField.direction = VentuzCommunicationDirection::ToVentuz;
        newField.fieldType = (VentuzType)field.type;
        newField.fieldName = FString( UTF8_TO_TCHAR( field.fieldName.c_str() ) );
        toVentuzFields.Add( newField );
    }

    ReconstructNode();
}

//////////////////////////////////////////////////////////////////////////
// Data getter node

FText UVentuzGetValue::GetNodeTitle( ENodeTitleType::Type TitleType ) const
{
    return LOCTEXT( "GetVentuzDataNodeTitle", "Get Ventuz Value" );
}

FText UVentuzGetValue::GetTooltipText() const
{
    return LOCTEXT( "GetVentuzDataNode_Tooltip", "Adds a node that can be used to retrieve a single value from Ventuz" );
}

FText UVentuzGetValue::GetMenuCategory() const
{
    return LOCTEXT( "VentuzGetDataNode_MenuCategory", "Ventuz" );
}

FSlateIcon UVentuzGetValue::GetIconAndTint( FLinearColor& OutColor ) const
{
    return FSlateIcon( "EditorStyle", "Kismet.AllClasses.VariableIcon" );
}

void UVentuzGetValue::GetMenuActions( FBlueprintActionDatabaseRegistrar& ActionRegistrar ) const
{
    Super::GetMenuActions( ActionRegistrar );

    UClass* Action = GetClass();

    if ( ActionRegistrar.IsOpenForRegistration( Action ) )
    {
        UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create( GetClass() );
        check( Spawner != nullptr );
        ActionRegistrar.AddBlueprintAction( Action, Spawner );
    }
}

void UVentuzGetValue::AllocateDefaultPins()
{
    const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();

    CreatePin( EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute );
    CreatePin( EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then );


    UEdGraphPin* InVentuzNodeNamePin = CreatePin( EGPD_Input, UEdGraphSchema_K2::PC_String, TEXT( "Node" ) );
    K2Schema->TrySetDefaultValue( *InVentuzNodeNamePin, "VentuzNode" );

    UEdGraphPin* InVentuzFieldNamePin = CreatePin( EGPD_Input, UEdGraphSchema_K2::PC_String, TEXT( "Field" ) );
    K2Schema->TrySetDefaultValue( *InVentuzFieldNamePin, "NodeField" );

    UEdGraphPin* OutValidPin = CreatePin( EGPD_Output, UEdGraphSchema_K2::PC_Boolean, TEXT( "IsValid" ) );
    K2Schema->SetPinAutogeneratedDefaultValueBasedOnType( OutValidPin );

    //CreatePin( EGPD_Output, FNameFromVentuzType( type ), FName( "Result" ) );
    CreatePin( EGPD_Output, FEdGraphPinTypeFromVentuzType( type ), FName("Result") );

    Super::AllocateDefaultPins();
}

void UVentuzGetValue::ExpandNode( class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph )
{
    Super::ExpandNode( CompilerContext, SourceGraph );

    auto* execPin = GetExecPin();
    if ( !execPin )
        return;

    auto* thenPin = FindPin( UEdGraphSchema_K2::PN_Then, EGPD_Output );
    if ( !thenPin )
        return;

    auto* outPin = FindPin( TEXT( "Result" ), EGPD_Output );
    if ( !outPin )
        return;

    auto* outValidPin = FindPin( TEXT( "IsValid" ), EGPD_Output );
    if ( !outValidPin )
        return;

    auto* nodePin = FindPin( TEXT( "Node" ), EGPD_Input );
    if ( !nodePin )
        return;

    auto* fieldPin = FindPin( TEXT( "Field" ), EGPD_Input );
    if ( !fieldPin )
        return;

    UFunction* getBoolValueFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "GetVentuzBoolValueByNodeName" ) ) );
    UFunction* getIntValueFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "GetVentuzIntValueByNodeName" ) ) );
    UFunction* getFloatValueFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "GetVentuzFloatValueByNodeName" ) ) );
    UFunction* getStringValueFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "GetVentuzStringValueByNodeName" ) ) );
    UFunction* getBoolArrayFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "GetVentuzBoolArrayByNodeName" ) ) );
    UFunction* getIntArrayFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "GetVentuzIntArrayByNodeName" ) ) );
    UFunction* getFloatArrayFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "GetVentuzFloatArrayByNodeName" ) ) );
    UFunction* getStringArrayFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "GetVentuzStringArrayByNodeName" ) ) );

    UFunction* calledFunction = nullptr;

    switch ( type )
    {
    case VentuzType::Float:
        calledFunction = getFloatValueFunc;
        break;
    case VentuzType::Int:
        calledFunction = getIntValueFunc;
        break;
    case VentuzType::Bool:
        calledFunction = getBoolValueFunc;
        break;
    case VentuzType::String:
        calledFunction = getStringValueFunc;
        break;
    case VentuzType::FloatArray:
        calledFunction = getFloatArrayFunc;
        break;
    case VentuzType::IntArray:
        calledFunction = getIntArrayFunc;
        break;
    case VentuzType::BoolArray:
        calledFunction = getBoolArrayFunc;
        break;
    case VentuzType::StringArray:
        calledFunction = getStringArrayFunc;
        break;
    default:
        return;
        break;
    }

    auto* getVentuzDataNode = CompilerContext.SpawnIntermediateNode< UK2Node_CallFunction >( this, SourceGraph );
    CompilerContext.MessageLog.NotifyIntermediateObjectCreation( getVentuzDataNode, this );
    getVentuzDataNode->SetFromFunction( calledFunction );
    getVentuzDataNode->AllocateDefaultPins();

    auto* nodeNamePin = getVentuzDataNode->FindPin( TEXT( "nodeName" ), EGPD_Input );
    auto* fieldNamePin = getVentuzDataNode->FindPin( TEXT( "fieldName" ), EGPD_Input );
    auto* availablePin = getVentuzDataNode->FindPin( TEXT( "isAvailable" ), EGPD_Output );
    auto* callResultPin = getVentuzDataNode->FindPin( UEdGraphSchema_K2::PN_ReturnValue, EGPD_Output );

    if ( !nodeNamePin || !fieldNamePin || !availablePin || !callResultPin )
        return;

    CompilerContext.MovePinLinksToIntermediate( *nodePin, *nodeNamePin );
    CompilerContext.MovePinLinksToIntermediate( *fieldPin, *fieldNamePin );
    CompilerContext.MovePinLinksToIntermediate( *outValidPin, *availablePin );
    CompilerContext.MovePinLinksToIntermediate( *outPin, *callResultPin );

    CompilerContext.MovePinLinksToIntermediate( *execPin, *getVentuzDataNode->GetExecPin() );
    CompilerContext.MovePinLinksToIntermediate( *thenPin, *getVentuzDataNode->GetThenPin() );
}

void UVentuzGetValue::PostEditChangeProperty( struct FPropertyChangedEvent& PropertyChangedEvent )
{
    Super::PostEditChangeProperty( PropertyChangedEvent );

    const FName PropertyName = ( PropertyChangedEvent.Property != NULL ) ? PropertyChangedEvent.Property->GetFName() : NAME_None;
    if ( PropertyName == GET_MEMBER_NAME_CHECKED( UVentuzGetValue, type ) )
    {
        FName outType = FNameFromVentuzType( type );

        bool orphanedResult = false;
        bool unorphanedResult = false;
        for ( UEdGraphPin* pin : Pins )
        {
            if ( pin->GetFName() == FName( "Result" ) )
            {
                if ( pin->PinType.PinCategory != outType )
                {
                    if ( !pin->bOrphanedPin )
                        orphanedResult = true;

                    pin->bOrphanedPin = true;
                    pin->bNotConnectable = true;
                }

                if ( pin->PinType.PinCategory == outType && pin->bOrphanedPin )
                {
                    pin->bOrphanedPin = false;
                    pin->bNotConnectable = false;
                    unorphanedResult = true;
                }
            }
        }

        if ( orphanedResult && !unorphanedResult )
            CreatePin( EGPD_Output, outType, FName( "Result" ) );

        ReconstructNode();
    }
}

bool UVentuzGetValue::ShouldShowNodeProperties() const
{
    return true;
}

//////////////////////////////////////////////////////////////////////////
// Data sender node

FText UVentuzSendValue::GetNodeTitle( ENodeTitleType::Type TitleType ) const
{
    return LOCTEXT( "SendVentuzDataNodeTitle", "Send Ventuz Value" );
}

FText UVentuzSendValue::GetTooltipText() const
{
    return LOCTEXT( "SendVentuzDataNode_Tooltip", "Adds a node that can be used to send a single value to Ventuz" );
}

FText UVentuzSendValue::GetMenuCategory() const
{
    return LOCTEXT( "VentuzSendDataNode_MenuCategory", "Ventuz" );
}

FSlateIcon UVentuzSendValue::GetIconAndTint( FLinearColor& OutColor ) const
{
    return FSlateIcon( "EditorStyle", "Kismet.AllClasses.VariableIcon" );
}

void UVentuzSendValue::GetMenuActions( FBlueprintActionDatabaseRegistrar& ActionRegistrar ) const
{
    Super::GetMenuActions( ActionRegistrar );

    UClass* Action = GetClass();

    if ( ActionRegistrar.IsOpenForRegistration( Action ) )
    {
        UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create( GetClass() );
        check( Spawner != nullptr );
        ActionRegistrar.AddBlueprintAction( Action, Spawner );
    }
}

void UVentuzSendValue::AllocateDefaultPins()
{
    const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();

    CreatePin( EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute );
    CreatePin( EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then );

    UEdGraphPin* InVentuzNodeNamePin = CreatePin( EGPD_Input, UEdGraphSchema_K2::PC_String, TEXT( "Node" ) );
    K2Schema->TrySetDefaultValue( *InVentuzNodeNamePin, "VentuzNode" );

    UEdGraphPin* InVentuzFieldNamePin = CreatePin( EGPD_Input, UEdGraphSchema_K2::PC_String, TEXT( "Field" ) );
    K2Schema->TrySetDefaultValue( *InVentuzFieldNamePin, "NodeField" );

    //CreatePin( EGPD_Input, FNameFromVentuzType( type ), FName( "Value" ) );
    CreatePin( EGPD_Input, FEdGraphPinTypeFromVentuzType( type ), FName( "Value" ) );

    Super::AllocateDefaultPins();
}

void UVentuzSendValue::ExpandNode( class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph )
{
    Super::ExpandNode( CompilerContext, SourceGraph );

    auto* execPin = GetExecPin();
    if ( !execPin )
        return;

    auto* thenPin = FindPin( UEdGraphSchema_K2::PN_Then, EGPD_Output );
    if ( !thenPin )
        return;

    auto* inPin = FindPin( TEXT( "Value" ), EGPD_Input );
    if ( !inPin )
        return;

    auto* nodePin = FindPin( TEXT( "Node" ), EGPD_Input );
    if ( !nodePin )
        return;

    auto* fieldPin = FindPin( TEXT( "Field" ), EGPD_Input );
    if ( !fieldPin )
        return;

    UFunction* sendBoolValueFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "SendVentuzBoolValueByNodeName" ) ) );
    UFunction* sendIntValueFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "SendVentuzIntValueByNodeName" ) ) );
    UFunction* sendFloatValueFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "SendVentuzFloatValueByNodeName" ) ) );
    UFunction* sendStringValueFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "SendVentuzStringValueByNodeName" ) ) );
    UFunction* sendBoolArrayFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "SendVentuzBoolArrayByNodeName" ) ) );
    UFunction* sendIntArrayFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "SendVentuzIntArrayByNodeName" ) ) );
    UFunction* sendFloatArrayFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "SendVentuzFloatArrayByNodeName" ) ) );
    UFunction* sendStringArrayFunc = UVentuzBPFunctionLibrary::StaticClass()->FindFunctionByName( FName( TEXT( "SendVentuzStringArrayByNodeName" ) ) );

    UFunction* calledFunction = nullptr;

    switch ( type )
    {
    case VentuzType::Float:
        calledFunction = sendFloatValueFunc;
        break;
    case VentuzType::Int:
        calledFunction = sendIntValueFunc;
        break;
    case VentuzType::Bool:
        calledFunction = sendBoolValueFunc;
        break;
    case VentuzType::String:
        calledFunction = sendStringValueFunc;
        break;
    case VentuzType::FloatArray:
        calledFunction = sendFloatArrayFunc;
        break;
    case VentuzType::IntArray:
        calledFunction = sendIntArrayFunc;
        break;
    case VentuzType::BoolArray:
        calledFunction = sendBoolArrayFunc;
        break;
    case VentuzType::StringArray:
        calledFunction = sendStringArrayFunc;
        break;
    default:
        return;
        break;
    }

    auto* sendVentuzDataNode = CompilerContext.SpawnIntermediateNode< UK2Node_CallFunction >( this, SourceGraph );
    CompilerContext.MessageLog.NotifyIntermediateObjectCreation( sendVentuzDataNode, this );
    sendVentuzDataNode->SetFromFunction( calledFunction );
    sendVentuzDataNode->AllocateDefaultPins();

    auto* nodeNamePin = sendVentuzDataNode->FindPin( TEXT( "nodeName" ), EGPD_Input );
    auto* fieldNamePin = sendVentuzDataNode->FindPin( TEXT( "fieldName" ), EGPD_Input );
    auto* valuePin = sendVentuzDataNode->FindPin( TEXT( "value" ), EGPD_Input );

    if ( !nodeNamePin || !fieldNamePin || !valuePin )
        return;

    CompilerContext.MovePinLinksToIntermediate( *nodePin, *nodeNamePin );
    CompilerContext.MovePinLinksToIntermediate( *fieldPin, *fieldNamePin );
    CompilerContext.MovePinLinksToIntermediate( *inPin, *valuePin );

    CompilerContext.MovePinLinksToIntermediate( *execPin, *sendVentuzDataNode->GetExecPin() );
    CompilerContext.MovePinLinksToIntermediate( *thenPin, *sendVentuzDataNode->GetThenPin() );
}

void UVentuzSendValue::PostEditChangeProperty( struct FPropertyChangedEvent& PropertyChangedEvent )
{
    Super::PostEditChangeProperty( PropertyChangedEvent );

    const FName PropertyName = ( PropertyChangedEvent.Property != NULL ) ? PropertyChangedEvent.Property->GetFName() : NAME_None;
    if ( PropertyName == GET_MEMBER_NAME_CHECKED( UVentuzGetValue, type ) )
    {
        FName outType = FNameFromVentuzType( type );

        bool orphanedResult = false;
        bool unorphanedResult = false;
        for ( UEdGraphPin* pin : Pins )
        {
            if ( pin->GetFName() == FName( "Value" ) )
            {
                if ( pin->PinType.PinCategory != outType )
                {
                    if ( !pin->bOrphanedPin )
                        orphanedResult = true;

                    pin->bOrphanedPin = true;
                    pin->bNotConnectable = true;
                }

                if ( pin->PinType.PinCategory == outType && pin->bOrphanedPin )
                {
                    pin->bOrphanedPin = false;
                    pin->bNotConnectable = false;
                    unorphanedResult = true;
                }
            }
        }

        if ( orphanedResult && !unorphanedResult )
            CreatePin( EGPD_Input, outType, FName( "Value" ) );

        ReconstructNode();
    }

}

bool UVentuzSendValue::ShouldShowNodeProperties() const
{
    return true;
}
