/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* global define, module, require, exports */

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(['jquery',
                'd3',
                'nf.ErrorHandler',
                'nf.Common',
                'nf.Dialog',
                'nf.Storage',
                'nf.Client',
                'nf.CanvasUtils',
                'nf.Connection'],
            function ($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection) {
                return (nf.ConnectionConfiguration = factory($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection));
            });
    } else if (typeof exports === 'object' && typeof module === 'object') {
        module.exports = (nf.ConnectionConfiguration =
            factory(require('jquery'),
                require('d3'),
                require('nf.ErrorHandler'),
                require('nf.Common'),
                require('nf.Dialog'),
                require('nf.Storage'),
                require('nf.Client'),
                require('nf.CanvasUtils'),
                require('nf.Connection')));
    } else {
        nf.ConnectionConfiguration = factory(root.$,
            root.d3,
            root.nf.ErrorHandler,
            root.nf.Common,
            root.nf.Dialog,
            root.nf.Storage,
            root.nf.Client,
            root.nf.CanvasUtils,
            root.nf.Connection);
    }
}(this, function ($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection) {
    'use strict';

    var nfBirdseye;
    var nfGraph;

    var defaultBackPressureObjectThreshold;
    var defaultBackPressureDataSizeThreshold;

    var CONNECTION_OFFSET_Y_INCREMENT = 75;
    var CONNECTION_OFFSET_X_INCREMENT = 200;

    var connectionUpsertionInProgress = false;

    var config = {
        urls: {
            api: '../nifi-api',
            prioritizers: '../nifi-api/flow/prioritizers'
        }
    };

    /**
     * Removes the temporary if necessary.
     */
    var removeTempEdge = function () {
        d3.select('path.connector').remove();
    };

    /**
     * Activates dialog's button model refresh on a connection relationships change.
     */
    var addDialogRelationshipsChangeListener = function() {
        // refresh button model when a relationship selection changes
        $('div.available-relationship').bind('change', function() {
            $('#connection-configuration').modal('refreshButtons');
        });
    }

    /**
     * Initializes the source in the new connection dialog.
     *
     * @argument {selection} source        The source
     */
    var initializeSourceNewConnectionDialog = function (source) {
        // handle the selected source
        if (nfCanvasUtils.isProcessor(source)) {
            return $.Deferred(function (deferred) {
                // initialize the source processor
                initializeSourceProcessor(source).done(function (processor) {
                    if (!nfCommon.isEmpty(processor.relationships)) {
                        // populate the available connections
                        $.each(processor.relationships, function (i, relationship) {
                            createRelationshipOption(relationship.name);
                        });
                        
                        addDialogRelationshipsChangeListener();

                        // if there is a single relationship auto select
                        var relationships = $('#relationship-names').children('div');
                        if (relationships.length === 1) {
                            relationships.children('div.available-relationship').removeClass('checkbox-unchecked').addClass('checkbox-checked');
                        }

                        // configure the button model
                        $('#connection-configuration').modal('setButtonModel', [{
                            buttonText: 'Add',
                            color: {
                                base: '#728E9B',
                                hover: '#004849',
                                text: '#ffffff'
                            },
                            disabled: function () {
                                // ensure some relationships were selected, also check create or updation in progress
                                return getSelectedRelationships().length === 0 || isConnectionUpsertionInProgess();
                            },
                            handler: {
                                click: function () {
                                    addConnection(getSelectedRelationships());
                                }
                            }
                        },
                            {
                                buttonText: 'Cancel',
                                color: {
                                    base: '#E3E8EB',
                                    hover: '#C7D2D7',
                                    text: '#004849'
                                },
                                disabled: function() {
                                    // when add button is clicked, should disable until the addition action is completed
                                    return isConnectionUpsertionInProgess();
                                },
                                handler: {
                                    click: function () {
                                        $('#connection-configuration').modal('hide');
                                    }
                                }
                            }]);

                        // resolve the deferred
                        deferred.resolve();
                    } else {
                        // there are no relationships for this processor
                        nfDialog.showOkDialog({
                            headerText: 'Connection Configuration',
                            dialogContent: '\'' + nfCommon.escapeHtml(processor.name) + '\' does not support any relationships.'
                        });

                        // reset the dialog
                        resetDialog();

                        deferred.reject();
                    }
                }).fail(function () {
                    deferred.reject();
                });
            }).promise();
        } else {
            return $.Deferred(function (deferred) {
                // determine how to initialize the source
                var connectionSourceDeferred;
                if (nfCanvasUtils.isInputPort(source)) {
                    connectionSourceDeferred = initializeSourceInputPort(source);
                } else if (nfCanvasUtils.isRemoteProcessGroup(source)) {
                    connectionSourceDeferred = initializeSourceRemoteProcessGroup(source);
                } else if (nfCanvasUtils.isProcessGroup(source)) {
                    connectionSourceDeferred = initializeSourceProcessGroup(source);
                } else {
                    connectionSourceDeferred = initializeSourceFunnel(source);
                }

                // finish initialization when appropriate
                connectionSourceDeferred.done(function () {
                    // configure the button model
                    $('#connection-configuration').modal('setButtonModel', [{
                        buttonText: 'Add',
                        color: {
                            base: '#728E9B',
                            hover: '#004849',
                            text: '#ffffff'
                        },
                        disabled : function(){
                            // when network is slow, should disable the button
                            return isConnectionUpsertionInProgess();
                        },
                        handler: {
                            click: function () {
                                // add the connection
                                addConnection();
                            }
                        }
                    },
                        {
                            buttonText: 'Cancel',
                            color: {
                                base: '#E3E8EB',
                                hover: '#C7D2D7',
                                text: '#004849'
                            },
                            disabled : function(){
                                return isConnectionUpsertionInProgess();
                            },
                            handler: {
                                click: function () {
                                    $('#connection-configuration').modal('hide');
                                }
                            }
                        }]);

                    deferred.resolve();
                }).fail(function () {
                    deferred.reject();
                });
            }).promise();
        }
    };

    /**
     * Initializes the source when the source is an input port.
     *
     * @argument {selection} source        The source
     */
    var initializeSourceInputPort = function (source) {
        return $.Deferred(function (deferred) {
            // get the input port data
            var inputPortData = source.datum();
            var inputPortName = inputPortData.permissions.canRead ? inputPortData.component.name : inputPortData.id;

            // populate the port information
            $('#input-port-source').show();
            $('#input-port-source-name').text(inputPortName).attr('title', inputPortName);

            // populate the connection source details
            $('#connection-source-id').val(inputPortData.id);
            $('#connection-source-component-id').val(inputPortData.id);

            // populate the group details
            $('#connection-source-group-id').val(nfCanvasUtils.getGroupId());
            $('#connection-source-group-name').text(nfCanvasUtils.getGroupName());

            // resolve the deferred
            deferred.resolve();
        }).promise();
    };

    /**
     * Initializes the source when the source is an input port.
     *
     * @argument {selection} source        The source
     */
    var initializeSourceFunnel = function (source) {
        return $.Deferred(function (deferred) {
            // get the funnel data
            var funnelData = source.datum();

            // populate the port information
            $('#funnel-source').show();

            // populate the connection source details
            $('#connection-source-id').val(funnelData.id);
            $('#connection-source-component-id').val(funnelData.id);

            // populate the group details
            $('#connection-source-group-id').val(nfCanvasUtils.getGroupId());
            $('#connection-source-group-name').text(nfCanvasUtils.getGroupName());

            // resolve the deferred
            deferred.resolve();
        }).promise();
    };

    /**
     * Initializes the source when the source is a processor.
     *
     * @argument {selection} source        The source
     */
    var initializeSourceProcessor = function (source) {
        return $.Deferred(function (deferred) {
            // get the processor data
            var processorData = source.datum();
            var processorName = processorData.permissions.canRead ? processorData.component.name : processorData.id;
            var processorType = processorData.permissions.canRead ? nfCommon.substringAfterLast(processorData.component.type, '.') : 'Processor';

            // populate the source processor information
            $('#processor-source').show();
            $('#processor-source-name').text(processorName).attr('title', processorName);
            $('#processor-source-type').text(processorType).attr('title', processorType);

            // populate the connection source details
            $('#connection-source-id').val(processorData.id);
            $('#connection-source-component-id').val(processorData.id);

            // populate the group details
            $('#connection-source-group-id').val(nfCanvasUtils.getGroupId());
            $('#connection-source-group-name').text(nfCanvasUtils.getGroupName());

            // show the available relationships
            $('#relationship-names-container').show();

            deferred.resolve(processorData.component);
        });
    };

    /**
     * Initializes the source when the source is a process group.
     *
     * @argument {selection} source        The source
     */
    var initializeSourceProcessGroup = function (source) {
        return $.Deferred(function (deferred) {
            // get the process group data
            var processGroupData = source.datum();

            $.ajax({
                type: 'GET',
                url: config.urls.api + '/flow/process-groups/' + encodeURIComponent(processGroupData.id),
                dataType: 'json'
            }).done(function (response) {
                var processGroup = response.processGroupFlow;
                var processGroupName = response.permissions.canRead ? processGroup.breadcrumb.breadcrumb.name : processGroup.id;
                var processGroupContents = processGroup.flow;

                // show the output port options
                var options = [];
                var publicOutputPortCount = 0;
                $.each(processGroupContents.outputPorts, function (i, outputPort) {
                    if (outputPort.allowRemoteAccess) {
                        publicOutputPortCount++;
                        return;
                    }
                    // require explicit access to the output port as it's the source of the connection
                    if (outputPort.permissions.canRead && outputPort.permissions.canWrite) {
                        var component = outputPort.component;
                        options.push({
                            text: component.name,
                            value: component.id,
                            description: nfCommon.escapeHtml(component.comments)
                        });
                    }
                });

                // only proceed if there are output ports
                if (!nfCommon.isEmpty(options)) {
                    $('#output-port-source').show();

                    // sort the options
                    options.sort(function (a, b) {
                        return a.text.localeCompare(b.text);
                    });

                    // create the combo
                    $('#output-port-options').combo({
                        options: options,
                        maxHeight: 300,
                        select: function (option) {
                            $('#connection-source-id').val(option.value);
                        }
                    });

                    // populate the connection details
                    $('#connection-source-component-id').val(processGroup.id);

                    // populate the group details
                    $('#connection-source-group-id').val(processGroup.id);
                    $('#connection-source-group-name').text(processGroupName);

                    deferred.resolve();
                } else {
                    var message = '\'' + nfCommon.escapeHtml(processGroupName) + '\' does not have any local output ports.';
                    if (nfCommon.isEmpty(processGroupContents.outputPorts) === false
                            && processGroupContents.outputPorts.length > publicOutputPortCount) {
                        message = 'Not authorized for any local output ports in \'' + nfCommon.escapeHtml(processGroupName) + '\'.';
                    }

                    // there are no output ports for this process group
                    nfDialog.showOkDialog({
                        headerText: 'Connection Configuration',
                        dialogContent: message
                    });

                    // reset the dialog
                    resetDialog();

                    deferred.reject();
                }
            }).fail(function (xhr, status, error) {
                // handle the error
                nfErrorHandler.handleAjaxError(xhr, status, error);

                deferred.reject();
            });
        }).promise();
    };

    /**
     * Initializes the source when the source is a remote process group.
     *
     * @argument {selection} source        The source
     */
    var initializeSourceRemoteProcessGroup = function (source) {
        return $.Deferred(function (deferred) {
            // get the remote process group data
            var remoteProcessGroupData = source.datum();

            $.ajax({
                type: 'GET',
                url: remoteProcessGroupData.uri,
                dataType: 'json'
            }).done(function (response) {
                var remoteProcessGroup = response.component;
                var remoteProcessGroupContents = remoteProcessGroup.contents;

                // only proceed if there are output ports
                if (!nfCommon.isEmpty(remoteProcessGroupContents.outputPorts)) {
                    $('#output-port-source').show();

                    // show the output port options
                    var options = [];
                    $.each(remoteProcessGroupContents.outputPorts, function (i, outputPort) {
                        options.push({
                            text: outputPort.name,
                            value: outputPort.id,
                            disabled: outputPort.exists === false,
                            description: nfCommon.escapeHtml(outputPort.comments)
                        });
                    });

                    // sort the options
                    options.sort(function (a, b) {
                        return a.text.localeCompare(b.text);
                    });

                    // create the combo
                    $('#output-port-options').combo({
                        options: options,
                        maxHeight: 300,
                        select: function (option) {
                            $('#connection-source-id').val(option.value);
                        }
                    });

                    // populate the connection details
                    $('#connection-source-component-id').val(remoteProcessGroup.id);

                    // populate the group details
                    $('#connection-source-group div.setting-name').text('Within Remote Group')
                    $('#connection-remote-source-url').text(remoteProcessGroup.targetUri).show();
                    $('#connection-source-group-id').val(remoteProcessGroup.id);
                    $('#connection-source-group-name').text(remoteProcessGroup.name);

                    deferred.resolve();
                } else {
                    // there are no relationships for this processor
                    nfDialog.showOkDialog({
                        headerText: 'Connection Configuration',
                        dialogContent: '\'' + nfCommon.escapeHtml(remoteProcessGroup.name) + '\' does not have any output ports.'
                    });

                    // reset the dialog
                    resetDialog();

                    deferred.reject();
                }
            }).fail(function (xhr, status, error) {
                // handle the error
                nfErrorHandler.handleAjaxError(xhr, status, error);

                deferred.reject();
            });
        }).promise();
    };

    var initializeDestinationNewConnectionDialog = function (destination) {
        if (nfCanvasUtils.isOutputPort(destination)) {
            return initializeDestinationOutputPort(destination);
        } else if (nfCanvasUtils.isProcessor(destination)) {
            return initializeDestinationProcessor(destination);
        } else if (nfCanvasUtils.isRemoteProcessGroup(destination)) {
            return initializeDestinationRemoteProcessGroup(destination);
        } else if (nfCanvasUtils.isFunnel(destination)) {
            return initializeDestinationFunnel(destination);
        } else {
            return initializeDestinationProcessGroup(destination);
        }
    };

    var initializeDestinationOutputPort = function (destination) {
        return $.Deferred(function (deferred) {
            var outputPortData = destination.datum();
            var outputPortName = outputPortData.permissions.canRead ? outputPortData.component.name : outputPortData.id;

            $('#output-port-destination').show();
            $('#output-port-destination-name').text(outputPortName).attr('title', outputPortName);

            // populate the connection destination details
            $('#connection-destination-id').val(outputPortData.id);
            $('#connection-destination-component-id').val(outputPortData.id);

            // populate the group details
            $('#connection-destination-group-id').val(nfCanvasUtils.getGroupId());
            $('#connection-destination-group-name').text(nfCanvasUtils.getGroupName());

            deferred.resolve();
        }).promise();
    };

    var initializeDestinationFunnel = function (destination) {
        return $.Deferred(function (deferred) {
            var funnelData = destination.datum();

            $('#funnel-destination').show();

            // populate the connection destination details
            $('#connection-destination-id').val(funnelData.id);
            $('#connection-destination-component-id').val(funnelData.id);

            // populate the group details
            $('#connection-destination-group-id').val(nfCanvasUtils.getGroupId());
            $('#connection-destination-group-name').text(nfCanvasUtils.getGroupName());

            deferred.resolve();
        }).promise();
    };

    var initializeDestinationProcessor = function (destination) {
        return $.Deferred(function (deferred) {
            var processorData = destination.datum();
            var processorName = processorData.permissions.canRead ? processorData.component.name : processorData.id;
            var processorType = processorData.permissions.canRead ? nfCommon.substringAfterLast(processorData.component.type, '.') : 'Processor';

            $('#processor-destination').show();
            $('#processor-destination-name').text(processorName).attr('title', processorName);
            $('#processor-destination-type').text(processorType).attr('title', processorType);

            // populate the connection destination details
            $('#connection-destination-id').val(processorData.id);
            $('#connection-destination-component-id').val(processorData.id);

            // populate the group details
            $('#connection-destination-group-id').val(nfCanvasUtils.getGroupId());
            $('#connection-destination-group-name').text(nfCanvasUtils.getGroupName());

            deferred.resolve();
        }).promise();
    };

    /**
     * Initializes the destination when the destination is a process group.
     *
     * @argument {selection} destination        The destination
     */
    var initializeDestinationProcessGroup = function (destination) {
        return $.Deferred(function (deferred) {
            var processGroupData = destination.datum();

            $.ajax({
                type: 'GET',
                url: config.urls.api + '/flow/process-groups/' + encodeURIComponent(processGroupData.id),
                dataType: 'json'
            }).done(function (response) {
                var processGroup = response.processGroupFlow;
                var processGroupName = response.permissions.canRead ? processGroup.breadcrumb.breadcrumb.name : processGroup.id;
                var processGroupContents = processGroup.flow;

                // show the input port options
                var options = [];
                $.each(processGroupContents.inputPorts, function (i, inputPort) {
                    if (!inputPort.allowRemoteAccess) {
                        options.push({
                            text: inputPort.permissions.canRead ? inputPort.component.name : inputPort.id,
                            value: inputPort.id,
                            description: inputPort.permissions.canRead ? nfCommon.escapeHtml(inputPort.component.comments) : null
                        });
                    }
                });

                // only proceed if there are output ports
                if (!nfCommon.isEmpty(options)) {
                    $('#input-port-destination').show();

                    // sort the options
                    options.sort(function (a, b) {
                        return a.text.localeCompare(b.text);
                    });

                    // create the combo
                    $('#input-port-options').combo({
                        options: options,
                        maxHeight: 300,
                        select: function (option) {
                            $('#connection-destination-id').val(option.value);
                        }
                    });

                    // populate the connection details
                    $('#connection-destination-component-id').val(processGroup.id);

                    // populate the group details
                    $('#connection-destination-group-id').val(processGroup.id);
                    $('#connection-destination-group-name').text(processGroupName);

                    deferred.resolve();
                } else {
                    // there are no relationships for this processor
                    nfDialog.showOkDialog({
                        headerText: 'Connection Configuration',
                        dialogContent: '\'' + nfCommon.escapeHtml(processGroupName) + '\' does not have any local input ports.'
                    });

                    // reset the dialog
                    resetDialog();

                    deferred.reject();
                }
            }).fail(function (xhr, status, error) {
                // handle the error
                nfErrorHandler.handleAjaxError(xhr, status, error);

                deferred.reject();
            });
        }).promise();
    };

    /**
     * Initializes the source when the source is a remote process group.
     *
     * @argument {selection} destination        The destination
     * @argument {object} connectionDestination The connection destination object
     */
    var initializeDestinationRemoteProcessGroup = function (destination, connectionDestination) {
        return $.Deferred(function (deferred) {
            var remoteProcessGroupData = destination.datum();

            $.ajax({
                type: 'GET',
                url: remoteProcessGroupData.uri,
                dataType: 'json'
            }).done(function (response) {
                var remoteProcessGroup = response.component;
                var remoteProcessGroupContents = remoteProcessGroup.contents;

                // only proceed if there are output ports
                if (!nfCommon.isEmpty(remoteProcessGroupContents.inputPorts)) {
                    $('#input-port-destination').show();

                    // show the input port options
                    var options = [];
                    $.each(remoteProcessGroupContents.inputPorts, function (i, inputPort) {
                        options.push({
                            text: inputPort.name,
                            value: inputPort.id,
                            disabled: inputPort.exists === false,
                            description: nfCommon.escapeHtml(inputPort.comments)
                        });
                    });

                    // sort the options
                    options.sort(function (a, b) {
                        return a.text.localeCompare(b.text);
                    });

                    // create the combo
                    $('#input-port-options').combo({
                        options: options,
                        maxHeight: 300,
                        select: function (option) {
                            $('#connection-destination-id').val(option.value);
                        }
                    });

                    // populate the connection details
                    $('#connection-destination-component-id').val(remoteProcessGroup.id);

                    // populate the group details
                    $('#connection-destination-group div.setting-name').text('Within Remote Group')
                    $('#connection-remote-destination-url').text(remoteProcessGroup.targetUri).show();
                    $('#connection-destination-group-id').val(remoteProcessGroup.id);
                    $('#connection-destination-group-name').text(remoteProcessGroup.name);

                    deferred.resolve();
                } else {
                    // there are no relationships for this processor
                    nfDialog.showOkDialog({
                        headerText: 'Connection Configuration',
                        dialogContent: '\'' + nfCommon.escapeHtml(remoteProcessGroup.name) + '\' does not have any input ports.'
                    });

                    // reset the dialog
                    resetDialog();

                    deferred.reject();
                }
            }).fail(function (xhr, status, error) {
                // handle the error
                nfErrorHandler.handleAjaxError(xhr, status, error);

                deferred.reject();
            });
        }).promise();
    };

    /**
     * Initializes the source panel for groups.
     *
     * @argument {selection} source    The source of the connection
     */
    var initializeSourceReadOnlyGroup = function (source) {
        return $.Deferred(function (deferred) {
            var sourceData = source.datum();
            var sourceName = sourceData.permissions.canRead ? sourceData.component.name : sourceData.id;

            // populate the port information
            $('#read-only-output-port-source').show();

            // populate the component information
            $('#connection-source-component-id').val(sourceData.id);

            // populate the group details
            $('#connection-source-group-id').val(sourceData.id);
            $('#connection-source-group-name').text(sourceName);

            if (nfCanvasUtils.isRemoteProcessGroup(source)) {
                $('#connection-source-group div.setting-name').text('Within Remote Group');
                if (sourceData.permissions.canRead) {
                    $('#connection-remote-source-url').text(sourceData.component.targetUri).show();
                }
            }

            // resolve the deferred
            deferred.resolve();
        }).promise();
    };

    /**
     * Initializes the source in the existing connection dialog.
     *
     * @argument {selection} source        The source
     */
    var initializeSourceEditConnectionDialog = function (source) {
        if (nfCanvasUtils.isProcessor(source)) {
            return initializeSourceProcessor(source);
        } else if (nfCanvasUtils.isInputPort(source)) {
            return initializeSourceInputPort(source);
        } else if (nfCanvasUtils.isFunnel(source)) {
            return initializeSourceFunnel(source);
        } else {
            return initializeSourceReadOnlyGroup(source);
        }
    };

    /**
     * Initializes the destination in the existing connection dialog.
     *
     * @argument {selection} destination        The destination
     * @argument {object} connectionDestination The connection destination object
     */
    var initializeDestinationEditConnectionDialog = function (destination, connectionDestination) {
        if (nfCanvasUtils.isProcessor(destination)) {
            return initializeDestinationProcessor(destination);
        } else if (nfCanvasUtils.isOutputPort(destination)) {
            return initializeDestinationOutputPort(destination);
        } else if (nfCanvasUtils.isRemoteProcessGroup(destination)) {
            return initializeDestinationRemoteProcessGroup(destination, connectionDestination);
        } else if (nfCanvasUtils.isFunnel(destination)) {
            return initializeDestinationFunnel(destination);
        } else {
            return initializeDestinationProcessGroup(destination);
        }
    };

    /**
     * Creates an option for the specified relationship name.
     *
     * @argument {string} name      The relationship name
     */
    var createRelationshipOption = function (name) {
        var relationshipLabel = $('<div class="relationship-name nf-checkbox-label ellipsis"></div>').text(name);
        var relationshipValue = $('<span class="relationship-name-value hidden"></span>').text(name);
        return $('<div class="available-relationship-container"><div class="available-relationship nf-checkbox checkbox-unchecked"></div>' +
            '</div>').append(relationshipLabel).append(relationshipValue).appendTo('#relationship-names');
    };

    /**
     * To set or reset the connection addition/update in progress
     * @param {boolean} status the status of connection addition/update
     */
    var setConnectionUpsertionInProgess = function(status)
    {        
        var needToUpdateDOM = (connectionUpsertionInProgress !== status) ;
        connectionUpsertionInProgress = status;
        if(needToUpdateDOM){
            $('#connection-configuration').modal('refreshButtons');
        }
    }

    /**
     * returns whether the connection addition/update in progress
     */
    var isConnectionUpsertionInProgess = function()
    {        
        return connectionUpsertionInProgress;
    }

    /**
     * Adds a new connection.
     *
     * @argument {array} selectedRelationships      The selected relationships
     */
    var addConnection = function (selectedRelationships) {
        // to handle the case of slow network
        //the add/cancel buttons should be disabled
        setConnectionUpsertionInProgess(true);

        // get the connection details
        var sourceId = $('#connection-source-id').val();
        var destinationId = $('#connection-destination-id').val();

        // get the selection components
        var sourceComponentId = $('#connection-source-component-id').val();
        var source = d3.select('#id-' + sourceComponentId);
        var destinationComponentId = $('#connection-destination-component-id').val();
        var destination = d3.select('#id-' + destinationComponentId);

        // get the source/destination data
        var sourceData = source.datum();
        var destinationData = destination.datum();

        // add bend points if we're dealing with a self loop
        var bends = [];
        if (sourceComponentId === destinationComponentId) {
            var rightCenter = {
                x: sourceData.position.x + (sourceData.dimensions.width),
                y: sourceData.position.y + (sourceData.dimensions.height / 2)
            };

            var xOffset = nfConnection.config.selfLoopXOffset;
            var yOffset = nfConnection.config.selfLoopYOffset;
            bends.push({
                'x': (rightCenter.x + xOffset),
                'y': (rightCenter.y - yOffset)
            });
            bends.push({
                'x': (rightCenter.x + xOffset),
                'y': (rightCenter.y + yOffset)
            });
        } else {
            var existingConnections = [];

            // get all connections for the source component
            var connectionsForSourceComponent = nfConnection.getComponentConnections(sourceComponentId);
            $.each(connectionsForSourceComponent, function (_, connectionForSourceComponent) {
                // get the id for the source/destination component
                var connectionSourceComponentId = nfCanvasUtils.getConnectionSourceComponentId(connectionForSourceComponent);
                var connectionDestinationComponentId = nfCanvasUtils.getConnectionDestinationComponentId(connectionForSourceComponent);

                // if the connection is between these same components, consider it for collisions
                if ((connectionSourceComponentId === sourceComponentId && connectionDestinationComponentId === destinationComponentId) ||
                    (connectionDestinationComponentId === sourceComponentId && connectionSourceComponentId === destinationComponentId)) {

                    // record all connections between these two components in question
                    existingConnections.push(connectionForSourceComponent);
                }
            });

            // if there are existing connections between these components, ensure the new connection won't collide
            if (existingConnections.length > 0) {
                var avoidCollision = false;
                $.each(existingConnections, function (_, existingConnection) {
                    // only consider multiple connections with no bend points a collision, the existance of 
                    // bend points suggests that the user has placed the connection into a desired location
                    if (nfCommon.isEmpty(existingConnection.bends)) {
                        avoidCollision = true;
                        return false;
                    }
                });

                // if we need to avoid a collision
                if (avoidCollision === true) {
                    // determine the middle of the source/destination components
                    var sourceMiddle = [sourceData.position.x + (sourceData.dimensions.width / 2), sourceData.position.y + (sourceData.dimensions.height / 2)];
                    var destinationMiddle = [destinationData.position.x + (destinationData.dimensions.width / 2), destinationData.position.y + (destinationData.dimensions.height / 2)];

                    // detect if the line is more horizontal or vertical
                    var slope = ((sourceMiddle[1] - destinationMiddle[1]) / (sourceMiddle[0] - destinationMiddle[0]));
                    var isMoreHorizontal = slope <= 1 && slope >= -1;

                    // determines if the specified coordinate collides with another connection
                    var collides = function (x, y) {
                        var collides = false;
                        $.each(existingConnections, function (_, existingConnection) {
                            if (!nfCommon.isEmpty(existingConnection.bends)) {
                                if (isMoreHorizontal) {
                                    // horizontal lines are adjusted in the y space
                                    if (existingConnection.bends[0].y === y) {
                                        collides = true;
                                        return false;
                                    }
                                } else {
                                    // vertical lines are adjusted in the x space
                                    if (existingConnection.bends[0].x === x) {
                                        collides = true;
                                        return false;
                                    }
                                }
                            }
                        });
                        return collides;
                    };

                    // find the mid point on the connection
                    var xCandidate = (sourceMiddle[0] + destinationMiddle[0]) / 2;
                    var yCandidate = (sourceMiddle[1] + destinationMiddle[1]) / 2;

                    // attempt to position this connection so it doesn't collide
                    var xStep = isMoreHorizontal ? 0 : CONNECTION_OFFSET_X_INCREMENT;
                    var yStep = isMoreHorizontal ? CONNECTION_OFFSET_Y_INCREMENT : 0;
                    var positioned = false;
                    while (positioned === false) {
                        // consider above and below, then increment and try again (if necessary)
                        if (collides(xCandidate - xStep, yCandidate - yStep) === false) {
                            bends.push({
                                'x': (xCandidate - xStep),
                                'y': (yCandidate - yStep)
                            });
                            positioned = true;
                        } else if (collides(xCandidate + xStep, yCandidate + yStep) === false) {
                            bends.push({
                                'x': (xCandidate + xStep),
                                'y': (yCandidate + yStep)
                            });
                            positioned = true;
                        }

                        if (isMoreHorizontal) {
                            yStep += CONNECTION_OFFSET_Y_INCREMENT;
                        } else {
                            xStep += CONNECTION_OFFSET_X_INCREMENT;
                        }
                    }
                }
            }
        }

        // determine the source group id
        var sourceGroupId = $('#connection-source-group-id').val();
        var destinationGroupId = $('#connection-destination-group-id').val();

        // determine the source and destination types
        var sourceType = nfCanvasUtils.getConnectableTypeForSource(source);
        var destinationType = nfCanvasUtils.getConnectableTypeForDestination(destination);

        // get the settings
        var connectionName = $('#connection-name').val();
        var flowFileExpiration = $('#flow-file-expiration').val();
        var backPressureObjectThreshold = $('#back-pressure-object-threshold').val();
        var backPressureDataSizeThreshold = $('#back-pressure-data-size-threshold').val();
        var prioritizers = $('#prioritizer-selected').sortable('toArray');
        var loadBalanceStrategy = $('#load-balance-strategy-combo').combo('getSelectedOption').value;
        var shouldLoadBalance = 'DO_NOT_LOAD_BALANCE' !== loadBalanceStrategy;
        var loadBalancePartitionAttribute = shouldLoadBalance && 'PARTITION_BY_ATTRIBUTE' === loadBalanceStrategy ? $('#load-balance-partition-attribute').val() : '';
        var loadBalanceCompression = shouldLoadBalance ? $('#load-balance-compression-combo').combo('getSelectedOption').value : 'DO_NOT_COMPRESS';

        if (validateSettings()) {
            var connectionEntity = {
                'revision': nfClient.getRevision({
                    'revision': {
                        'version': 0
                    }
                }),
                'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
                'component': {
                    'name': connectionName,
                    'source': {
                        'id': sourceId,
                        'groupId': sourceGroupId,
                        'type': sourceType
                    },
                    'destination': {
                        'id': destinationId,
                        'groupId': destinationGroupId,
                        'type': destinationType
                    },
                    'selectedRelationships': selectedRelationships,
                    'flowFileExpiration': flowFileExpiration,
                    'backPressureDataSizeThreshold': backPressureDataSizeThreshold,
                    'backPressureObjectThreshold': backPressureObjectThreshold,
                    'bends': bends,
                    'prioritizers': prioritizers,
                    'loadBalanceStrategy': loadBalanceStrategy,
                    'loadBalancePartitionAttribute': loadBalancePartitionAttribute,
                    'loadBalanceCompression': loadBalanceCompression
                }
            };

            // create the new connection
            $.ajax({
                type: 'POST',
                url: config.urls.api + '/process-groups/' + encodeURIComponent(nfCanvasUtils.getGroupId()) + '/connections',
                data: JSON.stringify(connectionEntity),
                dataType: 'json',
                contentType: 'application/json'
            }).done(function (response) {
                // add the connection
                nfGraph.add({
                    'connections': [response]
                }, {
                    'selectAll': true
                });

                setConnectionUpsertionInProgess(false);

                // close the dialog
                $('#connection-configuration').modal('hide');

                // reload the connections source/destination components
                nfCanvasUtils.reloadConnectionSourceAndDestination(sourceComponentId, destinationComponentId);

                // update component visibility
                nfGraph.updateVisibility();

                // update the birdseye
                nfBirdseye.refresh();
            }).fail(function(xhr, status, error){

                // update the button status 
                setConnectionUpsertionInProgess(false);
                nfErrorHandler.handleConfigurationUpdateAjaxError(xhr, status, error);
            });
        }
    };

    /**
     * Updates an existing connection.
     *
     * @argument {array} selectedRelationships          The selected relationships
     */
    var updateConnection = function (selectedRelationships) {
        //apply, cancel buttons should be disabled, while the connection update is in progress
        setConnectionUpsertionInProgess(true);

        // get the connection details
        var connectionId = $('#connection-id').text();
        var connectionUri = $('#connection-uri').val();

        // get the source details
        var sourceComponentId = $('#connection-source-component-id').val();

        // get the destination details
        var destinationComponentId = $('#connection-destination-component-id').val();
        var destination = d3.select('#id-' + destinationComponentId);
        var destinationType = nfCanvasUtils.getConnectableTypeForDestination(destination);

        // get the destination details
        var destinationId = $('#connection-destination-id').val();
        var destinationGroupId = $('#connection-destination-group-id').val();

        // get the settings
        var connectionName = $('#connection-name').val();
        var flowFileExpiration = $('#flow-file-expiration').val();
        var backPressureObjectThreshold = $('#back-pressure-object-threshold').val();
        var backPressureDataSizeThreshold = $('#back-pressure-data-size-threshold').val();
        var prioritizers = $('#prioritizer-selected').sortable('toArray');
        var loadBalanceStrategy = $('#load-balance-strategy-combo').combo('getSelectedOption').value;
        var shouldLoadBalance = 'DO_NOT_LOAD_BALANCE' !== loadBalanceStrategy;
        var loadBalancePartitionAttribute = shouldLoadBalance && 'PARTITION_BY_ATTRIBUTE' === loadBalanceStrategy ? $('#load-balance-partition-attribute').val() : '';
        var loadBalanceCompression = shouldLoadBalance ? $('#load-balance-compression-combo').combo('getSelectedOption').value : 'DO_NOT_COMPRESS';

        if (validateSettings()) {
            var d = nfConnection.get(connectionId);
            var connectionEntity = {
                'revision': nfClient.getRevision(d),
                'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
                'component': {
                    'id': connectionId,
                    'name': connectionName,
                    'destination': {
                        'id': destinationId,
                        'groupId': destinationGroupId,
                        'type': destinationType
                    },
                    'selectedRelationships': selectedRelationships,
                    'flowFileExpiration': flowFileExpiration,
                    'backPressureDataSizeThreshold': backPressureDataSizeThreshold,
                    'backPressureObjectThreshold': backPressureObjectThreshold,
                    'prioritizers': prioritizers,
                    'loadBalanceStrategy': loadBalanceStrategy,
                    'loadBalancePartitionAttribute': loadBalancePartitionAttribute,
                    'loadBalanceCompression': loadBalanceCompression
                }
            };

            // update the connection
            return $.ajax({
                type: 'PUT',
                url: connectionUri,
                data: JSON.stringify(connectionEntity),
                dataType: 'json',
                contentType: 'application/json'
            }).done(function (response) {
                //update updation progress status
                setConnectionUpsertionInProgess(false);

                // close the dialog
                $('#connection-configuration').modal('hide');

                // update this connection
                nfConnection.set(response);

                // reload the connections source/destination components
                nfCanvasUtils.reloadConnectionSourceAndDestination(sourceComponentId, destinationComponentId);
            }).fail(function(xhr, status, error){
                setConnectionUpsertionInProgess(false);
                nfErrorHandler.handleConfigurationUpdateAjaxError(xhr, status, error);
            });
        } else {
            return $.Deferred(function (deferred) {
                deferred.reject();
            }).promise();
        }
    };

    /**
     * Returns an array of selected relationship names.
     */
    var getSelectedRelationships = function () {
        // get all available relationships
        var availableRelationships = $('#relationship-names');
        var selectedRelationships = [];

        // go through each relationship to determine which are selected
        $.each(availableRelationships.children(), function (i, relationshipElement) {
            var relationship = $(relationshipElement);

            // get each relationship and its corresponding checkbox
            var relationshipCheck = relationship.children('div.available-relationship');

            // see if this relationship has been selected
            if (relationshipCheck.hasClass('checkbox-checked')) {
                selectedRelationships.push(relationship.children('span.relationship-name-value').text());
            }
        });

        return selectedRelationships;
    };

    /**
     * Validates the specified settings.
     */
    var validateSettings = function () {
        var errors = [];

        // validate the settings
        if (nfCommon.isBlank($('#flow-file-expiration').val())) {
            errors.push('File expiration must be specified');
        }
        if (!$.isNumeric($('#back-pressure-object-threshold').val())) {
            errors.push('Back pressure object threshold must be an integer value');
        }
        if (nfCommon.isBlank($('#back-pressure-data-size-threshold').val())) {
            errors.push('Back pressure data size threshold must be specified');
        }
        if ($('#load-balance-strategy-combo').combo('getSelectedOption').value === 'PARTITION_BY_ATTRIBUTE'
            && nfCommon.isBlank($('#load-balance-partition-attribute').val())) {
            errors.push('Cannot set Load Balance Strategy to "Partition by attribute" without providing a partitioning "Attribute Name"');
        }

        if (errors.length > 0) {
            nfDialog.showOkDialog({
                headerText: 'Configuration Error',
                dialogContent: nfCommon.formatUnorderedList(errors)
            });
            return false;
        } else {
            return true;
        }
    };

    /**
     * Resets the dialog.
     */
    var resetDialog = function () {
        // reset the prioritizers
        var selectedList = $('#prioritizer-selected');
        var availableList = $('#prioritizer-available');
        selectedList.children()
            .detach()
            .appendTo(availableList)
            .find('div.draggable-control')
            .remove();

        // sort the available list
        var listItems = availableList.children('li').get();
        listItems.sort(function (a, b) {
            var compA = $(a).text().toUpperCase();
            var compB = $(b).text().toUpperCase();
            return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
        });

        // clear the available list and re-insert each list item
        $.each(listItems, function () {
            $(this).detach();
        });
        $.each(listItems, function () {
            $(this).appendTo(availableList);
        });

        // reset the fields
        $('#connection-name').val('');
        $('#relationship-names').css('border-width', '0').empty();
        $('#relationship-names-container').hide();

        // clear the id field
        nfCommon.clearField('connection-id');

        // hide all the connection source panels
        $('#processor-source').hide();
        $('#input-port-source').hide();
        $('#output-port-source').hide();
        $('#read-only-output-port-source').hide();
        $('#funnel-source').hide();

        // hide all the connection destination panels
        $('#processor-destination').hide();
        $('#input-port-destination').hide();
        $('#output-port-destination').hide();
        $('#funnel-destination').hide();

        // clear and destination details
        $('#connection-source-id').val('');
        $('#connection-source-component-id').val('');
        $('#connection-source-group-id').val('');

        // clear any destination details
        $('#connection-destination-id').val('');
        $('#connection-destination-component-id').val('');
        $('#connection-destination-group-id').val('');

        // clear any ports
        $('#output-port-options').empty();
        $('#input-port-options').empty();

        // clear load balance settings
        $('#load-balance-strategy-combo').combo('setSelectedOption', nfCommon.loadBalanceStrategyOptions[0]);
        $('#load-balance-partition-attribute').val('');
        $('#load-balance-compression-combo').combo('setSelectedOption', nfCommon.loadBalanceCompressionOptions[0]);

        // see if the temp edge needs to be removed
        removeTempEdge();
    };

    var nfConnectionConfiguration = {

        /**
         * Initialize the connection configuration.
         *
         * @param nfBirdseyeRef   The nfBirdseye module.
         * @param nfGraphRef   The nfGraph module.
         */
        init: function (nfBirdseyeRef, nfGraphRef) {
            nfBirdseye = nfBirdseyeRef;
            nfGraph = nfGraphRef;

            // initially hide the relationship names container
            $('#relationship-names-container').hide();

            // initialize the configure connection dialog
            $('#connection-configuration').modal({
                scrollableContentStyle: 'scrollable',
                headerText: 'Configure Connection',
                handler: {
                    close: function () {
                        // reset the dialog on close
                        resetDialog();
                    },
                    open: function () {
                        nfCommon.toggleScrollable($('#' + this.find('.tab-container').attr('id') + '-content').get(0));
                    }
                }
            });

            // initialize the properties tabs
            $('#connection-configuration-tabs').tabbs({
                tabStyle: 'tab',
                selectedTabStyle: 'selected-tab',
                scrollableTabContentStyle: 'scrollable',
                tabs: [{
                    name: 'Details',
                    tabContentId: 'connection-details-tab-content'
                }, {
                    name: 'Settings',
                    tabContentId: 'connection-settings-tab-content'
                }]
            });

            // initialize the load balance strategy combo
            $('#load-balance-strategy-combo').combo({
                options: nfCommon.loadBalanceStrategyOptions,
                select: function (selectedOption) {
                    // Show the appropriate configurations
                    if (selectedOption.value === 'PARTITION_BY_ATTRIBUTE') {
                        $('#load-balance-partition-attribute-setting-separator').show();
                        $('#load-balance-partition-attribute-setting').show();
                    } else {
                        $('#load-balance-partition-attribute-setting-separator').hide();
                        $('#load-balance-partition-attribute-setting').hide();
                    }
                    if (selectedOption.value === 'DO_NOT_LOAD_BALANCE') {
                        $('#load-balance-compression-setting').hide();
                    } else {
                        $('#load-balance-compression-setting').show();
                    }
                }
            });


            // initialize the load balance compression combo
            $('#load-balance-compression-combo').combo({
                options: nfCommon.loadBalanceCompressionOptions
            });

            // load the processor prioritizers
            $.ajax({
                type: 'GET',
                url: config.urls.prioritizers,
                dataType: 'json'
            }).done(function (response) {
                // create an element for each available prioritizer
                $.each(response.prioritizerTypes, function (i, documentedType) {
                    nfConnectionConfiguration.addAvailablePrioritizer('#prioritizer-available', documentedType);
                });

                // work around for https://bugs.jqueryui.com/ticket/6054
                var shouldAllowDrop = true;

                // make the prioritizer containers sortable
                $('#prioritizer-available').sortable({
                    containment: $('#connection-settings-tab-content').find('.settings-right'),
                    connectWith: 'ul',
                    placeholder: 'ui-state-highlight',
                    scroll: true,
                    opacity: 0.6,
                    beforeStop: function (event, ui) {
                        if ($('#prioritizer-available').find('.ui-sortable-placeholder').length) {
                            shouldAllowDrop = false;
                        }
                    },
                    stop: function (event, ui) {
                        const allowDrop = shouldAllowDrop;
                        shouldAllowDrop = true;
                        return allowDrop;
                    }
                });

                $('#prioritizer-selected').sortable({
                    containment: $('#connection-settings-tab-content').find('.settings-right'),
                    placeholder: 'selected',
                    scroll: true,
                    opacity: 0.6,
                    receive: function (event, ui) {
                        nfConnectionConfiguration.addControlsForSelectedPrioritizer(ui.item);
                    },
                    update: function (event, ui) {
                        // update the buttons to possibly trigger the disabled state
                        $('#connection-configuration').modal('refreshButtons');
                    }
                });

                $('#prioritizer-available, #prioritizer-selected').disableSelection();

                // add a listener that will handle dblclick for all available prioritizers
                $(document).on('dblclick', '#prioritizer-available li', function() {
                    var availablePrioritizerElement = $(this).detach().appendTo($('#prioritizer-selected'));

                    nfConnectionConfiguration.addControlsForSelectedPrioritizer(availablePrioritizerElement);

                    // update the buttons to possibly trigger the disabled state
                    $('#connection-configuration').modal('refreshButtons');
                });
            }).fail(nfErrorHandler.handleAjaxError);

            nfConnectionConfiguration.sortAvailablePrioritizers();
        },

        /**
         * Adds the specified prioritizer to the specified container.
         *
         * @argument {string} prioritizerContainer      The dom Id of the prioritizer container
         * @argument {object} prioritizerType           The type of prioritizer
         */
        addAvailablePrioritizer: function (prioritizerContainer, prioritizerType) {
            var type = prioritizerType.type;
            var name = nfCommon.substringAfterLast(type, '.');

            // add the prioritizers to the available list
            var prioritizerList = $(prioritizerContainer);
            var prioritizer = $('<li></li>').append($('<span style="float: left;"></span>').text(name)).attr('id', type).addClass('ui-state-default').appendTo(prioritizerList);

            // add the description if applicable
            if (nfCommon.isDefinedAndNotNull(prioritizerType.description)) {
                $('<div class="fa fa-question-circle"></div>').appendTo(prioritizer).qtip($.extend({
                    content: nfCommon.escapeHtml(prioritizerType.description)
                }, nfCommon.config.tooltipConfig));
            }
        },

        /**
         * Sorts the available prioritizers.
         */
        sortAvailablePrioritizers: function () {
            var availablePrioritizersList = $('#prioritizer-available');
            availablePrioritizersList.children('li')
                .detach()
                .sort(function (aElement, bElement) {
                    var nameA = $(aElement).text();
                    var nameB = $(bElement).text();
                    return nameA.localeCompare(nameB);
                })
                .appendTo(availablePrioritizersList);
        },

        /**
         * Adds the controls to the specified selected draggable element.
         *
         * @argument {jQuery} draggableElement
         */
        addControlsForSelectedPrioritizer: function (draggableElement) {
            var removeIcon = $('<div class="draggable-control"><div class="fa fa-remove"></div></div>')
                .on('click', function () {
                    // remove the remove icon
                    removeIcon.remove();

                    // restore to the available parameter contexts
                    $('#prioritizer-available').append(draggableElement);

                    // resort the available parameter contexts
                    nfConnectionConfiguration.sortAvailablePrioritizers();

                    // update the buttons to possibly trigger the disabled state
                    $('#connection-configuration').modal('refreshButtons');
                })
                .appendTo(draggableElement);
        },

        /**
         * Shows the dialog for creating a new connection.
         *
         * @argument {string} sourceId      The source id
         * @argument {string} destinationId The destination id
         */
        createConnection: function (sourceId, destinationId, defaultSettings) {
            // select the source and destination
            var source = d3.select('#id-' + sourceId);
            var destination = d3.select('#id-' + destinationId);

            if (source.empty() || destination.empty()) {
                return;
            }

            // reset labels
            $('#connection-source-group div.setting-name').text('Within Group')
            $('#connection-destination-group div.setting-name').text('Within Group')
            $('#connection-remote-source-url').hide();
            $('#connection-remote-destination-url').hide();

            // initialize the connection dialog
            $.when(initializeSourceNewConnectionDialog(source), initializeDestinationNewConnectionDialog(destination)).done(function () {
                // set the default values
                $('#flow-file-expiration').val(defaultSettings.flowfileExpiration);
                $('#back-pressure-object-threshold').val(defaultSettings.objectThreshold);
                $('#back-pressure-data-size-threshold').val(defaultSettings.dataSizeThreshold);

                // select the first tab
                $('#connection-configuration-tabs').find('li:first').click();

                // configure the header and show the dialog
                $('#connection-configuration').modal('setHeaderText', 'Create Connection').modal('show');

                // add the ellipsis if necessary
                $('#connection-configuration div.relationship-name').ellipsis();

                // fill in the connection id
                nfCommon.populateField('connection-id', null);

                // show the border if necessary
                var relationshipNames = $('#relationship-names');
                if (relationshipNames.is(':visible') && relationshipNames.get(0).scrollHeight > Math.round(relationshipNames.innerHeight())) {
                    relationshipNames.css('border-width', '1px');
                }
            }).fail(function () {
                // see if the temp edge needs to be removed
                removeTempEdge();
            });
        },

        /**
         * Shows the configuration for the specified connection. If a destination is
         * specified it will be considered a new destination.
         *
         * @argument {selection} selection         The connection entry
         * @argument {selection} destination          Optional new destination
         */
        showConfiguration: function (selection, destination) {
            return $.Deferred(function (deferred) {
                var connectionEntry = selection.datum();
                var connection = connectionEntry.component;

                // identify the source component
                var sourceComponentId = nfCanvasUtils.getConnectionSourceComponentId(connectionEntry);
                var source = d3.select('#id-' + sourceComponentId);

                // identify the destination component
                if (nfCommon.isUndefinedOrNull(destination)) {
                    var destinationComponentId = nfCanvasUtils.getConnectionDestinationComponentId(connectionEntry);
                    destination = d3.select('#id-' + destinationComponentId);
                }

                // reset labels
                $('#connection-source-group div.setting-name').text('Within Group')
                $('#connection-destination-group div.setting-name').text('Within Group')
                $('#connection-remote-source-url').hide();
                $('#connection-remote-destination-url').hide();

                // initialize the connection dialog
                $.when(initializeSourceEditConnectionDialog(source), initializeDestinationEditConnectionDialog(destination, connection.destination)).done(function () {
                    var availableRelationships = connection.availableRelationships;
                    var selectedRelationships = connection.selectedRelationships;

                    // show the available relationship if applicable
                    if (nfCommon.isDefinedAndNotNull(availableRelationships) || nfCommon.isDefinedAndNotNull(selectedRelationships)) {
                        // populate the available connections
                        $.each(availableRelationships, function (i, name) {
                            createRelationshipOption(name);
                        });

                        addDialogRelationshipsChangeListener();

                        // ensure all selected relationships are present
                        // (may be undefined) and selected
                        $.each(selectedRelationships, function (i, name) {
                            // mark undefined relationships accordingly
                            if ($.inArray(name, availableRelationships) === -1) {
                                var option = createRelationshipOption(name);
                                $(option).children('div.relationship-name').addClass('undefined');
                            }

                            // ensure all selected relationships are checked
                            var relationships = $('#relationship-names').children('div');
                            $.each(relationships, function (i, relationship) {
                                var relationshipName = $(relationship).children('span.relationship-name-value');
                                if (relationshipName.text() === name) {
                                    $(relationship).children('div.available-relationship').removeClass('checkbox-unchecked').addClass('checkbox-checked');
                                }
                            });
                        });
                    }

                    // if the source is a process group or remote process group, select the appropriate port if applicable
                    if (nfCanvasUtils.isProcessGroup(source) || nfCanvasUtils.isRemoteProcessGroup(source)) {
                        // populate the connection source details
                        $('#connection-source-id').val(connection.source.id);
                        $('#read-only-output-port-name').text(connection.source.name).attr('title', connection.source.name);
                    }

                    // if the destination is a process gorup or remote process group, select the appropriate port if applicable
                    if (nfCanvasUtils.isProcessGroup(destination) || nfCanvasUtils.isRemoteProcessGroup(destination)) {
                        var destinationData = destination.datum();

                        // when the group ids differ, its a new destination component so we don't want to preselect any port
                        if (connection.destination.groupId === destinationData.id) {
                            $('#input-port-options').combo('setSelectedOption', {
                                value: connection.destination.id
                            });
                        }
                    }

                    // set the connection settings
                    $('#connection-name').val(connection.name);
                    $('#flow-file-expiration').val(connection.flowFileExpiration);
                    $('#back-pressure-object-threshold').val(connection.backPressureObjectThreshold);
                    $('#back-pressure-data-size-threshold').val(connection.backPressureDataSizeThreshold);

                    // select the load balance combos
                    $('#load-balance-strategy-combo').combo('setSelectedOption', {
                        value: connection.loadBalanceStrategy
                    });
                    $('#load-balance-compression-combo').combo('setSelectedOption', {
                        value: connection.loadBalanceCompression
                    });
                    $('#load-balance-partition-attribute').val(connection.loadBalancePartitionAttribute);

                    // format the connection id
                    nfCommon.populateField('connection-id', connection.id);

                    // handle each prioritizer
                    $.each(connection.prioritizers, function (i, type) {
                        var selectedPrioritizer = $('#prioritizer-available').children('li[id="' + type + '"]');
                        nfConnectionConfiguration.addControlsForSelectedPrioritizer(selectedPrioritizer);
                        selectedPrioritizer.detach().appendTo('#prioritizer-selected');
                    });

                    // store the connection details
                    $('#connection-uri').val(connectionEntry.uri);

                    // configure the button model
                    $('#connection-configuration').modal('setButtonModel', [{
                        buttonText: 'Apply',
                        color: {
                            base: '#728E9B',
                            hover: '#004849',
                            text: '#ffffff'
                        },
                        disabled: function () {
                            // ensure some relationships were selected with a processor as the source
                            if (nfCanvasUtils.isProcessor(source)) {
                                return getSelectedRelationships().length === 0 || isConnectionUpsertionInProgess();
                            }
                            return isConnectionUpsertionInProgess();
                        },
                        handler: {
                            click: function () {
                                setConnectionUpsertionInProgess(true);
                                // see if we're working with a processor as the source
                                if (nfCanvasUtils.isProcessor(source)) {
                                    // update the selected relationships
                                    updateConnection(getSelectedRelationships()).done(function () {
                                        deferred.resolve();
                                    }).fail(function () {
                                        deferred.reject();
                                    }).always(function(){
                                        setConnectionUpsertionInProgess(false);
                                    });
                                } else {
                                    // there are no relationships, but the source wasn't a processor, so update anyway
                                    updateConnection(undefined).done(function () {
                                        deferred.resolve();
                                    }).fail(function () {
                                        deferred.reject();
                                    }).always(function(){
                                        setConnectionUpsertionInProgess(false);
                                    });
                                }
                            }
                        }
                    },
                        {
                            buttonText: 'Cancel',
                            color: {
                                base: '#E3E8EB',
                                hover: '#C7D2D7',
                                text: '#004849'
                            },
                            disabled: function(){
                                return isConnectionUpsertionInProgess();
                            },
                            handler: {
                                click: function () {
                                    // hide the dialog
                                    $('#connection-configuration').modal('hide');

                                    // reject the deferred
                                    deferred.reject();
                                }
                            }
                        }]);

                    // show the details dialog
                    $('#connection-configuration').modal('setHeaderText', 'Configure Connection').modal('show');

                    // add the ellipsis if necessary
                    $('#connection-configuration div.relationship-name').ellipsis();

                    // show the border if necessary
                    var relationshipNames = $('#relationship-names');
                    if (relationshipNames.is(':visible') && relationshipNames.get(0).scrollHeight > Math.round(relationshipNames.innerHeight())) {
                        relationshipNames.css('border-width', '1px');
                    }
                }).fail(function () {
                    deferred.reject();
                });
            }).promise();
        }
    };

    return nfConnectionConfiguration;
}));
