/**
 * Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * WSO2 Inc. 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.
 */
define(['require', 'log', 'jquery', 'backbone', 'lodash', 'designViewUtils', 'dropElements', 'dagre', 'edge',
        'windowFilterProjectionQueryInput', 'joinQueryInput', 'patternOrSequenceQueryInput', 'queryOutput',
        'partitionWith', 'jsonValidator', 'constants', 'dragSelect'],

    function (require, log, $, Backbone, _, DesignViewUtils, DropElements, dagre, Edge,
              WindowFilterProjectionQueryInput, JoinQueryInput, PatternOrSequenceQueryInput, QueryOutput,
              PartitionWith, JSONValidator, Constants) {

        const TAB_INDEX = 10;
        const ENTER_KEY = 13;
        const LEFT_ARROW_KEY = 37;
        const RIGHT_ARROW_KEY = 39;
        const ESCAPE_KEY = 27;
        const TAB_KEY = 9;
        var constants = {
            SOURCE: 'sourceDrop',
            SINK: 'sinkDrop',
            STREAM: 'streamDrop',
            TABLE: 'tableDrop',
            WINDOW: 'windowDrop',
            TRIGGER: 'triggerDrop',
            AGGREGATION: 'aggregationDrop',
            FUNCTION: 'functionDrop',
            PROJECTION: 'projectionQueryDrop',
            FILTER: 'filterQueryDrop',
            JOIN: 'joinQueryDrop',
            WINDOW_QUERY: 'windowQueryDrop',
            FUNCTION_QUERY: 'functionQueryDrop',
            PATTERN: 'patternQueryDrop',
            SEQUENCE: 'sequenceQueryDrop',
            PARTITION: 'partitionDrop',
            PARTITION_CONNECTION_POINT: 'partition-connector-in-part',
            SELECTOR: 'selector',
            MULTI_SELECTOR: 'multi-selector'
        };

        /**
         * @class DesignGrid
         * @constructor
         * @class DesignGrid  Wraps the Ace editor for design view
         * @param {Object} options Rendering options for the view
         */
        var DesignGrid = function (options) {
            var errorMessage = 'unable to find design view grid container';
            if (!_.has(options, 'container')) {
                log.error(errorMessage);
                throw errorMessage;
            }
            var container = $(_.get(options, 'container'));
            if (!container.length > 0) {
                log.error(errorMessage);
                throw errorMessage;
            }
            this.options = options;
            this.rawExtensions = options.rawExtensions;
            this.configurationData = this.options.configurationData;
            this.container = this.options.container;
            this.application = this.options.application;
            this.jsPlumbInstance = options.jsPlumbInstance;
            this.currentTabId = this.application.tabController.activeTab.cid;
            this.designViewContainer = $('#design-container-' + this.currentTabId);
            this.toggleViewButton = $('#toggle-view-button-' + this.currentTabId);
            this.designGridContainer = $('#design-grid-container-' + this.currentTabId);
            this.selectedElements = [];
            this.selectedObjects = [];
        };


        DesignGrid.prototype.addSelectedElements = function(element){
            this.selectedElements.push(element);
        };

        DesignGrid.prototype.isSelectedElements = function(element){
            if (this.selectedElements.includes(element)){
                return true;
            } else{
                return false;
            }
        };

        DesignGrid.prototype.getSelectedElement = function(){
            return this.selectedElements;
        };

        DesignGrid.prototype.resetSelectedElement = function(){
           this.selectedElements = [];
        };

        DesignGrid.prototype.removeFromSelectedElements = function(element){
            for (var i = 0; i < this.selectedElements.length; i++) {
                if (this.selectedElements[i] == element) {
                    this.selectedElements.splice(i, 1);
                }
            }
        };

        DesignGrid.prototype.render = function () {
            var self = this;

            // newAgentId --> newAgent ID (Dropped Element ID)
            this.newAgentId = "0";
            var dropElementsOpts = {};
            _.set(dropElementsOpts, 'container', self.container);
            _.set(dropElementsOpts, 'configurationData', self.configurationData);
            _.set(dropElementsOpts, 'application', self.application);
            _.set(dropElementsOpts, 'jsPlumbInstance', self.jsPlumbInstance);
            _.set(dropElementsOpts, 'designGrid', self);
            this.dropElements = new DropElements(dropElementsOpts);
            this.canvas = $(self.container);

            // configuring the siddhi app level annotations
            var settingsButtonId = self.currentTabId + '-appSettingsId';
            var settingsButton = $("<div id='" + settingsButtonId + "' " +
                "class='btn app-annotations-button' " +
                "data-placement='bottom' data-toggle='tooltip' title='App Annotations'>" +
                "<i class='fw fw-settings'></i></div>");
            settingsButton.tooltip();
            self.canvas.append(settingsButton);
            var settingsIconElement = $('#' + settingsButtonId)[0];
            settingsIconElement.addEventListener('click', function () {
                self.dropElements.formBuilder.DefineFormForAppAnnotations(this);
            });

            // add text fields to display the siddhi app name and description
            var siddhiAppNameNodeId = self.currentTabId + '-siddhiAppNameId';
            var siddhiAppName = self.configurationData.getSiddhiAppConfig().getSiddhiAppName();
            var siddhiAppNameNode = $("<div id='" + siddhiAppNameNodeId + "' " +
                "class='siddhi-app-name-node'>" + siddhiAppName + "</div>");
            var siddhiAppDescription = self.configurationData.getSiddhiAppConfig().getSiddhiAppDescription();
            var siddhiAppDescriptionNode = $("<div id='siddhi-app-desc-node'>" + siddhiAppDescription + "</div>");
            self.canvas.append(siddhiAppNameNode, siddhiAppDescriptionNode);

            /**
             * @description jsPlumb function opened
             */
            self.jsPlumbInstance.ready(function () {

                self.jsPlumbInstance.importDefaults({
                    HoverPaintStyle: {
                        strokeStyle: '#424242',
                        strokeWidth: 2
                    },
                    Overlays: [["Arrow", {location: 1.0, id: "arrow", foldback: 1, width: 8, length: 8}]],
                    DragOptions: {cursor: "crosshair"},
                    Endpoints: [["Dot", {radius: 7}], ["Dot", {radius: 11}]],
                    EndpointStyle: {
                        radius: 3
                    },
                    ConnectionsDetachable: false,
                    Connector: ["Bezier", {curviness: 25}]
                });

                /**
                 * @function droppable method for the 'stream' & the 'query' objects
                 */
                self.canvas.droppable
                ({
                    accept: '.stream-drag, .table-drag, .window-drag, .trigger-drag, .aggregation-drag,' +
                    '.projection-query-drag, .filter-query-drag, .join-query-drag, .window-query-drag,' +
                    '.pattern-query-drag, .sequence-query-drag, .partition-drag, .source-drag, .sink-drag, ' +
                    '.function-drag, .function-query-drag',
                    containment: 'grid-container',

                    /**
                     *
                     * @param e --> original event object fired/ normalized by jQuery
                     * @param ui --> object that contains additional info added by jQuery depending on which
                     * interaction was used
                     * @helper clone
                     */

                    drop: function (e, ui) {
                        var mouseTop = e.pageY - self.canvas.offset().top + self.canvas.scrollTop() - 40;
                        var mouseLeft = e.pageX - self.canvas.offset().left + self.canvas.scrollLeft() - 60;
                        // Clone the element in the toolbox in order to drop the clone on the canvas
                        var droppedElement = ui.helper.clone();
                        // To further manipulate the jsplumb element, remove the jquery UI clone helper as jsPlumb
                        // doesn't support it
                        ui.helper.remove();
                        $(droppedElement).draggable({containment: "parent"});
                        // Repaint to reposition all the elements that are on the canvas after the drop/addition of a
                        // new element on the canvas
                        self.jsPlumbInstance.repaint(ui.helper);

                        // If the dropped Element is a Source annotation then->
                        if ($(droppedElement).hasClass('source-drag')) {
                            self.handleSourceAnnotation(mouseTop, mouseLeft, false, "Source");
                        }

                        // If the dropped Element is a Sink annotation then->
                        if ($(droppedElement).hasClass('sink-drag')) {
                            self.handleSinkAnnotation(mouseTop, mouseLeft, false, "Sink");
                        }

                        // If the dropped Element is a Stream then->
                        if ($(droppedElement).hasClass('stream-drag')) {
                            self.handleStream(mouseTop, mouseLeft, false);
                        }

                        // If the dropped Element is a Table then->
                        if ($(droppedElement).hasClass('table-drag')) {
                            self.handleTable(mouseTop, mouseLeft, false);
                        }

                        // If the dropped Element is a Window(not window query) then->
                        else if ($(droppedElement).hasClass('window-drag')) {
                            self.handleWindow(mouseTop, mouseLeft, false);
                        }

                        // If the dropped Element is a Trigger then->
                        else if ($(droppedElement).hasClass('trigger-drag')) {
                            self.handleTrigger(mouseTop, mouseLeft, false);
                        }

                        // If the dropped Element is a Aggregation then->
                        else if ($(droppedElement).hasClass('aggregation-drag')) {
                            self.handleAggregation(mouseTop, mouseLeft, false);
                        }

                        // If the dropped Element is a Aggregation then->
                        else if ($(droppedElement).hasClass('function-drag')) {
                            self.handleFunction(mouseTop, mouseLeft, false);
                        }

                        // If the dropped Element is a Projection Query then->
                        else if ($(droppedElement).hasClass('projection-query-drag')) {
                            self.handleWindowFilterProjectionQuery(constants.PROJECTION, mouseTop, mouseLeft, false,
                                "Query");
                        }

                        // If the dropped Element is a Filter query then->
                        else if ($(droppedElement).hasClass('filter-query-drag')) {
                            self.handleWindowFilterProjectionQuery(constants.FILTER, mouseTop, mouseLeft, false,
                                "Query");
                        }

                        // If the dropped Element is a Window Query then->
                        else if ($(droppedElement).hasClass('window-query-drag')) {
                            self.handleWindowFilterProjectionQuery(constants.WINDOW_QUERY, mouseTop, mouseLeft, false,
                                "Query");
                        }

                        // If the dropped Element is a Function Query then->
                        else if ($(droppedElement).hasClass('function-query-drag')) {
                            self.handleWindowFilterProjectionQuery(constants.FUNCTION_QUERY, mouseTop, mouseLeft, false,
                                "Query");
                        }

                        // If the dropped Element is a Join Query then->
                        else if ($(droppedElement).hasClass('join-query-drag')) {
                            self.handleJoinQuery(mouseTop, mouseLeft, false, "Join Query");
                        }

                        // If the dropped Element is a Pattern Query then->
                        else if ($(droppedElement).hasClass('pattern-query-drag')) {
                            self.handlePatternQuery(mouseTop, mouseLeft, false, "Pattern Query");
                        }

                        // If the dropped Element is a Sequence Query then->
                        else if ($(droppedElement).hasClass('sequence-query-drag')) {
                            self.handleSequenceQuery(mouseTop, mouseLeft, false, "Sequence Query");
                        }

                        // If the dropped Element is a Partition then->
                        else if ($(droppedElement).hasClass('partition-drag')) {
                            self.handlePartition(mouseTop, mouseLeft, false);
                        }

                        // set the isDesignViewContentChanged to true
                        self.configurationData.setIsDesignViewContentChanged(true);
                    }
                });
            });

            // check the validity of the connections and drop if invalid
            function checkConnectionValidityBeforeElementDrop() {
                self.jsPlumbInstance.bind('beforeDrop', function (connection) {
                    var connectionValidity = false;
                    var target = connection.targetId;
                    var targetId = target.substr(0, target.indexOf('-'));
                    /*
                    * There is no 'in' or 'out' clause(for other connection they will have like 'view74_element_6-out')
                    * section in partition connection point. So once we substr with '-' we don't get any value. So we
                    * explicitly set the targetId.
                    * */
                    if (targetId === '') {
                        targetId = target;
                    }
                    var targetElement = $('#' + targetId);

                    var source = connection.sourceId;
                    var sourceId = source.substr(0, source.indexOf('-'));
                    /*
                    * There is no 'in' or 'out' clause(for other connection they will have like 'view74_element_6-out')
                    * section in partition connection point. So once we substr with '-' we don't get any value. So we
                    * explicitly set the sourceId.
                    * */
                    if (sourceId === '') {
                        sourceId = source;
                    }
                    var sourceElement = $('#' + sourceId);

                    // avoid the expose of inner-streams outside the group
                    if (sourceElement.hasClass(constants.STREAM)
                        && self.jsPlumbInstance.getGroupFor(sourceId) !== undefined) {
                        if (self.jsPlumbInstance.getGroupFor(sourceId) !== self.jsPlumbInstance.getGroupFor(targetId)) {
                            DesignViewUtils.prototype
                                .errorAlert("Invalid Connection: Inner Streams are not exposed to outside");
                            return connectionValidity;
                        } else {
                            return connectionValidity = true;
                        }

                    } else if (targetElement.hasClass(constants.STREAM)
                        && self.jsPlumbInstance.getGroupFor(targetId) !== undefined) {
                        if (self.jsPlumbInstance.getGroupFor(targetId) !== self.jsPlumbInstance.getGroupFor(sourceId)) {
                            DesignViewUtils.prototype
                                .errorAlert("Invalid Connection: Inner Streams are not exposed to outside");
                            return connectionValidity;
                        } else {
                            return connectionValidity = true;
                        }

                    } else if (targetElement.hasClass(constants.PARTITION_CONNECTION_POINT)) {
                        if (!sourceElement.hasClass(constants.STREAM)) {
                            DesignViewUtils.prototype.errorAlert("Invalid Connection: Connect an outer stream");
                            return connectionValidity;
                        } else {
                            var partitionId = targetElement.parent()[0].id;
                            var partition = self.configurationData.getSiddhiAppConfig().getPartition(partitionId);
                            var connectedStreamName
                                = self.configurationData.getSiddhiAppConfig().getStream(sourceId).getName();
                            var isStreamConnected = partition.checkOuterStreamIsAlreadyConnected(connectedStreamName);
                            if (isStreamConnected) {
                                DesignViewUtils.prototype
                                    .errorAlert("Invalid Connection: Stream is already connected to the partition");
                                return connectionValidity;
                            } else {

                                var isStreamDirectlyConnectedToAQuery = false;
                                _.forEach(partition.getWindowFilterProjectionQueryList(), function (query) {
                                    if (query.getQueryInput() !== undefined
                                        && query.getQueryInput().getConnectedSource() !== undefined
                                        && query.getQueryInput().getConnectedSource() === connectedStreamName) {
                                        isStreamDirectlyConnectedToAQuery = true;
                                    }
                                });

                                _.forEach(partition.getJoinQueryList(), function (query) {
                                    if (query.getQueryInput() !== undefined) {
                                        if (query.getQueryInput().getFirstConnectedElement() !== undefined
                                            && query.getQueryInput().getFirstConnectedElement().type
                                            === constants.STREAM
                                            && query.getQueryInput().getFirstConnectedElement().name
                                            === connectedStreamName) {
                                            isStreamDirectlyConnectedToAQuery = true;

                                        } else if (query.getQueryInput().getSecondConnectedElement() !== undefined
                                            && query.getQueryInput().getSecondConnectedElement().type
                                            === constants.STREAM
                                            && query.getQueryInput().getSecondConnectedElement().name
                                            === connectedStreamName) {
                                            isStreamDirectlyConnectedToAQuery = true;
                                        }
                                    }
                                });

                                _.forEach(partition.getPatternQueryList(), function (query) {
                                    if (query.getQueryInput() !== undefined) {
                                        var connectedElementNameList
                                            = query.getQueryInput().getConnectedElementNameList();
                                        _.forEach(connectedElementNameList, function (elementName) {
                                            if (connectedStreamName === elementName) {
                                                isStreamDirectlyConnectedToAQuery = true;
                                            }
                                        });
                                    }
                                });

                                _.forEach(partition.getSequenceQueryList(), function (query) {
                                    if (query.getQueryInput() !== undefined) {
                                        var connectedElementNameList
                                            = query.getQueryInput().getConnectedElementNameList();
                                        _.forEach(connectedElementNameList, function (elementName) {
                                            if (connectedStreamName === elementName) {
                                                isStreamDirectlyConnectedToAQuery = true;
                                            }
                                        });
                                    }
                                });

                                if (isStreamDirectlyConnectedToAQuery) {
                                    DesignViewUtils.prototype.errorAlert("Invalid Connection: Connected stream is " +
                                        "already directly connected to a query inside the partition.");
                                    return connectionValidity;
                                } else {
                                    return connectionValidity = true;
                                }
                            }
                        }

                    } else if (sourceElement.hasClass(constants.PARTITION_CONNECTION_POINT)) {
                        // check whether the partition connection point has a valid connection with a outer stream.
                        // If not display a error message.
                        var sourceConnections = self.jsPlumbInstance.getConnections({target: sourceId});
                        if (sourceConnections.length === 0) {
                            DesignViewUtils.prototype.errorAlert("Invalid Connection: Connect a outer stream first");
                            return connectionValidity;
                        }
                        var partitionId = sourceElement.parent()[0].id;
                        if (self.jsPlumbInstance.getGroupFor(targetId)
                            !== self.jsPlumbInstance.getGroupFor(partitionId)) {
                            DesignViewUtils.prototype
                                .errorAlert("Invalid Connection: Connect to a query input inside the partition");
                            return connectionValidity;
                        } else {
                            if (targetElement.hasClass(constants.PROJECTION)
                                || targetElement.hasClass(constants.FILTER)
                                || targetElement.hasClass(constants.WINDOW_QUERY)
                                || targetElement.hasClass(constants.FUNCTION_QUERY)
                                || targetElement.hasClass(constants.PATTERN)
                                || targetElement.hasClass(constants.JOIN)
                                || targetElement.hasClass(constants.SEQUENCE)) {
                                return connectionValidity = true;
                            } else {
                                DesignViewUtils.prototype
                                    .errorAlert("Invalid Connection: Connect to a query input inside the partition");
                                return connectionValidity;
                            }
                        }

                    } else if (sourceElement.hasClass(constants.PARTITION)) {
                        if ($(self.jsPlumbInstance.getGroupFor(targetId)).attr('id') == sourceId) {
                            return connectionValidity = true;
                        }
                        DesignViewUtils.prototype.errorAlert("Invalid Connection: Connect to a partition query");
                        return connectionValidity;

                    } else if (sourceElement.hasClass(constants.SOURCE)) {
                        if (!targetElement.hasClass(constants.STREAM)) {
                            DesignViewUtils.prototype.errorAlert("Invalid Connection: Connect to a stream");
                            return connectionValidity;
                        } else {
                            return connectionValidity = true;
                        }

                    } else if (targetElement.hasClass(constants.SINK)) {
                        if (sourceElement.hasClass(constants.STREAM)) {
                            return connectionValidity = true;
                        }
                        DesignViewUtils.prototype
                            .errorAlert("Invalid Connection: Sink input source should be a stream");
                        return connectionValidity;

                    } else if (targetElement.hasClass(constants.AGGREGATION)) {
                        if (!(sourceElement.hasClass(constants.STREAM) || sourceElement.hasClass(constants.TRIGGER))) {
                            DesignViewUtils.prototype
                                .errorAlert("Invalid Connection: Aggregation input should be a stream or a trigger");
                            return connectionValidity;
                        } else {
                            return connectionValidity = true;
                        }
                    } //we allowing all sinks to be connected to source here if connections points are available to
                      // connect. This need to be changed if there are use cases to allow specifics to connect
                    else if (targetElement.hasClass(constants.SOURCE)) {
                        if (sourceElement.hasClass(constants.SINK)) {
                            return connectionValidity = true;
                        }
                        DesignViewUtils.prototype
                            .errorAlert("Invalid Connection: http-request sink input source should be a " +
                                "http-response source");
                        return connectionValidity;
                    }

                    // When connecting streams to a query inside the partition if it is connected to the partition
                    // connection point, it cannot connect to the query directly
                    if ((targetElement.hasClass(constants.PROJECTION) || targetElement.hasClass(constants.FILTER)
                        || targetElement.hasClass(constants.WINDOW_QUERY) || targetElement.hasClass(constants.JOIN)
                        || targetElement.hasClass(constants.FUNCTION_QUERY) || targetElement.hasClass(constants.PATTERN)
                        || targetElement.hasClass(constants.SEQUENCE))
                        && sourceElement.hasClass(constants.STREAM)) {
                        var querySavedInsideAPartition
                            = self.configurationData.getSiddhiAppConfig().getQueryByIdSavedInsideAPartition(targetId);
                        var isQueryInsideAPartition = querySavedInsideAPartition !== undefined;
                        if (isQueryInsideAPartition) {
                            var partitionId = (self.jsPlumbInstance.getGroupFor(targetId)).id;
                            var partition = self.configurationData.getSiddhiAppConfig().getPartition(partitionId);
                            var connectedStreamName
                                = self.configurationData.getSiddhiAppConfig().getStream(sourceId).getName();
                            var isStreamConnected = partition.checkOuterStreamIsAlreadyConnected(connectedStreamName);
                            if (isStreamConnected) {
                                DesignViewUtils.prototype
                                    .errorAlert("Invalid Connection: Stream is already connected to the partition");
                                return connectionValidity;
                            }
                        }
                    }
                    if (targetElement.hasClass(constants.PATTERN) || targetElement.hasClass(constants.SEQUENCE)) {
                        if (!(sourceElement.hasClass(constants.STREAM) || sourceElement.hasClass(constants.TRIGGER))) {
                            DesignViewUtils.prototype.errorAlert("Invalid Connection");
                            return connectionValidity;
                        } else {
                            return connectionValidity = true;
                        }

                    } else if (targetElement.hasClass(constants.PROJECTION) || targetElement.hasClass(constants.FILTER)
                        || targetElement.hasClass(constants.WINDOW_QUERY)
                        || targetElement.hasClass(constants.FUNCTION_QUERY)) {
                        if (!(sourceElement.hasClass(constants.STREAM) || sourceElement.hasClass(constants.WINDOW)
                            || sourceElement.hasClass(constants.TRIGGER))) {
                            DesignViewUtils.prototype.errorAlert("Invalid Connection");
                            return connectionValidity;
                        } else {
                            return connectionValidity = true;
                        }

                    } else if (targetElement.hasClass(constants.JOIN)) {
                        if (!(sourceElement.hasClass(constants.STREAM) || sourceElement.hasClass(constants.TABLE)
                            || sourceElement.hasClass(constants.AGGREGATION)
                            || sourceElement.hasClass(constants.TRIGGER)
                            || sourceElement.hasClass(constants.WINDOW))) {
                            DesignViewUtils.prototype.errorAlert("Invalid Connection");
                            return connectionValidity;
                        } else {
                            var sourceElementObject =
                                self.configurationData.getSiddhiAppConfig().getDefinitionElementById(sourceId);
                            if (sourceElementObject !== undefined) {
                                var connectedElementSourceType = sourceElementObject.type;
                            } else {
                                console.log("Cannot find the source element connected to join query");
                            }
                            var joinQuery = self.configurationData.getSiddhiAppConfig().getJoinQuery(targetId);
                            var queryInput = joinQuery.getQueryInput();
                            if (!queryInput) {
                                return connectionValidity = true;
                            } else {
                                var firstConnectedElement = queryInput.getFirstConnectedElement();
                                var secondConnectedElement = queryInput.getSecondConnectedElement();
                                if (!firstConnectedElement && !secondConnectedElement) {
                                    return connectionValidity = true;
                                } else if (firstConnectedElement !== undefined
                                    && secondConnectedElement !== undefined) {
                                    DesignViewUtils.prototype
                                        .errorAlert("Invalid Connection: Only two input elements are allowed to " +
                                            "connect in join query!");
                                    return connectionValidity = false;
                                } else if (firstConnectedElement !== undefined
                                    && !secondConnectedElement) {
                                    var firstElementType = firstConnectedElement.type;
                                    if (firstElementType === 'STREAM' || firstElementType === 'TRIGGER'
                                        || firstElementType === 'WINDOW') {
                                        return connectionValidity = true;
                                    } else if (connectedElementSourceType === 'STREAM'
                                        || connectedElementSourceType === 'TRIGGER'
                                        || connectedElementSourceType === 'WINDOW') {
                                        return connectionValidity = true;
                                    } else {
                                        DesignViewUtils.prototype
                                            .errorAlert("Invalid Connection: At least one connected input element in " +
                                                "join query should be a stream or a trigger or a window!");
                                        return connectionValidity = false;
                                    }
                                } else if (!firstConnectedElement && secondConnectedElement !== undefined) {
                                    var secondElementType = secondConnectedElement.type;
                                    if (secondElementType === 'STREAM' || secondElementType === 'TRIGGER'
                                        || secondElementType === 'WINDOW') {
                                        return connectionValidity = true;
                                    } else if (connectedElementSourceType === 'STREAM'
                                        || connectedElementSourceType === 'TRIGGER'
                                        || connectedElementSourceType === 'WINDOW') {
                                        return connectionValidity = true;
                                    } else {
                                        DesignViewUtils.prototype
                                            .errorAlert("Invalid Connection: At least one connected input element in " +
                                                "join query should be a stream or a trigger or a window!");
                                        return connectionValidity = false;
                                    }
                                }
                            }
                        }

                    } else if (sourceElement.hasClass(constants.PROJECTION) || sourceElement.hasClass(constants.FILTER)
                        || sourceElement.hasClass(constants.WINDOW_QUERY) || sourceElement.hasClass(constants.PATTERN)
                        || sourceElement.hasClass(constants.JOIN) || sourceElement.hasClass(constants.SEQUENCE)
                        || sourceElement.hasClass(constants.FUNCTION_QUERY)) {
                        if (!(targetElement.hasClass(constants.STREAM) || targetElement.hasClass(constants.TABLE)
                            || targetElement.hasClass(constants.WINDOW))) {
                            DesignViewUtils.prototype.errorAlert("Invalid Connection");
                            return connectionValidity;
                        } else {
                            return connectionValidity = true;
                        }
                    }
                    if (!connectionValidity) {
                        DesignViewUtils.prototype.errorAlert("Invalid Connection");
                    }
                    return connectionValidity;
                });
            }

            // Update the model when a connection is established and bind events for the connection
            function updateModelOnConnectionAttach() {
                self.jsPlumbInstance.bind('connection', function (connection) {

                    // set the isDesignViewContentChanged to true
                    self.configurationData.setIsDesignViewContentChanged(true);

                    var target = connection.targetId;
                    var targetId = target.substr(0, target.indexOf('-'));
                    var targetType;
                    /*
                    * There is no 'in' or 'out' clause(for other connection they will have like 'view74_element_6-out')
                    * section in partition connection point. So once we substr with '-' we don't get any value. So we
                    * explicitly set the targetId.  Simply if targetId is '' that means this connection is related to a
                    * partition.
                    * */
                    if (targetId === '') {
                        targetId = target;
                        targetType = 'PARTITION';
                    } else {
                        if (self.configurationData.getSiddhiAppConfig().getDefinitionElementById(targetId, true, true)
                            !== undefined) {
                            targetType
                                = self.configurationData.getSiddhiAppConfig()
                                .getDefinitionElementById(targetId, true, true).type;
                        } else {
                            console.log("Target element not found!");
                        }
                    }
                    var targetElement = $('#' + targetId);

                    var source = connection.sourceId;
                    var sourceId = source.substr(0, source.indexOf('-'));
                    var sourceType;
                    /*
                    * There is no 'in' or 'out' clause(for other connection they will have like 'view74_element_6-out')
                    * section in partition connection point. So once we substr with '-' we don't get any value. So we
                    * explicitly set the sourceId.  Simply if sourceId is '' that means this connection is related to a
                    * partition.
                    * */
                    var isFromFaultStream = connection.sourceId.substr(connection.sourceId.indexOf('-')) === '-err-out';
                    if (sourceId === '') {
                        sourceId = source;
                        sourceType = 'PARTITION';
                    } else {
                        if (isFromFaultStream) {
                            // Change the source id to the respective source id of the fault stream
                            var streamMap = {};
                            var eventStreamName = '';
                            self.configurationData.getSiddhiAppConfig().getStreamList().forEach(function(s) {
                                streamMap[s.getName()] = s;
                                if (s.getId() === sourceId) {
                                    eventStreamName = s.getName();
                                }
                            });
                            sourceId = streamMap[Constants.FAULT_STREAM_PREFIX + eventStreamName].getId();
                        }
                        if (self.configurationData.getSiddhiAppConfig().getDefinitionElementById(sourceId, true, true)
                            !== undefined) {
                            sourceType
                                = self.configurationData.getSiddhiAppConfig()
                                .getDefinitionElementById(sourceId, true, true).type;
                        } else {
                            console.log("Source element not found!");
                        }
                    }
                    var sourceElement = $('#' + sourceId);

                    var isConnectionMadeInsideAPartition = false;
                    if (self.jsPlumbInstance.getGroupFor(sourceId) !== undefined
                        && self.jsPlumbInstance.getGroupFor(targetId) !== undefined) {
                        isConnectionMadeInsideAPartition = true;
                    }
                    // create and add an edge to the edgeList

                    var edgeId = '' + sourceId + '_' + targetId + '';
                    var edgeInTheEdgeList = self.configurationData.getEdge(edgeId);
                    if (!edgeInTheEdgeList) {
                        var edgeOptions = {};
                        _.set(edgeOptions, 'id', edgeId);
                        _.set(edgeOptions, 'childId', targetId);
                        _.set(edgeOptions, 'childType', targetType);
                        _.set(edgeOptions, 'parentId', sourceId);
                        _.set(edgeOptions, 'parentType', sourceType);
                        var edge = new Edge(edgeOptions);
                        self.configurationData.addEdge(edge);
                    }

                    var model;
                    var connectedElementName;

                    if (targetElement.hasClass(constants.PARTITION_CONNECTION_POINT)
                        && sourceElement.hasClass(constants.STREAM)) {
                        var partitionId = targetElement.parent()[0].id;
                        var partition = self.configurationData.getSiddhiAppConfig().getPartition(partitionId);
                        connectedElementName
                            = self.configurationData.getSiddhiAppConfig().getStream(sourceId).getName();

                        /*
                        * check whether the stream is already connected to partition. This validation is done in the
                        * checkConnectionValidityBeforeElementDrop() function. But that beforedrop event is triggered
                        * only when user adds connection. It doesn't fire when we programmatically create connections
                        * (in this case rendering the design view from code). So we need to do the validation here
                        * again.
                        * */
                        var isStreamConnected = partition.checkOuterStreamIsAlreadyConnected(connectedElementName);
                        if (!isStreamConnected) {
                            var partitionWithOptions = {};
                            _.set(partitionWithOptions, 'streamName', connectedElementName);
                            _.set(partitionWithOptions, 'expression', undefined);
                            var partitionWithObject = new PartitionWith(partitionWithOptions);
                            partition.addPartitionWith(partitionWithObject);
                        }

                        var partitionElement = $('#' + partitionId);
                        var newPartitionConnectorInPartNo = self.generateNextConnectionPointIdForPartition(partitionId);

                        var connectionIn =
                            $('<div class="' + constants.PARTITION_CONNECTION_POINT + '">')
                                .attr('id', newPartitionConnectorInPartNo);
                        partitionElement.append(connectionIn);

                        self.jsPlumbInstance.makeTarget(connectionIn, {
                            anchor: 'Left',
                            maxConnections: 1,
                            deleteEndpointsOnDetach: true
                        });
                        self.jsPlumbInstance.makeSource(connectionIn, {
                            anchor: 'Right',
                            deleteEndpointsOnDetach: true
                        });

                    } else if (sourceElement.hasClass(constants.SOURCE) && targetElement.hasClass(constants.STREAM)) {
                        connectedElementName = self.configurationData.getSiddhiAppConfig().getStream(targetId)
                            .getName();
                        self.configurationData.getSiddhiAppConfig().getSource(sourceId)
                            .setConnectedElementName(connectedElementName);

                    } else if (targetElement.hasClass(constants.SINK) && sourceElement.hasClass(constants.STREAM)) {
                        connectedElementName = self.configurationData.getSiddhiAppConfig().getStream(sourceId)
                            .getName();
                        self.configurationData.getSiddhiAppConfig().getSink(targetId)
                            .setConnectedElementName(connectedElementName);

                    } else if (sourceElement.hasClass(constants.SINK) && targetElement.hasClass(constants.SOURCE)) {
                        connectedElementName = self.configurationData.getSiddhiAppConfig().getSource(targetId)
                            .getType();
                        self.configurationData.getSiddhiAppConfig().getSink(sourceId)
                            .setConnectedRightElementName(connectedElementName);

                    } else if (targetElement.hasClass(constants.AGGREGATION)
                        && (sourceElement.hasClass(constants.STREAM) || sourceElement.hasClass(constants.TRIGGER))) {
                        if (sourceElement.hasClass(constants.STREAM)) {
                            connectedElementName = self.configurationData.getSiddhiAppConfig().getStream(sourceId)
                                .getName();
                        } else if (sourceElement.hasClass(constants.TRIGGER)) {
                            connectedElementName = self.configurationData.getSiddhiAppConfig().getTrigger(sourceId)
                                .getName();
                        }
                        self.configurationData.getSiddhiAppConfig().getAggregation(targetId)
                            .setConnectedSource(connectedElementName);

                    } else if (sourceElement.hasClass(constants.STREAM) || sourceElement.hasClass(constants.TABLE)
                        || sourceElement.hasClass(constants.AGGREGATION) || sourceElement.hasClass(constants.WINDOW)
                        || sourceElement.hasClass(constants.TRIGGER)
                        || sourceElement.hasClass(constants.PARTITION_CONNECTION_POINT)) {

                        /*
                        * Partition connection point represents a stream connection point. so it holds a reference for
                        * the stream.So that in here we replaces the source element with the actual stream element if a
                        * connection partition connection pint is found.
                        * */
                        if (sourceElement.hasClass(constants.PARTITION_CONNECTION_POINT)) {
                            var sourceConnection = self.jsPlumbInstance.getConnections({target: sourceId});
                            var sourceConnectionId = sourceConnection[0].sourceId;
                            var connectedStreamId = sourceConnectionId.substr(0, sourceConnectionId.indexOf('-'));
                            connectedElementName = self.configurationData.getSiddhiAppConfig()
                                .getStream(connectedStreamId).getName();
                            sourceElement = $('#' + connectedStreamId);
                            sourceId = connectedStreamId;
                        }
                        else if (sourceElement.hasClass(constants.STREAM)) {
                            connectedElementName = self.configurationData.getSiddhiAppConfig().getStream(sourceId)
                                .getName();
                        }
                        else if (sourceElement.hasClass(constants.TABLE)) {
                            connectedElementName = self.configurationData.getSiddhiAppConfig().getTable(sourceId)
                                .getName();
                        }
                        else if (sourceElement.hasClass(constants.WINDOW)) {
                            connectedElementName = self.configurationData.getSiddhiAppConfig().getWindow(sourceId)
                                .getName();
                        }
                        else if (sourceElement.hasClass(constants.AGGREGATION)) {
                            connectedElementName = self.configurationData.getSiddhiAppConfig().getAggregation(sourceId)
                                .getName();
                        }
                        else if (sourceElement.hasClass(constants.TRIGGER)) {
                            connectedElementName = self.configurationData.getSiddhiAppConfig().getTrigger(sourceId)
                                .getName();
                        }

                        if ((sourceElement.hasClass(constants.STREAM) || sourceElement.hasClass(constants.WINDOW)
                            || sourceElement.hasClass(constants.TRIGGER))
                            && (targetElement.hasClass(constants.PROJECTION) || targetElement.hasClass(constants.FILTER)
                                || targetElement.hasClass(constants.WINDOW_QUERY)
                                || targetElement.hasClass(constants.FUNCTION_QUERY))) {
                            model = self.configurationData.getSiddhiAppConfig()
                                .getWindowFilterProjectionQuery(targetId);
                            var type;
                            if (targetElement.hasClass(constants.PROJECTION)) {
                                type = 'PROJECTION';
                            }
                            else if (targetElement.hasClass(constants.FILTER)) {
                                type = 'FILTER';
                            }
                            else if (targetElement.hasClass(constants.WINDOW_QUERY)) {
                                type = 'WINDOW';
                            }
                            else if (targetElement.hasClass(constants.FUNCTION_QUERY)) {
                                type = 'FUNCTION';
                            }
                            if (!model.getQueryInput()) {
                                var queryInputOptions = {};
                                _.set(queryInputOptions, 'type', type);
                                _.set(queryInputOptions, 'from', connectedElementName);
                                var queryInputObject = new WindowFilterProjectionQueryInput(queryInputOptions);
                                model.setQueryInput(queryInputObject);
                            } else {
                                model.getQueryInput().setConnectedSource(connectedElementName);
                            }
                        } else if (targetElement.hasClass(constants.JOIN)) {
                            model = self.configurationData.getSiddhiAppConfig().getJoinQuery(targetId);
                            var queryInput = model.getQueryInput();

                            var sourceElementObject =
                                self.configurationData.getSiddhiAppConfig().getDefinitionElementById(sourceId);
                            var connectedElement;
                            if (sourceElementObject !== undefined) {
                                var connectedElementSourceName = (sourceElementObject.element).getName();
                                var connectedElementSourceType = sourceElementObject.type;
                                connectedElement = {
                                    name: connectedElementSourceName,
                                    type: connectedElementSourceType
                                };

                                if (!queryInput) {
                                    var joinQueryInput = new JoinQueryInput();
                                    joinQueryInput.setFirstConnectedElement(connectedElement);
                                    model.setQueryInput(joinQueryInput);
                                } else {
                                    var firstConnectedElement = queryInput.getFirstConnectedElement();
                                    var secondConnectedElement = queryInput.getSecondConnectedElement();
                                    if (!firstConnectedElement) {
                                        queryInput.setFirstConnectedElement(connectedElement);
                                    } else if (!secondConnectedElement) {
                                        queryInput.setSecondConnectedElement(connectedElement);
                                    } else {
                                        console.log("Error: First and second input elements are already filled in " +
                                            "join query!");
                                    }
                                    if (queryInput.getLeft() && queryInput.getRight()) {
                                        if (queryInput.getLeft().getConnectedSource() === queryInput.getRight()
                                            .getConnectedSource()) {
                                            queryInput.setFirstConnectedElement(connectedElement);
                                            queryInput.setSecondConnectedElement(connectedElement);
                                        }
                                    }
                                }
                            }
                        } else if (sourceElement.hasClass(constants.STREAM)
                            || sourceElement.hasClass(constants.TRIGGER)) {

                            if (sourceElement.hasClass(constants.STREAM)) {
                                connectedElementName =
                                    self.configurationData.getSiddhiAppConfig().getStream(sourceId).getName();
                            } else {
                                connectedElementName =
                                    self.configurationData.getSiddhiAppConfig().getTrigger(sourceId).getName();
                            }

                            if (targetElement.hasClass(constants.PATTERN)) {
                                model = self.configurationData.getSiddhiAppConfig().getPatternQuery(targetId);
                                if (!model.getQueryInput()) {
                                    var patternQueryInputOptions = {};
                                    _.set(patternQueryInputOptions, 'type', 'PATTERN');
                                    var patternQueryInputObject =
                                        new PatternOrSequenceQueryInput(patternQueryInputOptions);
                                    patternQueryInputObject.addConnectedElementName(connectedElementName);
                                    model.setQueryInput(patternQueryInputObject);
                                } else {
                                    model.getQueryInput().addConnectedElementName(connectedElementName);
                                }
                            } else if (targetElement.hasClass(constants.SEQUENCE)) {
                                model = self.configurationData.getSiddhiAppConfig().getSequenceQuery(targetId);
                                if (!model.getQueryInput()) {
                                    var sequenceQueryInputOptions = {};
                                    _.set(sequenceQueryInputOptions, 'type', 'SEQUENCE');
                                    var sequenceQueryInputObject =
                                        new PatternOrSequenceQueryInput(sequenceQueryInputOptions);
                                    sequenceQueryInputObject.addConnectedElementName(connectedElementName);
                                    model.setQueryInput(sequenceQueryInputObject);
                                } else {
                                    model.getQueryInput().addConnectedElementName(connectedElementName);
                                }
                            }
                        }
                    }
                    else if (targetElement.hasClass(constants.STREAM) || targetElement.hasClass(constants.TABLE)
                        || targetElement.hasClass(constants.WINDOW)) {
                        if (sourceElement.hasClass(constants.PROJECTION) || sourceElement.hasClass(constants.FILTER)
                            || sourceElement.hasClass(constants.WINDOW_QUERY) || sourceElement.hasClass(constants.JOIN)
                            || sourceElement.hasClass(constants.PATTERN)
                            || sourceElement.hasClass(constants.FUNCTION_QUERY)
                            || sourceElement.hasClass(constants.SEQUENCE)) {

                            if (targetElement.hasClass(constants.STREAM)) {
                                connectedElementName = self.configurationData.getSiddhiAppConfig().getStream(targetId)
                                    .getName();
                            }
                            else if (targetElement.hasClass(constants.TABLE)) {
                                connectedElementName = self.configurationData.getSiddhiAppConfig().getTable(targetId)
                                    .getName();
                            }
                            else if (targetElement.hasClass(constants.WINDOW)) {
                                connectedElementName = self.configurationData.getSiddhiAppConfig().getWindow(targetId)
                                    .getName();
                            }

                            if (sourceElement.hasClass(constants.PROJECTION) || sourceElement.hasClass(constants.FILTER)
                                || sourceElement.hasClass(constants.WINDOW_QUERY)
                                || sourceElement.hasClass(constants.FUNCTION_QUERY)) {
                                model = self.configurationData.getSiddhiAppConfig()
                                    .getWindowFilterProjectionQuery(sourceId);
                            } else if (sourceElement.hasClass(constants.JOIN)) {
                                model = self.configurationData.getSiddhiAppConfig().getJoinQuery(sourceId);
                            } else if (sourceElement.hasClass(constants.PATTERN)) {
                                model = self.configurationData.getSiddhiAppConfig().getPatternQuery(sourceId);
                            } else if (sourceElement.hasClass(constants.SEQUENCE)) {
                                model = self.configurationData.getSiddhiAppConfig().getSequenceQuery(sourceId);
                            }

                            if (!model.getQueryOutput()) {
                                var queryOutputOptions = {};
                                _.set(queryOutputOptions, 'target', connectedElementName);
                                var patternQueryOutputObject = new QueryOutput(queryOutputOptions);
                                model.setQueryOutput(patternQueryOutputObject);
                            } else {
                                model.getQueryOutput().setTarget(connectedElementName);
                            }
                        }
                    }

                    // do not check for json validity if the design is still generating from the data sent from backend
                    if (!self.configurationData.getIsStillDrawingGraph()) {
                        if (sourceType === 'PARTITION') {
                            sourceId = sourceElement.parent().attr('id');
                        } else if (targetType === 'PARTITION') {
                            targetId = targetElement.parent().attr('id');
                        }
                        // validate source and target elements
                        checkJSONValidityOfElement(self, sourceId, true);
                        checkJSONValidityOfElement(self, targetId, true);
                    }

                    var connectionObject = connection.connection;
                    // add a overlay of a close icon for connection. connection can be detached by clicking on it
                    var close_icon_overlay = connectionObject.addOverlay([
                        "Custom", {
                            create: function () {
                                return $(
                                    '<span><i class="fw fw-delete" id="' + self.currentTabId + connectionObject.id +
                                    '"data-toggle="popover"></i></span>');
                            },
                            location: 0.60,
                            id: "close",
                            events: {
                                click: function () {
                                    popOverForConnector();
                                }
                            }
                        }
                    ]);

                    function popOverForConnector() {
                        $('#' + self.currentTabId + connectionObject.id).popover({
                            trigger: 'focus',
                            title: 'Confirmation',
                            html: true,
                            content: function () {
                                return $('.pop-over').html();
                            }
                        });
                        $('#' + self.currentTabId + connectionObject.id).off();
                        $('#' + self.currentTabId + connectionObject.id).popover("show");
                        $('.btn_no').focus();
                        $(".overlayed-container ").fadeTo(200, 1);
                        // Custom jQuery to hide popover on click of the close button
                        $("#" + self.currentTabId + connectionObject.id).siblings(".popover").on("click", ".popover-footer .btn_yes",
                            function () {
                                if (connectionObject.connector !== null) {
                                    self.jsPlumbInstance.deleteConnection(connectionObject);
                                }
                                $(".overlayed-container ").fadeOut(200);
                                $(this).parents(".popover").popover('hide');
                            });
                        $("#" + self.currentTabId + connectionObject.id).siblings(".popover").on("click", ".popover-footer .btn_no",
                            function () {
                                $(".overlayed-container ").fadeOut(200);
                                $(this).parents(".popover").popover('hide');
                                close_icon_overlay.setVisible(false);
                            });
                        // Dismiss the pop-over by clicking outside
                        $('.overlayed-container ').off('click');
                        $('.overlayed-container ').on('click', function (e) {
                            $('[data-toggle="popover"]').each(function () {
                                if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(
                                    e.target).length === 0) {
                                    $(this).popover('hide');
                                    $(".overlayed-container ").fadeOut(200);
                                    close_icon_overlay.setVisible(false);
                                }
                            });
                        });
                        $(".btn_no").on("keyup", function (e) {
                            if (e.which === ESCAPE_KEY && $("#" + self.currentTabId + connectionObject.id).popover()) {
                                $("#" + self.currentTabId + connectionObject.id).popover('hide');
                                $(".overlayed-container ").fadeOut(200);
                                close_icon_overlay.setVisible(false);
                            }
                        });
                        //Navigation using arrow keys
                        $(".btn_no").on('keydown', function (e) {
                            if (e.keyCode == RIGHT_ARROW_KEY) {
                                $('.btn_yes').focus();
                            }
                        });
                        $(".btn_yes").on('keydown', function (e) {
                            if (e.keyCode == LEFT_ARROW_KEY) {
                                $('.btn_no').focus();
                            }
                        });
                        $(".btn_no").on('keydown', function (e) {
                            if (e.keyCode == TAB_KEY) {
                                e.preventDefault();
                            }
                            if (e.keyCode == ENTER_KEY) {
                                e.stopPropagation();
                            }
                        });
                        //Stop tab propagation and enter propagation when popover showed
                        $(".btn_yes").on('keydown', function (e) {
                            if (e.keyCode == TAB_KEY) {
                                e.preventDefault();
                            }
                            if (e.keyCode == ENTER_KEY) {
                                e.stopPropagation();
                            }
                        });
                    }

                    if (isConnectionMadeInsideAPartition) {
                        close_icon_overlay.setVisible(false);
                        // show the close icon when mouse is over the connection
                        connectionObject.bind('mouseenter', function () {
                            close_icon_overlay.setVisible(true);
                        });
                        // hide the close icon when the mouse is not on the connection path
                        connectionObject.bind('mouseleave', function () {
                            if ($("#" + self.currentTabId + connectionObject.id).siblings(".popover").length == 0) {
                                close_icon_overlay.setVisible(false);
                            }
                        });
                    } else {
                        close_icon_overlay.setVisible(false);
                        // show the close icon when mouse is over the connection
                        connectionObject.bind('mouseover', function () {
                            close_icon_overlay.setVisible(true);
                        });
                        // hide the close icon when the mouse is not on the connection path
                        connectionObject.bind('mouseout', function () {
                            if ($("#" + self.currentTabId + connectionObject.id).siblings(".popover").length == 0) {
                                close_icon_overlay.setVisible(false);
                            }
                        });
                    }
                });
            }

            // Update the model before detaching a connection
            function updateModelOnBeforeConnectionDetach() {
                self.jsPlumbInstance.bind('beforeDetach', function (connection) {
                    var target = connection.targetId;
                    var targetId = target.substr(0, target.indexOf('-'));
                    /*
                    * There is no 'in' or 'out' clause(for other connection they will have like 'view74_element_6-out')
                    * section in partition connection point. So once we substr with '-' we don't get any value. So we
                    * explicitly set the targetId.  Simply if targetId is '' that means this connection is related to a
                    * partition.
                    * */
                    if (targetId === '') {
                        targetId = target;
                    } else if (!self.configurationData.getSiddhiAppConfig()
                        .getDefinitionElementById(targetId, true, true)) {
                        console.log("Target element not found!");
                    }
                    var targetElement = $('#' + targetId);

                    var source = connection.sourceId;
                    var sourceId = source.substr(0, source.indexOf('-'));
                    /*
                    * There is no 'in' or 'out' clause(for other connection they will have like 'view74_element_6-out')
                    * section in partition connection point. So once we substr with '-' we don't get any value. So we
                    * explicitly set the sourceId.  Simply if sourceId is '' that means this connection is related to a
                    * partition.
                    * */
                    if (sourceId === '') {
                        sourceId = source;
                    } else if (!self.configurationData.getSiddhiAppConfig()
                        .getDefinitionElementById(sourceId, true, true)) {
                        console.log("Source element not found!");
                    }
                    var sourceElement = $('#' + sourceId);

                    // removing edge from the edgeList
                    var edgeId = '' + sourceId + '_' + targetId + '';
                    self.configurationData.removeEdge(edgeId);

                    if (targetElement.hasClass(constants.PARTITION_CONNECTION_POINT)
                        && sourceElement.hasClass(constants.STREAM)) {
                        var partitionId = targetElement.parent()[0].id;
                        var partition = self.configurationData.getSiddhiAppConfig().getPartition(partitionId);
                        var disconnectedElementName
                            = self.configurationData.getSiddhiAppConfig().getStream(sourceId).getName();

                        partition.removePartitionWith(disconnectedElementName);

                        var connections = self.jsPlumbInstance.getConnections({source: targetId});
                        _.forEach(connections, function (connection) {
                            self.jsPlumbInstance.deleteConnection(connection);
                        });
                        targetElement.detach();

                        // validate the partition
                        checkJSONValidityOfElement(self, partitionId, true);
                    }
                });
            }

            // Update the model when a connection is detached
            function updateModelOnConnectionDetach() {
                self.jsPlumbInstance.bind('connectionDetached', function (connection) {

                    // set the isDesignViewContentChanged to true
                    self.configurationData.setIsDesignViewContentChanged(true);

                    var target = connection.targetId;
                    var targetId = target.substr(0, target.indexOf('-'));
                    /*
                    * There is no 'in' or 'out' clause(for other connection they will have like 'view74_element_6-out')
                    * section in partition connection point. So once we substr with '-' we don't get any value. So we
                    * explicitly set the targetId.  Simply if targetId is '' that means this connection is related to a
                    * partition.
                    * */
                    if (targetId === '') {
                        targetId = target;
                    } else if (!self.configurationData.getSiddhiAppConfig()
                        .getDefinitionElementById(targetId, true, true)) {
                        console.log("Target element not found!");
                    }
                    var targetElement = $('#' + targetId);

                    var source = connection.sourceId;
                    var sourceId = source.substr(0, source.indexOf('-'));
                    /*
                    * There is no 'in' or 'out' clause(for other connection they will have like 'view74_element_6-out')
                    * section in partition connection point. So once we substr with '-' we don't get any value. So we
                    * explicitly set the sourceId.  Simply if sourceId is '' that means this connection is related to a
                    * partition.
                    * */
                    if (sourceId === '') {
                        sourceId = source;
                    } else if (!self.configurationData.getSiddhiAppConfig()
                        .getDefinitionElementById(sourceId, true, true)) {
                        console.log("Source element not found!");
                    }
                    var sourceElement = $('#' + sourceId);

                    // removing edge from the edgeList
                    var edgeId = '' + sourceId + '_' + targetId + '';
                    self.configurationData.removeEdge(edgeId);

                    var model;

                    if (sourceElement.hasClass(constants.SOURCE) && targetElement.hasClass(constants.STREAM)) {
                        self.configurationData.getSiddhiAppConfig().getSource(sourceId)
                            .setConnectedElementName(undefined);

                    } else if (targetElement.hasClass(constants.SINK) && sourceElement.hasClass(constants.STREAM)) {
                        self.configurationData.getSiddhiAppConfig().getSink(targetId)
                            .setConnectedElementName(undefined);

                    } else if (targetElement.hasClass(constants.AGGREGATION)
                        && (sourceElement.hasClass(constants.STREAM) || sourceElement.hasClass(constants.TRIGGER))) {
                        model = self.configurationData.getSiddhiAppConfig().getAggregation(targetId)
                        model.setConnectedSource(undefined);
                        if(sourceElement.hasClass(constants.STREAM)) {
                            model.resetModel(model);
                        }
                    } else if (sourceElement.hasClass(constants.STREAM) || sourceElement.hasClass(constants.TABLE)
                        || sourceElement.hasClass(constants.AGGREGATION) || sourceElement.hasClass(constants.WINDOW)
                        || sourceElement.hasClass(constants.TRIGGER)
                        || sourceElement.hasClass(constants.PARTITION_CONNECTION_POINT)) {

                        // if the sourceElement has the class constants.PARTITION_CONNECTION_POINT then that is
                        // basically a stream because a connection point holds a connection to a stream.
                        // So we replace that sourceElement with the actual stream element.
                        if (sourceElement.hasClass(constants.PARTITION_CONNECTION_POINT)) {
                            var sourceConnection = self.jsPlumbInstance.getConnections({target: sourceId});
                            var sourceConnectionId = sourceConnection[0].sourceId;
                            var connectedStreamId = sourceConnectionId.substr(0, sourceConnectionId.indexOf('-'));
                            sourceElement = $('#' + connectedStreamId);
                            sourceId = connectedStreamId;
                        }

                        if ((sourceElement.hasClass(constants.STREAM) || sourceElement.hasClass(constants.WINDOW)
                            || sourceElement.hasClass(constants.TRIGGER))
                            && (targetElement.hasClass(constants.PROJECTION) || targetElement.hasClass(constants.FILTER)
                                || targetElement.hasClass(constants.WINDOW_QUERY)
                                || targetElement.hasClass(constants.FUNCTION_QUERY))) {
                            model = self.configurationData.getSiddhiAppConfig()
                                .getWindowFilterProjectionQuery(targetId);
                            model.resetInputModel(model);

                        } else if (targetElement.hasClass(constants.JOIN)) {
                            model = self.configurationData.getSiddhiAppConfig().getJoinQuery(targetId);
                            var queryInput = model.getQueryInput();
                            var sourceElementObject =
                                self.configurationData.getSiddhiAppConfig().getDefinitionElementById(sourceId);
                            if (sourceElementObject !== undefined) {
                                var disconnectedElementSourceName = (sourceElementObject.element).getName();
                                if (!queryInput) {
                                    console.log("Join query output is undefined!");
                                    return;
                                }
                                var firstConnectedElement = queryInput.getFirstConnectedElement();
                                var secondConnectedElement = queryInput.getSecondConnectedElement();
                                if (!firstConnectedElement && !secondConnectedElement) {
                                    console.log("firstConnectedElement and secondConnectedElement are undefined!");
                                } else if (firstConnectedElement &&
                                    (firstConnectedElement.name === disconnectedElementSourceName ||
                                    !firstConnectedElement.name)) {
                                    queryInput.setFirstConnectedElement(undefined);
                                } else if (secondConnectedElement
                                    && (secondConnectedElement.name === disconnectedElementSourceName ||
                                    !secondConnectedElement.name)) {
                                    queryInput.setSecondConnectedElement(undefined);
                                } else {
                                    console.log("Error: Disconnected source name not found in join query!");
                                    return;
                                }

                                // if left or sources are created then remove data from those sources
                                if (queryInput.getLeft() !== undefined
                                    && queryInput.getLeft().getConnectedSource() === disconnectedElementSourceName) {
                                    queryInput.setLeft(undefined);
                                } else if (queryInput.getRight() !== undefined
                                    && queryInput.getRight().getConnectedSource() === disconnectedElementSourceName) {
                                    queryInput.setRight(undefined);
                                }
                                model.resetInputModel(model);
                            }

                        } else if (sourceElement.hasClass(constants.STREAM)
                            || sourceElement.hasClass(constants.TRIGGER)) {

                            var disconnectedElementName;
                            if (sourceElement.hasClass(constants.STREAM)) {
                                disconnectedElementName =
                                    self.configurationData.getSiddhiAppConfig().getStream(sourceId).getName();
                            } else {
                                disconnectedElementName =
                                    self.configurationData.getSiddhiAppConfig().getTrigger(sourceId).getName();
                            }

                            if (targetElement.hasClass(constants.PATTERN)) {
                                model = self.configurationData.getSiddhiAppConfig().getPatternQuery(targetId);
                                model.resetInputModel(model, disconnectedElementName);
                            } else if (targetElement.hasClass(constants.SEQUENCE)) {
                                model = self.configurationData.getSiddhiAppConfig().getSequenceQuery(targetId);
                                model.resetInputModel(model, disconnectedElementName);
                            }
                        }

                    } else if (targetElement.hasClass(constants.STREAM) || targetElement.hasClass(constants.TABLE)
                        || targetElement.hasClass(constants.WINDOW)) {
                        if (sourceElement.hasClass(constants.PROJECTION) || sourceElement.hasClass(constants.FILTER)
                            || sourceElement.hasClass(constants.WINDOW_QUERY)
                            || sourceElement.hasClass(constants.FUNCTION_QUERY)
                            || sourceElement.hasClass(constants.JOIN)
                            || sourceElement.hasClass(constants.PATTERN)
                            || sourceElement.hasClass(constants.SEQUENCE)) {

                            if (sourceElement.hasClass(constants.PROJECTION) || sourceElement.hasClass(constants.FILTER)
                                || sourceElement.hasClass(constants.WINDOW_QUERY)
                                || sourceElement.hasClass(constants.FUNCTION_QUERY)) {
                                model = self.configurationData.getSiddhiAppConfig()
                                    .getWindowFilterProjectionQuery(sourceId);
                            } else if (sourceElement.hasClass(constants.JOIN)) {
                                model = self.configurationData.getSiddhiAppConfig().getJoinQuery(sourceId);
                            } else if (sourceElement.hasClass(constants.PATTERN)) {
                                model = self.configurationData.getSiddhiAppConfig().getPatternQuery(sourceId);
                            } else if (sourceElement.hasClass(constants.SEQUENCE)) {
                                model = self.configurationData.getSiddhiAppConfig().getSequenceQuery(sourceId);
                            }
                            model.resetOutputModel(model);
                        }
                    }

                    // validate source and target elements
                    checkJSONValidityOfElement(self, sourceId, true);
                    checkJSONValidityOfElement(self, targetId, true);
                });
            }

            function addMemberToPartitionGroup(self) {
                self.jsPlumbInstance.bind('group:addMember', function (event) {

                    // set the isDesignViewContentChanged to true
                    self.configurationData.setIsDesignViewContentChanged(true);

                    var partitionId = $(event.group).attr('id');
                    var partition = self.configurationData.getSiddhiAppConfig().getPartition(partitionId);

                    // check whether member is already added to the group
                    if (partition.isElementInsidePartition(event.el.id)) {
                        return;
                    }

                    var errorMessage = '';
                    var isGroupMemberValid = false;
                    if ($(event.el).hasClass(constants.FILTER) || $(event.el).hasClass(constants.PROJECTION)
                        || $(event.el).hasClass(constants.WINDOW_QUERY)
                        || $(event.el).hasClass(constants.FUNCTION_QUERY)
                        || $(event.el).hasClass(constants.JOIN)
                        || $(event.el).hasClass(constants.SEQUENCE) || $(event.el).hasClass(constants.PATTERN)
                        || $(event.el).hasClass(constants.STREAM)) {

                        var elementId = event.el.id;
                        var sourceConnectionPointId = elementId + '-out';
                        var targetConnectionPointId = elementId + '-in';

                        var noOfSourceConnections
                            = self.jsPlumbInstance.getConnections({source: sourceConnectionPointId});
                        var noOfTargetConnections
                            = self.jsPlumbInstance.getConnections({target: targetConnectionPointId});
                        var totalConnection = noOfSourceConnections.length + noOfTargetConnections.length;
                        if (totalConnection === 0) {
                            isGroupMemberValid = true;

                            if ($(event.el).hasClass(constants.STREAM)) {
                                var streamObject = self.configurationData.getSiddhiAppConfig().getStream(elementId);
                                var streamObjectCopy = _.cloneDeep(streamObject);

                                var streamName = streamObjectCopy.getName();
                                var firstCharacterInStreamName = (streamName).charAt(0);
                                if (firstCharacterInStreamName !== '#') {
                                    streamName = '#' + streamName;
                                }
                                // check if there is an inner stream with the same name exists. If yes do not add
                                // the element to the partition
                                var isStreamNameUsed
                                    = self.dropElements.formBuilder.formUtils
                                    .isStreamDefinitionNameUsedInPartition(partitionId, streamName);
                                if (!isStreamNameUsed) {
                                    streamObjectCopy.setName(streamName);
                                    var textNode = $('#' + elementId).parent().find('.streamNameNode');
                                    textNode.html(streamName);
                                    self.configurationData.getSiddhiAppConfig().removeStream(elementId);
                                    partition.addStream(streamObjectCopy);

                                    JSONValidator.prototype
                                        .validateInnerStream(streamObjectCopy, self.jsPlumbInstance, true);
                                } else {
                                    isGroupMemberValid = false;
                                    errorMessage = ' An inner stream with the same name is already added to the ' +
                                        'partition.';
                                }
                            } else if ($(event.el).hasClass(constants.PROJECTION)) {
                                var projectionQueryObject = self.configurationData.getSiddhiAppConfig()
                                    .getWindowFilterProjectionQuery(elementId);
                                var projectionQueryObjectCopy = _.cloneDeep(projectionQueryObject);
                                self.configurationData.getSiddhiAppConfig()
                                    .removeWindowFilterProjectionQuery(elementId);
                                partition.addWindowFilterProjectionQuery(projectionQueryObjectCopy);

                            } else if ($(event.el).hasClass(constants.FILTER)) {
                                var filterQueryObject = self.configurationData.getSiddhiAppConfig()
                                    .getWindowFilterProjectionQuery(elementId);
                                var filterQueryObjectCopy = _.cloneDeep(filterQueryObject);
                                self.configurationData.getSiddhiAppConfig()
                                    .removeWindowFilterProjectionQuery(elementId);
                                partition.addWindowFilterProjectionQuery(filterQueryObjectCopy);

                            } else if ($(event.el).hasClass(constants.WINDOW_QUERY)) {
                                var windowQueryObject = self.configurationData.getSiddhiAppConfig()
                                    .getWindowFilterProjectionQuery(elementId);
                                var windowQueryObjectCopy = _.cloneDeep(windowQueryObject);
                                self.configurationData.getSiddhiAppConfig()
                                    .removeWindowFilterProjectionQuery(elementId);
                                partition.addWindowFilterProjectionQuery(windowQueryObjectCopy);

                            } else if ($(event.el).hasClass(constants.FUNCTION_QUERY)) {
                                var functionQueryObject = self.configurationData.getSiddhiAppConfig()
                                    .getWindowFilterProjectionQuery(elementId);
                                var functionQueryObjectCopy = _.cloneDeep(functionQueryObject);
                                self.configurationData.getSiddhiAppConfig()
                                    .removeWindowFilterProjectionQuery(elementId);
                                partition.addWindowFilterProjectionQuery(functionQueryObjectCopy);

                            } else if ($(event.el).hasClass(constants.PATTERN)) {

                                var patternQueryObject = self.configurationData.getSiddhiAppConfig()
                                    .getPatternQuery(elementId);
                                var patternQueryObjectCopy = _.cloneDeep(patternQueryObject);
                                self.configurationData.getSiddhiAppConfig().removePatternQuery(elementId);
                                partition.addPatternQuery(patternQueryObjectCopy);

                            } else if ($(event.el).hasClass(constants.SEQUENCE)) {
                                var sequenceQueryObject = self.configurationData.getSiddhiAppConfig()
                                    .getSequenceQuery(elementId);
                                var sequenceQueryObjectCopy = _.cloneDeep(sequenceQueryObject);
                                self.configurationData.getSiddhiAppConfig().removeSequenceQuery(elementId);
                                partition.addSequenceQuery(sequenceQueryObjectCopy);

                            } else if ($(event.el).hasClass(constants.JOIN)) {
                                var joinQueryObject = self.configurationData.getSiddhiAppConfig()
                                    .getJoinQuery(elementId);
                                var joinQueryObjectCopy = _.cloneDeep(joinQueryObject);
                                self.configurationData.getSiddhiAppConfig().removeJoinQuery(elementId);
                                partition.addJoinQuery(joinQueryObjectCopy);
                            }
                        }
                    }

                    if (!isGroupMemberValid) {
                        DesignViewUtils.prototype
                            .warnAlert('This element cannot be added to partition.' + errorMessage);
                        self.jsPlumbInstance.removeFromGroup(event.group, event.el, false);
                        var elementClientX = $(event.el).attr('data-x');
                        var elementClientY = $(event.el).attr('data-y');
                        var detachedElement = $(event.el).detach();
                        detachedElement.css({
                            left: parseInt(elementClientX) - self.canvas.offset().left,
                            top: parseInt(elementClientY) - self.canvas.offset().top
                        });
                        self.canvas.append(detachedElement);
                        self.jsPlumbInstance.repaintEverything();
                    }
                });
            }

            function checkJSONValidityOfElement(self, elementId, doNotShowErrorMessages) {
                var element = self.configurationData.getSiddhiAppConfig()
                    .getDefinitionElementById(elementId, true, true, true);
                if (element !== undefined) {
                    var type = element.type;
                    var elementObject = element.element;
                    if (type === 'STREAM') {
                        // If this is an inner stream perform validation
                        var streamSavedInsideAPartition
                            = self.configurationData.getSiddhiAppConfig()
                            .getStreamSavedInsideAPartition(elementObject.getId());
                        // if streamSavedInsideAPartition is undefined then the stream is not inside a partition
                        if (streamSavedInsideAPartition !== undefined) {
                            JSONValidator.prototype.validateInnerStream(elementObject, self.jsPlumbInstance, true);
                        }
                    } else if (type === 'WINDOW_FILTER_PROJECTION_QUERY') {
                        JSONValidator.prototype.validateWindowFilterProjectionQuery(elementObject,
                            doNotShowErrorMessages);
                    } else if (type === 'PATTERN_QUERY') {
                        JSONValidator.prototype.validatePatternOrSequenceQuery(elementObject, 'Pattern Query',
                            doNotShowErrorMessages);
                    } else if (type === 'SEQUENCE_QUERY') {
                        JSONValidator.prototype.validatePatternOrSequenceQuery(elementObject, 'Sequence Query',
                            doNotShowErrorMessages);
                    } else if (type === 'JOIN_QUERY') {
                        JSONValidator.prototype.validateJoinQuery(elementObject, doNotShowErrorMessages);
                    } else if (type === 'SOURCE') {
                        JSONValidator.prototype.validateSourceOrSinkAnnotation(elementObject, 'Source',
                            doNotShowErrorMessages);
                    } else if (type === 'SINK') {
                        JSONValidator.prototype.validateSourceOrSinkAnnotation(elementObject, 'Sink',
                            doNotShowErrorMessages);
                    } else if (type === 'AGGREGATION') {
                        JSONValidator.prototype.validateAggregation(elementObject, doNotShowErrorMessages);
                    } else if (type === 'PARTITION') {
                        JSONValidator.prototype.validatePartition(elementObject, self.jsPlumbInstance,
                            doNotShowErrorMessages);
                    }
                }
            }

            checkConnectionValidityBeforeElementDrop();

            updateModelOnConnectionAttach();

            updateModelOnBeforeConnectionDetach();

            updateModelOnConnectionDetach();

            addMemberToPartitionGroup(self);

            self.drawGraphFromAppData();

            self.enableMultipleSelection();
        };

        DesignGrid.prototype.drawGraphFromAppData = function () {
            var self = this;

            //Send SiddhiApp Config to the backend and get tooltips
            var sendingString = JSON.stringify(self.configurationData.siddhiAppConfig);
            var response = this.getTooltips(sendingString);
            var tooltipList = [];
            if (response.status === "success") {
                tooltipList = response.tooltipList;
            } else {
                log.error(response.errorMessage);
            }

            // set isStillDrawingGraph to true since the graph drawing has begun
            self.configurationData.setIsStillDrawingGraph(true);

            _.forEach(self.configurationData.getSiddhiAppConfig().getSourceList(), function (source) {
                var sourceId = source.getId();
                var sourceName = source.getType();
                var array = sourceId.split("_");
                var lastArrayEntry = parseInt(array[array.length - 1]);
                var mouseTop = lastArrayEntry * 100 - self.canvas.offset().top + self.canvas.scrollTop() - 40;
                var mouseLeft = lastArrayEntry * 200 - self.canvas.offset().left + self.canvas.scrollLeft() - 60;
                var sourceToolTip = '';
                var toolTipObject = _.find(tooltipList, function (toolTip) {
                    return toolTip.id === sourceId;
                });
                if (toolTipObject !== undefined) {
                    sourceToolTip = toolTipObject.text;
                }
                self.handleSourceAnnotation(mouseTop, mouseLeft, true, sourceName, sourceId, sourceToolTip);
            });

            _.forEach(self.configurationData.getSiddhiAppConfig().getSinkList(), function (sink) {
                var sinkId = sink.getId();
                var sinkName = sink.getType();
                var array = sinkId.split("_");
                var lastArrayEntry = parseInt(array[array.length - 1]);
                var mouseTop = lastArrayEntry * 100 - self.canvas.offset().top + self.canvas.scrollTop() - 40;
                var mouseLeft = lastArrayEntry * 200 - self.canvas.offset().left + self.canvas.scrollLeft() - 60;
                var sinkToolTip = '';
                var toolTipObject = _.find(tooltipList, function (toolTip) {
                    return toolTip.id === sinkId;
                });
                if (toolTipObject !== undefined) {
                    sinkToolTip = toolTipObject.text;
                }
                self.handleSinkAnnotation(mouseTop, mouseLeft, true, sinkName, sinkId, sinkToolTip);
            });

            _.forEach(self.configurationData.getSiddhiAppConfig().getStreamList(), function (stream) {
                var streamId = stream.getId();
                var streamName = stream.getName();
                var array = streamId.split("_");
                var lastArrayEntry = parseInt(array[array.length - 1]);
                var mouseTop = lastArrayEntry * 100 - self.canvas.offset().top + self.canvas.scrollTop() - 40;
                var mouseLeft = lastArrayEntry * 200 - self.canvas.offset().left + self.canvas.scrollLeft() - 60;
                var streamToolTip = '';
                var toolTipObject = _.find(tooltipList, function (toolTip) {
                    return toolTip.id === streamId;
                });
                if (toolTipObject !== undefined) {
                    streamToolTip = toolTipObject.text;
                }
                self.handleStream(mouseTop, mouseLeft, true, streamId, streamName, streamToolTip, stream);
            });

            _.forEach(self.configurationData.getSiddhiAppConfig().getTableList(), function (table) {

                var tableId = table.getId();
                var tableName = table.getName();
                var array = tableId.split("_");
                var lastArrayEntry = parseInt(array[array.length - 1]);
                var mouseTop = lastArrayEntry * 100 - self.canvas.offset().top + self.canvas.scrollTop() - 40;
                var mouseLeft = lastArrayEntry * 200 - self.canvas.offset().left + self.canvas.scrollLeft() - 60;
                var tableToolTip = '';
                var toolTipObject = _.find(tooltipList, function (toolTip) {
                    return toolTip.id === tableId;
                });
                if (toolTipObject !== undefined) {
                    tableToolTip = toolTipObject.text;
                }
                self.handleTable(mouseTop, mouseLeft, true, tableId, tableName, tableToolTip);
            });

            _.forEach(self.configurationData.getSiddhiAppConfig().getWindowList(), function (window) {

                var windowId = window.getId();
                var windowName = window.getName();
                var array = windowId.split("_");
                var lastArrayEntry = parseInt(array[array.length - 1]);
                var mouseTop = lastArrayEntry * 100 - self.canvas.offset().top + self.canvas.scrollTop() - 40;
                var mouseLeft = lastArrayEntry * 200 - self.canvas.offset().left + self.canvas.scrollLeft() - 60;
                var windowToolTip = '';
                var toolTipObject = _.find(tooltipList, function (toolTip) {
                    return toolTip.id === windowId;
                });
                if (toolTipObject !== undefined) {
                    windowToolTip = toolTipObject.text;
                }
                self.handleWindow(mouseTop, mouseLeft, true, windowId, windowName, windowToolTip);
            });

            _.forEach(self.configurationData.getSiddhiAppConfig().getTriggerList(), function (trigger) {

                var triggerId = trigger.getId();
                var triggerName = trigger.getName();
                var array = triggerId.split("_");
                var lastArrayEntry = parseInt(array[array.length - 1]);
                var mouseTop = lastArrayEntry * 100 - self.canvas.offset().top + self.canvas.scrollTop() - 40;
                var mouseLeft = lastArrayEntry * 200 - self.canvas.offset().left + self.canvas.scrollLeft() - 60;
                var triggerToolTip = '';
                var toolTipObject = _.find(tooltipList, function (toolTip) {
                    return toolTip.id === triggerId;
                });
                if (toolTipObject !== undefined) {
                    triggerToolTip = toolTipObject.text;
                }
                self.handleTrigger(mouseTop, mouseLeft, true, triggerId, triggerName, triggerToolTip);
            });

            _.forEach(self.configurationData.getSiddhiAppConfig().getAggregationList(), function (aggregation) {

                var aggregationId = aggregation.getId();
                var aggregationName = aggregation.getName();
                var array = aggregationId.split("_");
                var lastArrayEntry = parseInt(array[array.length - 1]);
                var mouseTop = lastArrayEntry * 100 - self.canvas.offset().top + self.canvas.scrollTop() - 40;
                var mouseLeft = lastArrayEntry * 200 - self.canvas.offset().left + self.canvas.scrollLeft() - 60;
                var aggregationToolTip = '';
                var toolTipObject = _.find(tooltipList, function (toolTip) {
                    return toolTip.id === aggregationId;
                });
                if (toolTipObject !== undefined) {
                    aggregationToolTip = toolTipObject.text;
                }
                self.handleAggregation(mouseTop, mouseLeft, true, aggregationId, aggregationName, aggregationToolTip);
            });

            _.forEach(self.configurationData.getSiddhiAppConfig().getFunctionList(), function (functionObject) {

                var functionId = functionObject.getId();
                var functionName = functionObject.getName();
                var array = functionId.split("_");
                var lastArrayEntry = parseInt(array[array.length - 1]);
                var mouseTop = lastArrayEntry * 100 - self.canvas.offset().top + self.canvas.scrollTop() - 40;
                var mouseLeft = lastArrayEntry * 200 - self.canvas.offset().left + self.canvas.scrollLeft() - 60;
                var functionToolTip = '';
                var toolTipObject = _.find(tooltipList, function (toolTip) {
                    return toolTip.id === functionId;
                });
                if (toolTipObject !== undefined) {
                    functionToolTip = toolTipObject.text;
                }
                self.handleFunction(mouseTop, mouseLeft, true, functionId, functionName, functionToolTip);
            });

            _.forEach(self.configurationData.getSiddhiAppConfig().getPatternQueryList(), function (patternQuery) {

                var patternQueryId = patternQuery.getId();
                var patternQueryName = patternQuery.getQueryName();
                var array = patternQueryId.split("_");
                var lastArrayEntry = parseInt(array[array.length - 1]);
                var mouseTop = lastArrayEntry * 100 - self.canvas.offset().top + self.canvas.scrollTop() - 40;
                var mouseLeft = lastArrayEntry * 200 - self.canvas.offset().left + self.canvas.scrollLeft() - 60;
                var patternQueryToolTip = '';
                var toolTipObject = _.find(tooltipList, function (toolTip) {
                    return toolTip.id === patternQueryId;
                });
                if (toolTipObject !== undefined) {
                    patternQueryToolTip = toolTipObject.text;
                }
                self.handlePatternQuery(mouseTop, mouseLeft, true, patternQueryName, patternQueryId,
                    patternQueryToolTip);
            });

            _.forEach(self.configurationData.getSiddhiAppConfig().getSequenceQueryList(), function (sequenceQuery) {

                var sequenceQueryId = sequenceQuery.getId();
                var sequenceQueryName = sequenceQuery.getQueryName();
                var array = sequenceQueryId.split("_");
                var lastArrayEntry = parseInt(array[array.length - 1]);
                var mouseTop = lastArrayEntry * 100 - self.canvas.offset().top + self.canvas.scrollTop() - 40;
                var mouseLeft = lastArrayEntry * 200 - self.canvas.offset().left + self.canvas.scrollLeft() - 60;
                var sequenceQueryToolTip = '';
                var toolTipObject = _.find(tooltipList, function (toolTip) {
                    return toolTip.id === sequenceQueryId;
                });
                if (toolTipObject !== undefined) {
                    sequenceQueryToolTip = toolTipObject.text;
                }
                self.handleSequenceQuery(mouseTop, mouseLeft, true, sequenceQueryName, sequenceQueryId,
                    sequenceQueryToolTip);
            });

            _.forEach(self.configurationData.getSiddhiAppConfig().getWindowFilterProjectionQueryList(),
                function (windowFilterProjectionQuery) {
                    var queryId = windowFilterProjectionQuery.getId();
                    var queryName = windowFilterProjectionQuery.getQueryName();
                    var querySubType = windowFilterProjectionQuery.getQueryInput().getType();

                    var queryType;
                    if (querySubType === 'PROJECTION') {
                        queryType = constants.PROJECTION;
                    } else if (querySubType === 'FILTER') {
                        queryType = constants.FILTER;
                    } else if (querySubType === 'WINDOW') {
                        queryType = constants.WINDOW_QUERY;
                    } else if (querySubType === 'FUNCTION') {
                        queryType = constants.FUNCTION_QUERY;
                    }

                    var array = queryId.split("_");
                    var lastArrayEntry = parseInt(array[array.length - 1]);
                    var mouseTop = lastArrayEntry * 100 - self.canvas.offset().top + self.canvas.scrollTop() - 40;
                    var mouseLeft = lastArrayEntry * 200 - self.canvas.offset().left + self.canvas.scrollLeft() - 60;
                    var queryToolTip = '';
                    var toolTipObject = _.find(tooltipList, function (toolTip) {
                        return toolTip.id === queryId;
                    });
                    if (toolTipObject !== undefined) {
                        queryToolTip = toolTipObject.text;
                    }
                    self.handleWindowFilterProjectionQuery(queryType, mouseTop, mouseLeft, true, queryName, queryId,
                        queryToolTip);
                });

            _.forEach(self.configurationData.getSiddhiAppConfig().getJoinQueryList(), function (joinQuery) {

                var joinQueryId = joinQuery.getId();
                var joinQueryName = joinQuery.getQueryName();
                var array = joinQueryId.split("_");
                var lastArrayEntry = parseInt(array[array.length - 1]);
                var mouseTop = lastArrayEntry * 100 - self.canvas.offset().top + self.canvas.scrollTop() - 40;
                var mouseLeft = lastArrayEntry * 200 - self.canvas.offset().left + self.canvas.scrollLeft() - 60;
                var joinQueryToolTip = '';
                var toolTipObject = _.find(tooltipList, function (toolTip) {
                    return toolTip.id === joinQueryId;
                });
                if (toolTipObject !== undefined) {
                    joinQueryToolTip = toolTipObject.text;
                }
                self.handleJoinQuery(mouseTop, mouseLeft, true, joinQueryName, joinQueryId, joinQueryToolTip);
            });

            _.forEach(self.configurationData.getSiddhiAppConfig().getPartitionList(), function (partition) {

                var partitionId = partition.getId();
                var array = partitionId.split("_");
                var lastArrayEntry = parseInt(array[array.length - 1]);
                var mouseTop = lastArrayEntry * 100 - self.canvas.offset().top + self.canvas.scrollTop() - 40;
                var mouseLeft = lastArrayEntry * 200 - self.canvas.offset().left + self.canvas.scrollLeft() - 60;
                var partitionToolTip = '';
                var toolTipObject = _.find(tooltipList, function (toolTip) {
                    return toolTip.id === partitionId;
                });
                if (toolTipObject !== undefined) {
                    partitionToolTip = toolTipObject.text;
                }
                self.handlePartition(mouseTop, mouseLeft, true, partitionId, partitionToolTip);

                var jsPlumbPartitionGroup = self.jsPlumbInstance.getGroup(partitionId);

                _.forEach(partition.getStreamList(), function (stream) {
                    var streamId = stream.getId();
                    var streamName = stream.getName();
                    var array = streamId.split("_");
                    var lastArrayEntry = parseInt(array[array.length - 1]);
                    var mouseTop = lastArrayEntry * 100 - self.canvas.offset().top + self.canvas.scrollTop() - 40;
                    var mouseLeft = lastArrayEntry * 200 - self.canvas.offset().left + self.canvas.scrollLeft() - 60;
                    var streamToolTip = '';
                    var toolTipObject = _.find(tooltipList, function (toolTip) {
                        return toolTip.id === streamId;
                    });
                    if (toolTipObject !== undefined) {
                        streamToolTip = toolTipObject.text;
                    }
                    self.handleStream(mouseTop, mouseLeft, true, streamId, streamName, streamToolTip, stream);

                    var streamElement = $('#' + streamId)[0];
                    self.jsPlumbInstance.addToGroup(jsPlumbPartitionGroup, streamElement);
                });

                _.forEach(partition.getPatternQueryList(), function (patternQuery) {

                    var patternQueryId = patternQuery.getId();
                    var patternQueryName = patternQuery.getQueryName();
                    var array = patternQueryId.split("_");
                    var lastArrayEntry = parseInt(array[array.length - 1]);
                    var mouseTop = lastArrayEntry * 100 - self.canvas.offset().top + self.canvas.scrollTop() - 40;
                    var mouseLeft = lastArrayEntry * 200 - self.canvas.offset().left + self.canvas.scrollLeft() - 60;
                    var patternQueryToolTip = '';
                    var toolTipObject = _.find(tooltipList, function (toolTip) {
                        return toolTip.id === patternQueryId;
                    });
                    if (toolTipObject !== undefined) {
                        patternQueryToolTip = toolTipObject.text;
                    }
                    self.handlePatternQuery(mouseTop, mouseLeft, true, patternQueryName, patternQueryId,
                        patternQueryToolTip);

                    var patternElement = $('#' + patternQueryId)[0];
                    self.jsPlumbInstance.addToGroup(jsPlumbPartitionGroup, patternElement);
                });

                _.forEach(partition.getSequenceQueryList(), function (sequenceQuery) {

                    var sequenceQueryId = sequenceQuery.getId();
                    var sequenceQueryName = sequenceQuery.getQueryName();
                    var array = sequenceQueryId.split("_");
                    var lastArrayEntry = parseInt(array[array.length - 1]);
                    var mouseTop = lastArrayEntry * 100 - self.canvas.offset().top + self.canvas.scrollTop() - 40;
                    var mouseLeft = lastArrayEntry * 200 - self.canvas.offset().left + self.canvas.scrollLeft() - 60;
                    var sequenceQueryToolTip = '';
                    var toolTipObject = _.find(tooltipList, function (toolTip) {
                        return toolTip.id === sequenceQueryId;
                    });
                    if (toolTipObject !== undefined) {
                        sequenceQueryToolTip = toolTipObject.text;
                    }
                    self.handleSequenceQuery(mouseTop, mouseLeft, true, sequenceQueryName, sequenceQueryId,
                        sequenceQueryToolTip);

                    var sequenceElement = $('#' + sequenceQueryId)[0];
                    self.jsPlumbInstance.addToGroup(jsPlumbPartitionGroup, sequenceElement);
                });

                _.forEach(partition.getWindowFilterProjectionQueryList(),
                    function (windowFilterProjectionQuery) {
                        var queryId = windowFilterProjectionQuery.getId();
                        var queryName = windowFilterProjectionQuery.getQueryName();
                        var querySubType = windowFilterProjectionQuery.getQueryInput().getType();

                        var queryType;
                        if (querySubType === 'PROJECTION') {
                            queryType = constants.PROJECTION;
                        } else if (querySubType === 'FILTER') {
                            queryType = constants.FILTER;
                        } else if (querySubType === 'WINDOW') {
                            queryType = constants.WINDOW_QUERY;
                        } else if (querySubType === 'FUNCTION') {
                            queryType = constants.FUNCTION_QUERY;
                        }

                        var array = queryId.split("_");
                        var lastArrayEntry = parseInt(array[array.length - 1]);
                        var mouseTop
                            = lastArrayEntry * 100 - self.canvas.offset().top + self.canvas.scrollTop() - 40;
                        var mouseLeft
                            = lastArrayEntry * 200 - self.canvas.offset().left + self.canvas.scrollLeft() - 60;
                        var queryToolTip = '';
                        var toolTipObject = _.find(tooltipList, function (toolTip) {
                            return toolTip.id === queryId;
                        });
                        if (toolTipObject !== undefined) {
                            queryToolTip = toolTipObject.text;
                        }
                        self.handleWindowFilterProjectionQuery(
                            queryType, mouseTop, mouseLeft, true, queryName, queryId, queryToolTip);

                        var queryElement = $('#' + queryId)[0];
                        self.jsPlumbInstance.addToGroup(jsPlumbPartitionGroup, queryElement);
                    });

                _.forEach(partition.getJoinQueryList(), function (joinQuery) {

                    var joinQueryId = joinQuery.getId();
                    var joinQueryName = joinQuery.getQueryName();
                    var array = joinQueryId.split("_");
                    var lastArrayEntry = parseInt(array[array.length - 1]);
                    var mouseTop = lastArrayEntry * 100 - self.canvas.offset().top + self.canvas.scrollTop() - 40;
                    var mouseLeft = lastArrayEntry * 200 - self.canvas.offset().left + self.canvas.scrollLeft() - 60;
                    var joinQueryToolTip = '';
                    var toolTipObject = _.find(tooltipList, function (toolTip) {
                        return toolTip.id === joinQueryId;
                    });
                    if (toolTipObject !== undefined) {
                        joinQueryToolTip = toolTipObject.text;
                    }
                    self.handleJoinQuery(mouseTop, mouseLeft, true, joinQueryName, joinQueryId, joinQueryToolTip);

                    var joinElement = $('#' + joinQueryId)[0];
                    self.jsPlumbInstance.addToGroup(jsPlumbPartitionGroup, joinElement);
                });
            });

            _.forEach(self.configurationData.edgeList, function (edge) {
                var targetId;
                var sourceId;
                var paintStyle = {
                    strokeWidth: 2,
                    stroke: '#424242',
                    outlineStroke: "transparent",
                    outlineWidth: "3"
                };

                if (edge.getChildType() === 'PARTITION') {
                    targetId = edge.getChildId();
                    sourceId = edge.getParentId() + '-out';
                } else if (edge.getParentType() === 'PARTITION') {
                    targetId = edge.getChildId() + '-in';
                    sourceId = edge.getParentId();
                } else if (edge.getParentType() === 'SINK' && edge.getChildType() === 'SOURCE') {
                    targetId = edge.getChildId() + '-in';
                    sourceId = edge.getParentId() + '-out';
                    paintStyle = {
                        strokeWidth: 2, stroke: "#424242", dashstyle: "2 3", outlineStroke: "transparent",
                        outlineWidth: "3"
                    }
                } else {
                    // check if the edge is originating from a fault stream. if so get the corresponding event stream
                    // and draw the edge from the -err-out connector.
                    if (edge.isFromFaultStream()) {
                        sourceId = edge.getParentId() + '-err-out';
                        paintStyle = {
                            strokeWidth: 2, stroke: "#FF0000", dashstyle: "2 3", outlineStroke: "transparent",
                            outlineWidth: "3"
                        };
                    } else {
                        sourceId = edge.getParentId() + '-out';
                    }
                    targetId = edge.getChildId() + '-in';
                }

                self.jsPlumbInstance.connect({
                    source: sourceId,
                    target: targetId,
                    paintStyle: paintStyle
                });
            });

            // re-align the elements
            self.autoAlignElements();

            /*
            * In here we set a timeout because when drawing the graph jsplumb triggers a 'addMember' event (when adding
            * an element to the partition) an it takes some time to execute. So that we add a timeout and set the
            * isDesignViewContentChange to false.
            * */
            setTimeout(function () {
                // set the isDesignViewContentChanged to false
                self.configurationData.setIsDesignViewContentChanged(false);

                // set isStillDrawingGraph to false since the graph drawing is done
                self.configurationData.setIsStillDrawingGraph(false);
            }, 100);
        };

        /**
         * @function Auto align and center the diagram
         */
        DesignGrid.prototype.autoAlignElements = function () {
            var self = this;
            // Create a new graph instance
            var graph = new dagre.graphlib.Graph({compound: true});
            // Sets the graph to grow from left to right, and also to separate the distance between each node
            graph.setGraph({rankdir: 'LR', edgesep: 10, ranksep: 100, nodesep: 50});
            // This sets the default edge label to `null` as edges/arrows in the design view will
            // never have any labels/names to display on the screen
            graph.setDefaultEdgeLabel(function () {
                return {};
            });

            // Obtain all the draggable UI elements and add them into the nodes[] array
            var currentTabElement = document.getElementById(self.currentTabId);
            var nodes = [];
            Array.prototype.push.apply(nodes, currentTabElement.getElementsByClassName(constants.SOURCE));
            Array.prototype.push.apply(nodes, currentTabElement.getElementsByClassName(constants.SINK));
            Array.prototype.push.apply(nodes, currentTabElement.getElementsByClassName(constants.STREAM));
            Array.prototype.push.apply(nodes, currentTabElement.getElementsByClassName(constants.TABLE));
            Array.prototype.push.apply(nodes, currentTabElement.getElementsByClassName(constants.WINDOW));
            Array.prototype.push.apply(nodes, currentTabElement.getElementsByClassName(constants.TRIGGER));
            Array.prototype.push.apply(nodes, currentTabElement.getElementsByClassName(constants.AGGREGATION));
            Array.prototype.push.apply(nodes, currentTabElement.getElementsByClassName(constants.FUNCTION));
            Array.prototype.push.apply(nodes, currentTabElement.getElementsByClassName(constants.PROJECTION));
            Array.prototype.push.apply(nodes, currentTabElement.getElementsByClassName(constants.FILTER));
            Array.prototype.push.apply(nodes, currentTabElement.getElementsByClassName(constants.WINDOW_QUERY));
            Array.prototype.push.apply(nodes, currentTabElement.getElementsByClassName(constants.FUNCTION_QUERY));
            Array.prototype.push.apply(nodes, currentTabElement.getElementsByClassName(constants.JOIN));
            Array.prototype.push.apply(nodes, currentTabElement.getElementsByClassName(constants.PATTERN));
            Array.prototype.push.apply(nodes, currentTabElement.getElementsByClassName(constants.SEQUENCE));
            Array.prototype.push.apply(nodes, currentTabElement.getElementsByClassName(constants.PARTITION));

            // Create an empty JSON to store information of the given graph's nodes, edges and groups.
            var graphJSON = [];
            graphJSON.nodes = [];
            graphJSON.edges = [];
            graphJSON.groups = [];

            // For every node object in the nodes[] array
            var i = 0;
            nodes.forEach(function (node) {
                // Add each node to the dagre graph object
                graph.setNode(node.id, {width: node.offsetWidth, height: node.offsetHeight});
                // Add each node information to the graphJSON object
                graphJSON.nodes[i] = {
                    id: node.id,
                    width: node.offsetWidth,
                    height: node.offsetHeight
                };
                i++;
            });

            // For every edge in the jsplumb instance
            i = 0;
            var edges = self.jsPlumbInstance.getAllConnections();
            // Get the edge information and add it too graphJSON.edges[] array
            // Note - This loop is used to exclude the edges between nodes and partition connections
            edges.forEach(function (edge) {
                var source;
                var target;
                var sourceId;
                var targetId;
                // Get the current edge's parent and child Id's
                var parent = edge.sourceId;
                var child = edge.targetId;
                // If the current edge's parent is a partition connection
                if (parent.includes('_pc')) {
                    // Loop through the edges again
                    edges.forEach(function (value) {
                        // Get the inner loops edge (value) targetId as child
                        child = value.targetId;
                        // If child is a partition connection
                        if (child.includes('_pc')) {
                            // If the parent partition connection Id is equal to the child partition connection Id
                            if (parent === child) {
                                // Link inner edge's (value) sourceId with the outer edge's target Id as a single edge
                                source = value.sourceId;
                                target = edge.targetId;
                                sourceId = source.substr(0, source.indexOf('-'));
                                targetId = target.substr(0, target.indexOf('-'));
                            }
                        }
                    });
                } else if (!child.includes('_pc')) {
                    // If the child of the current edge is *not* a partition connection
                    source = edge.sourceId;
                    target = edge.targetId;
                    sourceId = source.substr(0, source.indexOf('-'));
                    targetId = target.substr(0, target.indexOf('-'));
                }
                // Set the sourceId and targetId to graphJSON if they are not undefined
                if (sourceId !== undefined && targetId !== undefined) {
                    graphJSON.edges[i] = {
                        parent: sourceId,
                        child: targetId
                    };
                    i++;
                }

            });
            // Once the needed edge information has been obtained and added to the graphJSON variable
            // then the edges can be set to the dagre graph variable.
            graphJSON.edges.forEach(function (edge) {
                graph.setEdge(edge.parent, edge.child);
            });

            // For every group/partition element
            i = 0;
            var groups = [];
            Array.prototype.push.apply(groups, currentTabElement.getElementsByClassName(constants.PARTITION));
            groups.forEach(function (partition) {
                // Add the group information to the graphJSON object
                graphJSON.groups[i] = {
                    id: null,
                    children: []
                };
                graphJSON.groups[i].id = partition.id;
                // Identify the children in each group/partition element
                var c = 0;
                var children = partition.childNodes;
                children.forEach(function (child) {
                    // If the children is of the following types, then only can they be considered as a child
                    // of the group
                    var className = child.className;
                    if (className.includes(constants.STREAM) || className.includes(constants.PROJECTION) ||
                        className.includes(constants.FILTER) || className.includes(constants.WINDOW_QUERY) ||
                        className.includes(constants.FUNCTION_QUERY) || className.includes(constants.JOIN) ||
                        className.includes(constants.PATTERN) || className.includes(constants.SEQUENCE)) {
                        // Set the child to it's respective group in the dagre graph object
                        graph.setParent(child.id, partition.id);
                        // Add the child information of each group to the graphJSON object
                        graphJSON.groups[i].children[c] = child.id;
                        c++;
                    }
                });

                i++;
            });

            // This command tells dagre to calculate and finalize the final layout of how the
            // nodes in the graph should be placed
            dagre.layout(graph);

            // Set the default minimum and maximum coordinates to zero
            var minimumCoordinate = {x: 0, y: 0};
            var maximumCoordinate = {x: 0, y: 0};
            // Traverse through every node and find the minimum and maximum x & y coordinates
            // The minimum and maximum x & y coordinates have to be found to obtain the size of the graph
            graph.nodes().forEach(function (nodeId) {
                // Get the instance of the dagre node of 'nodeId'
                var node = graph.node(nodeId);
                // Get the minimum x & y coordinates of the current node
                var minX = node.x - (node.width / 2);
                var minY = node.y - (node.height / 2);
                // Get the maximum x & y coordinates of the current node
                var maxX = node.x + (node.width / 2);
                var maxY = node.y + (node.height / 2);
                // Find the minimum and maximum 'x' coordinates from all nodes
                if (maxX > maximumCoordinate.x || maximumCoordinate.x === 0) {
                    maximumCoordinate.x = maxX;
                }
                if (minX < minimumCoordinate.x || minimumCoordinate.x === 0) {
                    minimumCoordinate.x = minX;
                }
                // Find the minimum and maximum 'y' coordinates from all the nodes
                if (maxY > maximumCoordinate.y || maximumCoordinate.y === 0) {
                    maximumCoordinate.y = maxY;
                }
                if (minY < minimumCoordinate.y || minimumCoordinate.y === 0) {
                    minimumCoordinate.y = minY;
                }
            });
            // Obtain the width and the height of the current design-grid instance
            var gridWidth = this.designGridContainer.width();
            var gridHeight = this.designGridContainer.height();
            // The difference in the largest and smallest 'x' coordinates gives the graph width
            var graphWidth = maximumCoordinate.x - minimumCoordinate.x;
            // The difference in the largest and smallest 'y' coordinates gives the graph height
            var graphHeight = maximumCoordinate.y - minimumCoordinate.y;
            // Set the centerLeft and centerTop coordinates to default 20
            // NOTE - The 'centerLeft' and 'centerTop' variables are the values that have to be added
            // to the final 'left' and 'top' CSS positions of the graph to align the graph to the
            // center of the design-grid
            var centerLeft = 20;
            var centerTop = 20;
            if (gridWidth > graphWidth) {
                centerLeft = (gridWidth - graphWidth) / 2;
            }
            if (gridHeight > graphHeight) {
                centerTop = (gridHeight - graphHeight) / 2;
            }

            // Re-align the elements in the grid based on the graph layout given by dagre
            graph.nodes().forEach(function (nodeId) {
                // Get a dagre instance of the node of `nodeId`
                var node = graph.node(nodeId);
                // Get a JQuery instance of the node of `nodeId`
                var $node = $("#" + nodeId);
                // Identify if the node is in a partiton or not using the information
                // in the graphJSON object
                var isInPartition = false;
                var partitionId = -1;
                graphJSON.groups.forEach(function (group) {
                    group.children.forEach(function (child) {
                        if (nodeId === child) {
                            isInPartition = true;
                            partitionId = group.id;
                        }
                    });
                });
                // Note that dagre defines the left(x) & top(y) positions from the center of the element
                // This has to be converted to the actual left and top position of a JQuery element
                if (isInPartition) {
                    // If the current node is in a partition, then that node must be added relative to the position
                    // of it's parent partition
                    var partitionNode = graph.node(partitionId);
                    // Identify the left and top value
                    var partitionNodeLeft = partitionNode.x - (partitionNode.width / 2) + centerLeft;
                    var partitionNodeTop = partitionNode.y - (partitionNode.height / 2) + centerTop;
                    // Identify the node's left and top position relative to it's partition's top and left position
                    var left = node.x - (node.width / 2) + centerLeft - partitionNodeLeft;
                    var top = node.y - (node.height / 2) + centerTop - partitionNodeTop;
                    // Set the inner node's left and top position
                    $node.css("left", left + "px");
                    $node.css("top", top + "px");
                } else {
                    // If the node is not in a partition then it's left and top positions are obtained relative to
                    // the entire grid
                    var left = node.x - (node.width / 2) + centerLeft;
                    var top = node.y - (node.height / 2) + centerTop;
                    // Set the node's left and top positions
                    $node.css("left", left + "px");
                    $node.css("top", top + "px");
                }
                // Resize the node with the new width and height defined by dagre
                // The node size can only change for partition nodes
                $node.css("width", node.width + "px");
                $node.css("height", node.height + "px");
            });

            // Redraw the edges in jsplumb
            self.jsPlumbInstance.repaintEverything();
        };

        DesignGrid.prototype.handleSourceAnnotation = function (mouseTop, mouseLeft, isCodeToDesignMode, sourceName,
                                                                sourceId, sourceToolTip) {
            var self = this;
            var elementId;
            if (isCodeToDesignMode !== undefined && !isCodeToDesignMode) {
                elementId = self.getNewAgentId();
            } else if (isCodeToDesignMode !== undefined && isCodeToDesignMode) {
                if (sourceId !== undefined) {
                    elementId = sourceId;
                    self.generateNextNewAgentId();
                } else {
                    console.log("sourceId parameter is undefined");
                }
            } else {
                console.log("isCodeToDesignMode parameter is undefined");
            }
            var newAgent = $('<div>').attr({
                'id': elementId,
                'tabindex': TAB_INDEX
            }).addClass(constants.SOURCE);
            if (isCodeToDesignMode) {
                newAgent.attr('title', sourceToolTip);
            }
            self.canvas.append(newAgent);
            // Drop the source element. Inside this a it generates the source definition form.
            self.dropElements.dropSource(newAgent, elementId, mouseTop, mouseLeft, isCodeToDesignMode, sourceName);
            self.configurationData.getSiddhiAppConfig()
                .setFinalElementCount(self.configurationData.getSiddhiAppConfig().getFinalElementCount() + 1);
            self.dropElements.registerElementEventListeners(newAgent);
            self.enableMultipleSelection();
        };

        DesignGrid.prototype.handleSinkAnnotation = function (mouseTop, mouseLeft, isCodeToDesignMode, sinkName,
                                                              sinkId, sinkToolTip) {
            var self = this;
            var elementId;
            if (isCodeToDesignMode !== undefined && !isCodeToDesignMode) {
                elementId = self.getNewAgentId();
            } else if (isCodeToDesignMode !== undefined && isCodeToDesignMode) {
                if (sinkId !== undefined) {
                    elementId = sinkId;
                    self.generateNextNewAgentId();
                } else {
                    console.log("sinkId parameter is undefined");
                }
            } else {
                console.log("isCodeToDesignMode parameter is undefined");
            }
            var newAgent = $('<div>').attr({
                'id': elementId,
                'tabindex': TAB_INDEX
            }).addClass(constants.SINK);
            if (isCodeToDesignMode) {
                newAgent.attr('title', sinkToolTip);
            }
            self.canvas.append(newAgent);
            // Drop the sink element. Inside this a it generates the sink definition form.
            self.dropElements.dropSink(newAgent, elementId, mouseTop, mouseLeft, isCodeToDesignMode, sinkName);
            self.configurationData.getSiddhiAppConfig()
                .setFinalElementCount(self.configurationData.getSiddhiAppConfig().getFinalElementCount() + 1);
            self.dropElements.registerElementEventListeners(newAgent);
            self.enableMultipleSelection();
        };

        DesignGrid.prototype.handleStream = function (mouseTop, mouseLeft, isCodeToDesignMode, streamId, streamName,
                                                      streamToolTip, stream) {
            var self = this;
            var elementId;
            if (isCodeToDesignMode !== undefined && !isCodeToDesignMode) {
                elementId = stream && stream.getId() ? stream.getId() : self.getNewAgentId();
            } else if (isCodeToDesignMode !== undefined && isCodeToDesignMode) {
                if (streamId !== undefined) {
                    elementId = streamId;
                    self.generateNextNewAgentId();
                } else {
                    console.log("streamId parameter is undefined");
                }
            } else {
                console.log("isCodeToDesignMode parameter is undefined");
            }

            var newAgent = $('<div>').attr({
                'id': elementId,
                'tabindex': TAB_INDEX
            }).addClass(constants.STREAM);

            var inFaultStreamCreationPath = false;

            // If this is a fault stream, hide it
            if (stream && stream.isFaultStream()) {
                newAgent.hide();
                inFaultStreamCreationPath = true;
            }

            if (isCodeToDesignMode) {
                newAgent.attr('title', streamToolTip);
            }
            self.canvas.append(newAgent);
            // Drop the stream element. Inside this a it generates the stream definition form.
            self.dropElements.dropStream(newAgent, elementId, mouseTop, mouseLeft, isCodeToDesignMode,
                false, streamName, stream && stream.hasFaultStream(), inFaultStreamCreationPath);
            self.configurationData.getSiddhiAppConfig()
                .setFinalElementCount(self.configurationData.getSiddhiAppConfig().getFinalElementCount() + 1);
            self.dropElements.registerElementEventListeners(newAgent);
            self.enableMultipleSelection();
        };

        DesignGrid.prototype.handleTable = function (mouseTop, mouseLeft, isCodeToDesignMode, tableId, tableName,
                                                     tableToolTip) {
            var self = this;
            var elementId;
            if (isCodeToDesignMode !== undefined && !isCodeToDesignMode) {
                elementId = self.getNewAgentId();
            } else if (isCodeToDesignMode !== undefined && isCodeToDesignMode) {
                if (tableId !== undefined) {
                    elementId = tableId;
                    self.generateNextNewAgentId();
                } else {
                    console.log("tableId parameter is undefined");
                }
            } else {
                console.log("isCodeToDesignMode parameter is undefined");
            }
            var newAgent = $('<div>').attr({
                'id': elementId,
                'tabindex': TAB_INDEX
            }).addClass(constants.TABLE);
            if (isCodeToDesignMode) {
                newAgent.attr('title', tableToolTip);
            }
            self.canvas.append(newAgent);
            // Drop the Table element. Inside this a it generates the table definition form.
            self.dropElements.dropTable(newAgent, elementId, mouseTop, mouseLeft, isCodeToDesignMode, tableName);
            self.configurationData.getSiddhiAppConfig()
                .setFinalElementCount(self.configurationData.getSiddhiAppConfig().getFinalElementCount() + 1);
            self.dropElements.registerElementEventListeners(newAgent);
            self.enableMultipleSelection();
        };

        DesignGrid.prototype.handleWindow = function (mouseTop, mouseLeft, isCodeToDesignMode, windowId, windowName,
                                                      windowToolTip) {
            var self = this;
            var elementId;
            if (isCodeToDesignMode !== undefined && !isCodeToDesignMode) {
                elementId = self.getNewAgentId();
            } else if (isCodeToDesignMode !== undefined && isCodeToDesignMode) {
                if (windowId !== undefined) {
                    elementId = windowId;
                    self.generateNextNewAgentId();
                } else {
                    console.log("windowId parameter is undefined");
                }
            } else {
                console.log("isCodeToDesignMode parameter is undefined");
            }
            var newAgent = $('<div>').attr({
                'id': elementId,
                'tabindex': TAB_INDEX
            }).addClass(constants.WINDOW);
            if (isCodeToDesignMode) {
                newAgent.attr('title', windowToolTip);
            }
            self.canvas.append(newAgent);
            // Drop the Table element. Inside this a it generates the table definition form.
            self.dropElements.dropWindow(newAgent, elementId, mouseTop, mouseLeft, isCodeToDesignMode, windowName);
            self.configurationData.getSiddhiAppConfig()
                .setFinalElementCount(self.configurationData.getSiddhiAppConfig().getFinalElementCount() + 1);
            self.dropElements.registerElementEventListeners(newAgent);
            self.enableMultipleSelection();
        };

        DesignGrid.prototype.handleTrigger = function (mouseTop, mouseLeft, isCodeToDesignMode, triggerId,
                                                       triggerName, triggerToolTip) {
            var self = this;
            var elementId;
            if (isCodeToDesignMode !== undefined && !isCodeToDesignMode) {
                elementId = self.getNewAgentId();
            } else if (isCodeToDesignMode !== undefined && isCodeToDesignMode) {
                if (triggerId !== undefined) {
                    elementId = triggerId;
                    self.generateNextNewAgentId();
                } else {
                    console.log("triggerId parameter is undefined");
                }
            } else {
                console.log("isCodeToDesignMode parameter is undefined");
            }
            var newAgent = $('<div>').attr({
                'id': elementId,
                'tabindex': TAB_INDEX
            }).addClass(constants.TRIGGER);
            if (isCodeToDesignMode) {
                newAgent.attr('title', triggerToolTip);
            }
            self.canvas.append(newAgent);
            // Drop the Trigger element. Inside this a it generates the trigger definition form.
            self.dropElements.dropTrigger(newAgent, elementId, mouseTop, mouseLeft, isCodeToDesignMode, triggerName);
            self.configurationData.getSiddhiAppConfig()
                .setFinalElementCount(self.configurationData.getSiddhiAppConfig().getFinalElementCount() + 1);
            self.dropElements.registerElementEventListeners(newAgent);
            self.enableMultipleSelection();
        };

        DesignGrid.prototype.handleAggregation = function (mouseTop, mouseLeft, isCodeToDesignMode, aggregationId,
                                                           aggregationName, aggregationToolTip) {
            var self = this;
            var elementId;
            if (isCodeToDesignMode !== undefined && !isCodeToDesignMode) {
                elementId = self.getNewAgentId();
            } else if (isCodeToDesignMode !== undefined && isCodeToDesignMode) {
                if (aggregationId !== undefined) {
                    elementId = aggregationId;
                    self.generateNextNewAgentId();
                } else {
                    console.log("aggregationId parameter is undefined");
                }
            } else {
                console.log("isCodeToDesignMode parameter is undefined");
            }
            var newAgent = $('<div>').attr({
                'id': elementId,
                'tabindex': TAB_INDEX
            }).addClass(constants.AGGREGATION);
            if (isCodeToDesignMode) {
                newAgent.attr('title', aggregationToolTip);
            }
            self.canvas.append(newAgent);
            // Drop the Aggregation element. Inside this a it generates the aggregation definition form.
            self.dropElements.dropAggregation(newAgent, elementId, mouseTop, mouseLeft, isCodeToDesignMode,
                aggregationName);
            self.configurationData.getSiddhiAppConfig()
                .setFinalElementCount(self.configurationData.getSiddhiAppConfig().getFinalElementCount() + 1);
            self.dropElements.registerElementEventListeners(newAgent);
            self.enableMultipleSelection();
        };

        DesignGrid.prototype.handleFunction = function (mouseTop, mouseLeft, isCodeToDesignMode, functionId,
                                                        functionName, functionToolTip) {
            var self = this;
            var elementId;
            if (isCodeToDesignMode !== undefined && !isCodeToDesignMode) {
                elementId = self.getNewAgentId();
            } else if (isCodeToDesignMode !== undefined && isCodeToDesignMode) {
                if (functionId !== undefined) {
                    elementId = functionId;
                    self.generateNextNewAgentId();
                } else {
                    console.log("functionId parameter is undefined");
                }
            } else {
                console.log("isCodeToDesignMode parameter is undefined");
            }
            var newAgent = $('<div>').attr({
                'id': elementId,
                'tabindex': TAB_INDEX
            }).addClass(constants.FUNCTION);
            if (isCodeToDesignMode) {
                newAgent.attr('title', functionToolTip);
            }
            self.canvas.append(newAgent);
            // Drop the Function element. Inside this a it generates the function definition form.
            self.dropElements.dropFunction(newAgent, elementId, mouseTop, mouseLeft, isCodeToDesignMode, functionName);
            self.configurationData.getSiddhiAppConfig()
                .setFinalElementCount(self.configurationData.getSiddhiAppConfig().getFinalElementCount() + 1);
            self.dropElements.registerElementEventListeners(newAgent);
            self.enableMultipleSelection();
        };


        DesignGrid.prototype.handleWindowFilterProjectionQuery = function (type, mouseTop, mouseLeft,
                                                                           isCodeToDesignMode, queryName, queryId,
                                                                           queryToolTip) {
            var self = this;
            var elementId;
            if (isCodeToDesignMode !== undefined && !isCodeToDesignMode) {
                elementId = self.getNewAgentId();
            } else if (isCodeToDesignMode !== undefined && isCodeToDesignMode) {
                if (queryId !== undefined) {
                    elementId = queryId;
                    self.generateNextNewAgentId();
                } else {
                    console.log("queryId parameter is undefined");
                }
            } else {
                console.log("isCodeToDesignMode parameter is undefined");
            }
            var newAgent = $('<div>').attr({
                'id': elementId,
                'tabindex': TAB_INDEX
            }).addClass(type);
            if (isCodeToDesignMode) {
                newAgent.attr('title', queryToolTip);
            }
            self.canvas.append(newAgent);
            // Drop the element instantly since its projections will be set only when the user requires it
            self.dropElements.dropWindowFilterProjectionQuery(newAgent, elementId, type, mouseTop, mouseLeft, queryName,
                isCodeToDesignMode);
            self.configurationData.getSiddhiAppConfig()
                .setFinalElementCount(self.configurationData.getSiddhiAppConfig().getFinalElementCount() + 1);
            self.dropElements.registerElementEventListeners(newAgent);
            self.enableMultipleSelection();
        };

        DesignGrid.prototype.handleJoinQuery = function (mouseTop, mouseLeft, isCodeToDesignMode, joinQueryName,
                                                         joinQueryId, joinQueryToolTip) {
            var self = this;
            var elementId;
            if (isCodeToDesignMode !== undefined && !isCodeToDesignMode) {
                elementId = self.getNewAgentId();
            } else if (isCodeToDesignMode !== undefined && isCodeToDesignMode) {
                if (joinQueryId !== undefined) {
                    elementId = joinQueryId;
                    self.generateNextNewAgentId();
                } else {
                    console.log("queryId parameter is undefined");
                }
            } else {
                console.log("isCodeToDesignMode parameter is undefined");
            }
            var newAgent = $('<div>').attr({
                'id': elementId,
                'tabindex': TAB_INDEX
            }).addClass(constants.JOIN);
            if (isCodeToDesignMode) {
                newAgent.attr('title', joinQueryToolTip);
            }
            self.canvas.append(newAgent);
            // Drop the element instantly since its projections will be set only when the user requires it
            self.dropElements.dropJoinQuery(newAgent, elementId, mouseTop, mouseLeft, joinQueryName,
                isCodeToDesignMode);
            self.configurationData.getSiddhiAppConfig()
                .setFinalElementCount(self.configurationData.getSiddhiAppConfig().getFinalElementCount() + 1);
            self.dropElements.registerElementEventListeners(newAgent);
            self.enableMultipleSelection();
        };

        DesignGrid.prototype.handlePatternQuery = function (mouseTop, mouseLeft, isCodeToDesignMode, patternQueryName,
                                                            patternQueryId, patternQueryToolTip) {
            var self = this;
            var elementId;
            if (isCodeToDesignMode !== undefined && !isCodeToDesignMode) {
                elementId = self.getNewAgentId();
            } else if (isCodeToDesignMode !== undefined && isCodeToDesignMode) {
                if (patternQueryId !== undefined) {
                    elementId = patternQueryId;
                    self.generateNextNewAgentId();
                } else {
                    console.log("patternQueryId parameter is undefined");
                }
            } else {
                console.log("isCodeToDesignMode parameter is undefined");
            }
            var newAgent = $('<div>').attr({
                'id': elementId,
                'tabindex': TAB_INDEX
            }).addClass(constants.PATTERN);
            if (isCodeToDesignMode) {
                newAgent.attr('title', patternQueryToolTip);
            }
            self.canvas.append(newAgent);
            // Drop the element instantly since its projections will be set only when the user requires it
            self.dropElements.dropPatternQuery(newAgent, elementId, mouseTop, mouseLeft, isCodeToDesignMode,
                patternQueryName);
            self.configurationData.getSiddhiAppConfig()
                .setFinalElementCount(self.configurationData.getSiddhiAppConfig().getFinalElementCount() + 1);
            self.dropElements.registerElementEventListeners(newAgent);
            self.enableMultipleSelection();
        };

        DesignGrid.prototype.handleSequenceQuery = function (mouseTop, mouseLeft, isCodeToDesignMode, sequenceQueryName,
                                                             sequenceQueryId, sequenceQueryToolTip) {
            var self = this;
            var elementId;
            if (isCodeToDesignMode !== undefined && !isCodeToDesignMode) {
                elementId = self.getNewAgentId();
            } else if (isCodeToDesignMode !== undefined && isCodeToDesignMode) {
                if (sequenceQueryId !== undefined) {
                    elementId = sequenceQueryId;
                    self.generateNextNewAgentId();
                } else {
                    console.log("sequenceQueryId parameter is undefined");
                }
            } else {
                console.log("isCodeToDesignMode parameter is undefined");
            }
            var newAgent = $('<div>').attr({
                'id': elementId,
                'tabindex': TAB_INDEX
            }).addClass(constants.SEQUENCE);
            if (isCodeToDesignMode) {
                newAgent.attr('title', sequenceQueryToolTip);
            }
            self.canvas.append(newAgent);
            // Drop the element instantly since its projections will be set only when the user requires it
            self.dropElements.dropSequenceQuery(newAgent, elementId, mouseTop, mouseLeft, isCodeToDesignMode,
                sequenceQueryName);
            self.configurationData.getSiddhiAppConfig()
                .setFinalElementCount(self.configurationData.getSiddhiAppConfig().getFinalElementCount() + 1);
            self.dropElements.registerElementEventListeners(newAgent);
            self.enableMultipleSelection();
        };

        DesignGrid.prototype.handlePartition = function (mouseTop, mouseLeft, isCodeToDesignMode, partitionId,
                                                         partitionToolTip) {
            var self = this;
            var elementId;
            if (isCodeToDesignMode !== undefined && !isCodeToDesignMode) {
                elementId = self.getNewAgentId();
            } else if (isCodeToDesignMode !== undefined && isCodeToDesignMode) {
                if (partitionId !== undefined) {
                    elementId = partitionId;
                    self.generateNextNewAgentId();
                } else {
                    console.log("partitionId parameter is undefined");
                }
            } else {
                console.log("isCodeToDesignMode parameter is undefined");
            }
            var newAgent = $('<div>').attr({
                'id': elementId,
                'tabindex': TAB_INDEX
            }).addClass(constants.PARTITION);
            if (isCodeToDesignMode) {
                newAgent.attr('title', partitionToolTip);
            }
            self.canvas.append(newAgent);
            // Drop the element instantly since its projections will be set only when the user requires it
            self.dropElements.dropPartition(newAgent, elementId, mouseTop, mouseLeft, isCodeToDesignMode);
            self.configurationData.getSiddhiAppConfig()
                .setFinalElementCount(self.configurationData.getSiddhiAppConfig().getFinalElementCount() + 1);
            self.dropElements.registerElementEventListeners(newAgent);
            self.enableMultipleSelection();
        };

        DesignGrid.prototype.generateNextNewAgentId = function () {
            var newId = parseInt(this.newAgentId) + 1;
            this.newAgentId = "" + newId + "";
            return this.currentTabId + "_element_" + this.newAgentId;
        };

        DesignGrid.prototype.getNewAgentId = function () {
            var self = this;
            return self.generateNextNewAgentId();
        };

        DesignGrid.prototype.generateNextConnectionPointIdForPartition = function (partitionId) {
            var partitionElement = $('#' + partitionId);
            var partitionConnections = partitionElement.find('.' + constants.PARTITION_CONNECTION_POINT);
            var partitionIds = [];
            _.forEach(partitionConnections, function (connection) {
                partitionIds.push(parseInt((connection.id).slice(-1)));
            });

            var maxId = partitionIds.reduce(function (a, b) {
                return Math.max(a, b);
            });
            return partitionId + '_pc' + (maxId + 1);
        };

        DesignGrid.prototype.enableMultipleSelection = function () {
            var self = this;
            var selector = $('<div>').attr('id', constants.MULTI_SELECTOR + self.currentTabId).addClass(constants.SELECTOR);
            self.canvas.append(selector);
            new DragSelect({
                selectables: document.querySelectorAll('.jtk-draggable'),
                selector: document.getElementById(constants.MULTI_SELECTOR + self.currentTabId),
                area: document.getElementById('design-grid-container-' + self.currentTabId),
                multiSelectKeys: ['ctrlKey', 'shiftKey'],
                onElementSelect: function (element) {
                    if (!self.selectedObjects.includes(element, 0)) {
                        self.jsPlumbInstance.addToDragSelection(element);
                        self.addSelectedElements(element);
                        $(element).focus();
                        $(element).addClass('selected-container');
                        self.selectedObjects.push(element);
                    }
                },
                onElementUnselect: function (element) {
                    self.jsPlumbInstance.removeFromDragSelection(element);
                    self.removeFromSelectedElements(element);
                    for (var i = 0; i < self.selectedObjects.length; i++) {
                        if (self.selectedObjects[i] == element) {
                            self.selectedObjects.splice(i, 1);
                        }
                    }
                    $(element).removeClass('selected-container focused-container');
                    self.selectedObjects = [];
                }
            });
        };

        /**
         * Generate tooltips for all the elements
         *
         * @param designViewJSON siddhiAppConfig
         */
        DesignGrid.prototype.getTooltips = function (designViewJSON) {
            var self = this;
            var result = {};
            self.tooltipsURL = window.location.protocol + "//" + window.location.host + "/editor/tooltips";
            $.ajax({
                type: "POST",
                url: self.tooltipsURL,
                data: self.options.application.utils.base64EncodeUnicode(designViewJSON),
                async: false,
                success: function (response) {
                    result = {status: "success", tooltipList: response};
                },
                error: function (error) {
                    if (error.responseText) {
                        result = {status: "fail", errorMessage: error.responseText};
                    } else {
                        result = {status: "fail", errorMessage: "Error Occurred while processing your request"};
                    }
                }
            });
            return result;
        };

        return DesignGrid;
    });
