/*globals define, _, WebGMEGlobal*/
/*jshint browser: true */
/**
 * @author rkereskenyi / https://github.com/rkereskenyi
 */

define(['js/logger',
    'js/util',
    'js/Constants',
    'js/Utils/GMEConcepts',
    'js/NodePropertyNames',
    'js/RegistryKeys',
    'js/Widgets/DiagramDesigner/DiagramDesignerWidget.Constants',
    './MetaEditorControl.DiagramDesignerWidgetEventHandlers',
    './MetaRelations',
    './MetaEditorConstants',
    'js/Utils/PreferencesHelper'
], function (Logger,
             util,
             CONSTANTS,
             GMEConcepts,
             nodePropertyNames,
             REGISTRY_KEYS,
             DiagramDesignerWidgetConstants,
             MetaEditorControlDiagramDesignerWidgetEventHandlers,
             MetaRelations,
             MetaEditorConstants,
             PreferencesHelper) {

    'use strict';

    var MetaEditorControl,
        META_DECORATOR = 'MetaDecorator',
        WIDGET_NAME = 'DiagramDesigner',
        META_RULES_CONTAINER_NODE_ID = MetaEditorConstants.META_ASPECT_CONTAINER_ID;

    MetaEditorControl = function (options) {
        var self = this;

        this.logger = options.logger || Logger.create(options.loggerName || 'gme:Panels:MetaEditor:MetaEditorControl',
                WebGMEGlobal.gmeConfig.client.log);

        this._client = options.client;

        //initialize core collections and variables
        this.diagramDesigner = options.widget;

        if (this._client === undefined) {
            this.logger.error('MetaEditorControl\'s client is not specified...');
            throw ('MetaEditorControl can not be created');
        }

        if (this.diagramDesigner === undefined) {
            this.logger.error('MetaEditorControl\'s DiagramDesigner is not specified...');
            throw ('MetaEditorControl can not be created');
        }

        //in METAEDITOR mode DRAG & COPY is not enabled
        this.diagramDesigner.enableDragCopy(false);

        this._metaAspectMemberPatterns = {};

        this._filteredOutConnTypes = [];
        this._filteredOutConnectionDescriptors = {};

        //local variable holding info about the currently opened node
        this.currentNodeInfo = {id: null, members: []};

        this._metaAspectMembersAll = [];
        this._metaAspectMembersPerSheet = {};
        this._metaAspectMembersCoordinatesGlobal = {};
        this._metaAspectMembersCoordinatesPerSheet = {};
        this._selectedMetaAspectSheetMembers = [];

        //set default connection type to containment
        this._setNewConnectionType(MetaRelations.META_RELATIONS.CONTAINMENT);

        this._initFilterPanel();

        //attach all the event handlers for event's coming from DiagramDesigner
        this.attachDiagramDesignerWidgetEventHandlers();

        //let the decorator-manager download the required decorator
        this._client.decoratorManager.download([META_DECORATOR], WIDGET_NAME, function () {
            self.logger.debug('MetaEditorControl ctor finished');

            //load meta container node
            //give the UI time to render first before start using it's features
            setTimeout(function () {
                self._loadMetaAspectContainerNode();
            }, 10);
        });
    };

    MetaEditorControl.prototype._loadMetaAspectContainerNode = function () {
        var self = this;

        this.metaAspectContainerNodeID = META_RULES_CONTAINER_NODE_ID;

        this.logger.debug('_loadMetaAspectContainerNode: "' + this.metaAspectContainerNodeID + '"');

        this._initializeSelectedSheet();

        //remove current territory patterns
        if (this._territoryId) {
            this._client.removeUI(this._territoryId);
        }

        //put new node's info into territory rules
        this._selfPatterns = {};
        this._selfPatterns[this.metaAspectContainerNodeID] = {children: 0};

        //create and set territory
        this._territoryId = this._client.addUI(this, function (events) {
            self._eventCallback(events);
        });
        this._client.updateTerritory(this._territoryId, this._selfPatterns);
    };

    /**********************************************************/
    /*                    PUBLIC METHODS                      */
    /**********************************************************/
    MetaEditorControl.prototype._eventCallback = function (events) {
        var i = events ? events.length : 0,
            e;

        this.logger.debug('_eventCallback "' + i + '" items');

        this.diagramDesigner.beginUpdate();

        while (i--) {
            e = events[i];
            switch (e.etype) {
                case CONSTANTS.TERRITORY_EVENT_LOAD:
                    this._onLoad(e.eid);
                    break;
                case CONSTANTS.TERRITORY_EVENT_UPDATE:
                    this._onUpdate(e.eid);
                    break;
                case CONSTANTS.TERRITORY_EVENT_UNLOAD:
                    this._onUnload(e.eid);
                    break;
                default:
                    break;
            }
        }

        this.diagramDesigner.endUpdate();

        this.diagramDesigner.hideProgressbar();

        this.logger.debug('_eventCallback "' + events.length + '" items - DONE');
    };

    //might not be the best approach
    MetaEditorControl.prototype.destroy = function () {
        this._detachClientEventListeners();
        this._client.removeUI(this._territoryId);
        this._client.removeUI(this._metaAspectMembersTerritoryId);
        this.diagramDesigner.clear();
    };


    /**********************************************************/
    /*                LOAD / UPDATE / UNLOAD HANDLER          */
    /**********************************************************/
    MetaEditorControl.prototype._onLoad = function (gmeID) {
        if (gmeID === this.metaAspectContainerNodeID) {
            this._processMetaAspectContainerNode();
        } else {
            this._processNodeLoad(gmeID);
        }
    };

    MetaEditorControl.prototype._onUpdate = function (gmeID) {
        if (gmeID === this.metaAspectContainerNodeID) {
            this._processMetaAspectContainerNode();
        } else {
            this._processNodeUpdate(gmeID);
        }
    };

    MetaEditorControl.prototype._onUnload = function (gmeID) {
        var self = this;

        if (gmeID === this.metaAspectContainerNodeID) {
            //the opened model has been deleted....
            //most probably a project / branch / whatever change
            this.logger.debug('The currently opened aspect has been deleted --- GMEID: "' +
                this.metaAspectContainerNodeID + '"');
            setTimeout(function () {
                self._loadMetaAspectContainerNode();
            }, 10);
        } else {
            this._processNodeUnload(gmeID);
        }
    };
    /**********************************************************/
    /*       END OF --- LOAD / UPDATE / UNLOAD HANDLER        */
    /**********************************************************/


    /**********************************************************/
    /*                CUSTOM BUTTON EVENT HANDLERS            */
    /**********************************************************/
    MetaEditorControl.prototype._printNodeData = function () {
        //TODO could be filled with meaningful info
    };
    /**********************************************************/
    /*       END OF --- CUSTOM BUTTON EVENT HANDLERS          */
    /**********************************************************/


    /***********************************************************/
    /*  PROCESS CURRENT NODE TO HANDLE ADDED / REMOVED ELEMENT */
    /***********************************************************/
    MetaEditorControl.prototype._processMetaAspectContainerNode = function () {
        var aspectNodeID = this.metaAspectContainerNodeID,
            aspectNode = this._client.getNode(aspectNodeID),
            len,
            diff,
            objDesc,
            componentID,
            gmeID,
            metaAspectSetMembers = aspectNode.getMemberIds(MetaEditorConstants.META_ASPECT_SET_NAME),
            territoryChanged = false,
            selectedSheetMembers,
            positionsUpdated;

        //this._metaAspectMembersAll contains all the currently known members of the meta aspect
        //update current member list
        this._metaAspectMembersAll = metaAspectSetMembers.slice(0);
        len = this._metaAspectMembersAll.length;
        this._metaAspectMembersCoordinatesGlobal = {};
        while (len--) {
            gmeID = this._metaAspectMembersAll[len];
            this._metaAspectMembersCoordinatesGlobal[gmeID] = aspectNode.getMemberRegistry(
                MetaEditorConstants.META_ASPECT_SET_NAME,
                gmeID,
                REGISTRY_KEYS.POSITION);
        }

        //setSelected sheet
        //this._selectedMetaAspectSet
        //process the sheets
        positionsUpdated = this._processMetaAspectSheetsRegistry();

        this.logger.debug('_metaAspectMembersAll: \n' + JSON.stringify(this._metaAspectMembersAll));
        this.logger.debug('_metaAspectMembersCoordinatesGlobal: \n' +
            JSON.stringify(this._metaAspectMembersCoordinatesGlobal));

        this.logger.debug('_metaAspectMembersPerSheet: \n' + JSON.stringify(this._metaAspectMembersPerSheet));
        this.logger.debug('_metaAspectMembersCoordinatesPerSheet: \n' +
            JSON.stringify(this._metaAspectMembersCoordinatesPerSheet));

        //check to see if the territory needs to be changed
        //the territory contains the nodes that are on the currently opened sheet
        //this._selectedMetaAspectSheetMembers
        selectedSheetMembers = this._metaAspectMembersPerSheet[this._selectedMetaAspectSet] || [];

        //check deleted nodes
        diff = _.difference(this._selectedMetaAspectSheetMembers, selectedSheetMembers);
        len = diff.length;
        while (len--) {
            delete this._metaAspectMemberPatterns[diff[len]];
            territoryChanged = true;
        }

        //check added nodes
        diff = _.difference(selectedSheetMembers, this._selectedMetaAspectSheetMembers);
        len = diff.length;
        while (len--) {
            this._metaAspectMemberPatterns[diff[len]] = {children: 0};
            territoryChanged = true;
        }

        //check all other nodes for position change
        //or any other change that could have happened (local registry modifications)
        //diff = positionsUpdated;//_.intersection(this._selectedMetaAspectSheetMembers, selectedSheetMembers);
        diff = _.intersection(this._selectedMetaAspectSheetMembers, selectedSheetMembers);
        len = diff.length;
        while (len--) {
            gmeID = diff[len];
            objDesc = {position: {x: 100, y: 100}};

            if (this._metaAspectMembersCoordinatesPerSheet[this._selectedMetaAspectSet][gmeID]) {
                objDesc.position.x = this._metaAspectMembersCoordinatesPerSheet[this._selectedMetaAspectSet][gmeID].x;
                objDesc.position.y = this._metaAspectMembersCoordinatesPerSheet[this._selectedMetaAspectSet][gmeID].y;
            }

            if (this._GMEID2ComponentID.hasOwnProperty(gmeID)) {
                componentID = this._GMEID2ComponentID[gmeID];
                this.diagramDesigner.updateDesignerItem(componentID, objDesc);
            }
        }

        this._selectedMetaAspectSheetMembers = selectedSheetMembers.slice(0);

        //there was change in the territory
        if (territoryChanged === true) {
            this._client.updateTerritory(this._metaAspectMembersTerritoryId, this._metaAspectMemberPatterns);
        }
    };
    /**********************************************************************/
    /*  END OF --- PROCESS CURRENT NODE TO HANDLE ADDED / REMOVED ELEMENT */
    /**********************************************************************/


    /**************************************************************************/
    /*  HANDLE OBJECT LOAD  --- DISPLAY IT WITH ALL THE POINTERS / SETS / ETC */
    /**************************************************************************/
    MetaEditorControl.prototype._processNodeLoad = function (gmeID) {
        var uiComponent,
            decClass,
            objDesc;

        //component loaded
        if (this._GMENodes.indexOf(gmeID) === -1) {
            //aspect's member has been loaded
            objDesc = {position: {x: 100, y: 100}};

            if (this._metaAspectMembersCoordinatesPerSheet[this._selectedMetaAspectSet][gmeID]) {
                objDesc.position.x = this._metaAspectMembersCoordinatesPerSheet[this._selectedMetaAspectSet][gmeID].x;
                objDesc.position.y = this._metaAspectMembersCoordinatesPerSheet[this._selectedMetaAspectSet][gmeID].y;
            }

            decClass = this._client.decoratorManager.getDecoratorForWidget(META_DECORATOR, WIDGET_NAME);

            objDesc.decoratorClass = decClass;
            objDesc.control = this;
            objDesc.metaInfo = {};
            objDesc.metaInfo[CONSTANTS.GME_ID] = gmeID;
            //each meta specific registry customization will be stored in the MetaContainer node's main META SET
            // (MetaEditorConstants.META_ASPECT_SET_NAME)
            objDesc.preferencesHelper = PreferencesHelper.getPreferences([{
                containerID: this.metaAspectContainerNodeID,
                setID: MetaEditorConstants.META_ASPECT_SET_NAME
            }]);

            uiComponent = this.diagramDesigner.createDesignerItem(objDesc);

            this._GMENodes.push(gmeID);
            this._GMEID2ComponentID[gmeID] = uiComponent.id;
            this._ComponentID2GMEID[uiComponent.id] = gmeID;

            //process new node to display containment / pointers / inheritance / sets as connections
            this._processNodeMetaContainment(gmeID);
            this._processNodeMetaPointers(gmeID, false);
            this._processNodeMetaInheritance(gmeID);
            this._processNodeMetaPointers(gmeID, true);

            //check all the waiting pointers (whose SRC/DST is already displayed and waiting for the DST/SRC to show up)
            //it might be this new node
            this._processConnectionWaitingList(gmeID);
        }
    };

    MetaEditorControl.prototype._processConnectionWaitingList = function (gmeID) {
        var len,
            gmeSrcID,
            gmeDstID,
            connType,
            connTexts,
            c = [];

        //check for possible endpoint as gmeID
        gmeDstID = gmeID;
        if (this._connectionWaitingListByDstGMEID && this._connectionWaitingListByDstGMEID.hasOwnProperty(gmeDstID)) {
            for (gmeSrcID in this._connectionWaitingListByDstGMEID[gmeDstID]) {
                if (this._connectionWaitingListByDstGMEID[gmeDstID].hasOwnProperty(gmeSrcID)) {
                    len = this._connectionWaitingListByDstGMEID[gmeDstID][gmeSrcID].length;
                    while (len--) {
                        connType = this._connectionWaitingListByDstGMEID[gmeDstID][gmeSrcID][len][0];
                        connTexts = this._connectionWaitingListByDstGMEID[gmeDstID][gmeSrcID][len][1];
                        c.push({
                            gmeSrcID: gmeSrcID,
                            gmeDstID: gmeDstID,
                            connType: connType,
                            connTexts: connTexts
                        });
                    }
                }
            }

            delete this._connectionWaitingListByDstGMEID[gmeDstID];
        }

        //check for possible source as gmeID
        gmeSrcID = gmeID;
        if (this._connectionWaitingListBySrcGMEID && this._connectionWaitingListBySrcGMEID.hasOwnProperty(gmeSrcID)) {
            for (gmeDstID in this._connectionWaitingListBySrcGMEID[gmeSrcID]) {
                if (this._connectionWaitingListBySrcGMEID[gmeSrcID].hasOwnProperty(gmeDstID)) {
                    len = this._connectionWaitingListBySrcGMEID[gmeSrcID][gmeDstID].length;
                    while (len--) {
                        connType = this._connectionWaitingListBySrcGMEID[gmeSrcID][gmeDstID][len][0];
                        connTexts = this._connectionWaitingListBySrcGMEID[gmeSrcID][gmeDstID][len][1];
                        c.push({
                            gmeSrcID: gmeSrcID,
                            gmeDstID: gmeDstID,
                            connType: connType,
                            connTexts: connTexts
                        });
                    }
                }
            }

            delete this._connectionWaitingListBySrcGMEID[gmeSrcID];
        }

        len = c.length;
        while (len--) {
            gmeSrcID = c[len].gmeSrcID;
            gmeDstID = c[len].gmeDstID;
            connType = c[len].connType;
            connTexts = c[len].connTexts;
            this._createConnection(gmeSrcID, gmeDstID, connType, connTexts);
        }
    };
    /**************************************************************************/
    /*  END OF --- HANDLE OBJECT LOAD DISPLAY IT WITH ALL THE POINTERS / ...  */
    /**************************************************************************/


    /****************************************************************************/
    /*  HANDLE OBJECT UNLOAD  --- DISPLAY IT WITH ALL THE POINTERS / SETS / ETC */
    /****************************************************************************/
    MetaEditorControl.prototype._processNodeUnload = function (gmeID) {
        var componentID,
            idx,
            len,
            otherEnd,
            pointerName,
            aConns,
            connectionID;

        if (this._GMEID2ComponentID.hasOwnProperty(gmeID)) {
            componentID = this._GMEID2ComponentID[gmeID];

            //gather all the information that is stored in this node's META

            //CONTAINMENT
            len = this._nodeMetaContainment[gmeID].targets.length;
            while (len--) {
                otherEnd = this._nodeMetaContainment[gmeID].targets[len];
                this._removeConnection(gmeID, otherEnd, MetaRelations.META_RELATIONS.CONTAINMENT);
            }

            //POINTERS
            len = this._nodeMetaPointers[gmeID].combinedNames.length;
            while (len--) {
                pointerName = this._nodeMetaPointers[gmeID].combinedNames[len];
                otherEnd = this._nodeMetaPointers[gmeID][pointerName].target;
                pointerName = this._nodeMetaPointers[gmeID][pointerName].name;
                this._removeConnection(gmeID, otherEnd, MetaRelations.META_RELATIONS.POINTER, pointerName);
            }

            //INHERITANCE
            if (this._nodeMetaInheritance[gmeID] && !_.isEmpty(this._nodeMetaInheritance[gmeID])) {
                this._removeConnection(this._nodeMetaInheritance[gmeID],
                    gmeID,
                    MetaRelations.META_RELATIONS.INHERITANCE);
            }

            //POINTER LISTS
            len = this._nodeMetaSets[gmeID].combinedNames.length;
            while (len--) {
                pointerName = this._nodeMetaSets[gmeID].combinedNames[len];
                otherEnd = this._nodeMetaSets[gmeID][pointerName].target;
                pointerName = this._nodeMetaSets[gmeID][pointerName].name;
                this._removeConnection(gmeID, otherEnd, MetaRelations.META_RELATIONS.SET, pointerName);
            }

            //finally delete the guy from the screen
            this.diagramDesigner.deleteComponent(componentID);

            delete this._ComponentID2GMEID[componentID];

            delete this._GMEID2ComponentID[gmeID];

            idx = this._GMENodes.indexOf(gmeID);
            this._GMENodes.splice(idx, 1);

            //check if there is any more connection present that's associated with this object
            //typically the connection end is this guy
            //if so, remove but save to savedList
            aConns = this._getAssociatedConnections(gmeID);
            len = aConns.src.length;
            while (len--) {
                connectionID = aConns.src[len];
                //save the connection to the waiting list, since the destination is still there
                this._saveConnectionToWaitingList(this._connectionListByID[connectionID].GMESrcId,
                    this._connectionListByID[connectionID].GMEDstId,
                    this._connectionListByID[connectionID].type,
                    this._connectionListByID[connectionID].connTexts);
                this._removeConnection(this._connectionListByID[connectionID].GMESrcId,
                    this._connectionListByID[connectionID].GMEDstId,
                    this._connectionListByID[connectionID].type);
            }

            len = aConns.dst.length;
            while (len--) {
                connectionID = aConns.dst[len];
                if (this._connectionListByID[connectionID]) {
                    //save the connection to the waiting list, since the destination is still there
                    this._saveConnectionToWaitingList(this._connectionListByID[connectionID].GMESrcId,
                        this._connectionListByID[connectionID].GMEDstId,
                        this._connectionListByID[connectionID].type,
                        this._connectionListByID[connectionID].connTexts);
                    this._removeConnection(this._connectionListByID[connectionID].GMESrcId,
                        this._connectionListByID[connectionID].GMEDstId,
                        this._connectionListByID[connectionID].type);
                }
            }

            //check the waiting list and remove any connection that was waiting and this end was present
            for (otherEnd in this._connectionWaitingListBySrcGMEID) {
                if (this._connectionWaitingListBySrcGMEID.hasOwnProperty(otherEnd)) {
                    delete this._connectionWaitingListBySrcGMEID[otherEnd][gmeID];

                    if (_.isEmpty(this._connectionWaitingListBySrcGMEID[otherEnd])) {
                        delete this._connectionWaitingListBySrcGMEID[otherEnd];
                    }
                }
            }

            for (otherEnd in this._connectionWaitingListByDstGMEID) {
                if (this._connectionWaitingListByDstGMEID.hasOwnProperty(otherEnd)) {
                    delete this._connectionWaitingListByDstGMEID[otherEnd][gmeID];

                    if (_.isEmpty(this._connectionWaitingListByDstGMEID[otherEnd])) {
                        delete this._connectionWaitingListByDstGMEID[otherEnd];
                    }
                }
            }

            //keep up accounting
            delete this._nodeMetaContainment[gmeID];
            delete this._nodeMetaPointers[gmeID];
            delete this._nodeMetaInheritance[gmeID];
            delete this._nodeMetaSets[gmeID];
        }
    };
    /****************************************************************************/
    /*                      END OF --- HANDLE OBJECT UNLOAD                     */
    /****************************************************************************/


    /****************************************************************************/
    /*  CREATE A SPECIFIC TYPE OF CONNECTION BETWEEN 2 GME OBJECTS              */
    /****************************************************************************/
    MetaEditorControl.prototype._createConnection = function (gmeSrcId, gmeDstId, connType, connTexts) {
        var connDesc,
            connComponent,
            metaInfo;
        //need to check if the src and dst objects are displayed or not
        //if YES, create connection
        //if NO, store information in a waiting queue

        if (this._GMENodes.indexOf(gmeSrcId) !== -1 && this._GMENodes.indexOf(gmeDstId) !== -1) {
            //source and destination is displayed

            if (this._filteredOutConnTypes.indexOf(connType) === -1) {
                //connection type is not filtered out    
                connDesc = {
                    srcObjId: this._GMEID2ComponentID[gmeSrcId],
                    srcSubCompId: undefined,
                    dstObjId: this._GMEID2ComponentID[gmeDstId],
                    dstSubCompId: undefined,
                    reconnectable: false,
                    name: '',
                    nameEdit: false
                };

                //set visual properties
                _.extend(connDesc, MetaRelations.getLineVisualDescriptor(connType));

                //fill out texts
                if (connTexts) {
                    _.extend(connDesc, connTexts);
                }

                connComponent = this.diagramDesigner.createConnection(connDesc);

                //set connection metaInfo and store connection type
                //the MetaDecorator uses this information when queried for connectionArea
                metaInfo = {};
                metaInfo[MetaRelations.CONNECTION_META_INFO.TYPE] = connType;
                connComponent.setMetaInfo(metaInfo);

                this._saveConnection(gmeSrcId, gmeDstId, connType, connComponent.id, connTexts);
            } else {
                //connection type is filtered out
                this._filteredOutConnectionDescriptors[connType].push([gmeSrcId, gmeDstId, connTexts]);
            }
        } else {
            //source or destination is not displayed, store it in a queue
            this._saveConnectionToWaitingList(gmeSrcId, gmeDstId, connType, connTexts);
        }
    };


    MetaEditorControl.prototype._saveConnectionToWaitingList = function (gmeSrcId, gmeDstId, connType, connTexts) {
        if (this._GMENodes.indexOf(gmeSrcId) !== -1 && this._GMENodes.indexOf(gmeDstId) === -1) {
            //#1 - the destination object is missing from the screen
            this._connectionWaitingListByDstGMEID[gmeDstId] = this._connectionWaitingListByDstGMEID[gmeDstId] || {};

            this._connectionWaitingListByDstGMEID[gmeDstId][gmeSrcId] =
                this._connectionWaitingListByDstGMEID[gmeDstId][gmeSrcId] || [];

            this._connectionWaitingListByDstGMEID[gmeDstId][gmeSrcId].push([connType, connTexts]);
        } else if (this._GMENodes.indexOf(gmeSrcId) === -1 && this._GMENodes.indexOf(gmeDstId) !== -1) {
            //#2 -  the source object is missing from the screen
            this._connectionWaitingListBySrcGMEID[gmeSrcId] = this._connectionWaitingListBySrcGMEID[gmeSrcId] || {};
            this._connectionWaitingListBySrcGMEID[gmeSrcId][gmeDstId] =
                this._connectionWaitingListBySrcGMEID[gmeSrcId][gmeDstId] || [];
            this._connectionWaitingListBySrcGMEID[gmeSrcId][gmeDstId].push([connType, connTexts]);
        } else {
            //#3 - both gmeSrcId and gmeDstId is missing from the screen
            //NOTE: this should never happen!!!
            this.logger.error('_saveConnectionToWaitingList both gmeSrcId and gmeDstId is undefined...');
        }
    };


    MetaEditorControl.prototype._saveConnection = function (gmeSrcId, gmeDstId, connType, connComponentId, connTexts) {
        //save by SRC
        this._connectionListBySrcGMEID[gmeSrcId] = this._connectionListBySrcGMEID[gmeSrcId] || {};
        this._connectionListBySrcGMEID[gmeSrcId][gmeDstId] = this._connectionListBySrcGMEID[gmeSrcId][gmeDstId] || {};

        this._connectionListBySrcGMEID[gmeSrcId][gmeDstId][connType] =
            this._connectionListBySrcGMEID[gmeSrcId][gmeDstId][connType] || [];

        this._connectionListBySrcGMEID[gmeSrcId][gmeDstId][connType].push(connComponentId);

        //save by DST
        this._connectionListByDstGMEID[gmeDstId] = this._connectionListByDstGMEID[gmeDstId] || {};
        this._connectionListByDstGMEID[gmeDstId][gmeSrcId] = this._connectionListByDstGMEID[gmeDstId][gmeSrcId] || {};

        this._connectionListByDstGMEID[gmeDstId][gmeSrcId][connType] =
            this._connectionListByDstGMEID[gmeDstId][gmeSrcId][connType] || [];

        this._connectionListByDstGMEID[gmeDstId][gmeSrcId][connType].push(connComponentId);

        //save by type
        this._connectionListByType[connType] = this._connectionListByType[connType] || [];
        this._connectionListByType[connType].push(connComponentId);

        //save by connectionID
        this._connectionListByID[connComponentId] = {
            GMESrcId: gmeSrcId,
            GMEDstId: gmeDstId,
            type: connType,
            name: (connTexts && connTexts.name) ? connTexts.name : undefined,
            connTexts: connTexts
        };
    };
    /****************************************************************************/
    /*  END OF --- CREATE A SPECIFIC TYPE OF CONNECTION BETWEEN 2 GME OBJECTS   */
    /****************************************************************************/


    /****************************************************************************/
    /*  REMOVES A SPECIFIC TYPE OF CONNECTION FROM 2 GME OBJECTS                */
    /****************************************************************************/
    MetaEditorControl.prototype._removeConnection = function (gmeSrcId, gmeDstId, connType, pointerName) {
        var connectionID,
            idx,
            len,
            connectionPresent = false;

        //only bother if
        //- both the source and destination is present on the screen
        //the connection in question is drawn
        if (this._connectionListBySrcGMEID[gmeSrcId] &&
            this._connectionListBySrcGMEID[gmeSrcId][gmeDstId] &&
            this._connectionListBySrcGMEID[gmeSrcId][gmeDstId][connType]) {
            connectionPresent = true;
        }

        if (!connectionPresent) {
            return;
        }

        len = this._connectionListBySrcGMEID[gmeSrcId][gmeDstId][connType].length;

        while (len--) {
            connectionID = this._connectionListBySrcGMEID[gmeSrcId][gmeDstId][connType][len];

            //if a pointer with a specific name should be removed
            //clear out the connectionID if this connection is not the representation of that pointer
            if (connType === MetaRelations.META_RELATIONS.POINTER &&
                pointerName &&
                pointerName !== '' &&
                this._connectionListByID[connectionID].name !== pointerName) {
                connectionID = undefined;
            }

            //if the connectionID is still valid
            if (connectionID) {
                this.diagramDesigner.deleteComponent(connectionID);

                //clean up accounting
                delete this._connectionListByID[connectionID];

                idx = this._connectionListByType[connType].indexOf(connectionID);
                this._connectionListByType[connType].splice(idx, 1);

                idx = this._connectionListBySrcGMEID[gmeSrcId][gmeDstId][connType].indexOf(connectionID);
                this._connectionListBySrcGMEID[gmeSrcId][gmeDstId][connType].splice(idx, 1);

                idx = this._connectionListByDstGMEID[gmeDstId][gmeSrcId][connType].indexOf(connectionID);
                this._connectionListByDstGMEID[gmeDstId][gmeSrcId][connType].splice(idx, 1);
            }
        }
    };
    /****************************************************************************/
    /*  END OF --- REMOVES A SPECIFIC TYPE OF CONNECTION FROM 2 GME OBJECTS     */
    /****************************************************************************/


    /*****************************************************************************/
    /*                UPDATE CONNECTION TEXT                                     */
    /*****************************************************************************/
    MetaEditorControl.prototype._updateConnectionText = function (gmeSrcId, gmeDstId, connType, connTexts) {
        var connectionID,
            idx,
            len = this._connectionListBySrcGMEID[gmeSrcId][gmeDstId][connType].length,
            pointerName = connTexts.name,
            found = false,
            connDesc;

        while (len--) {
            connectionID = this._connectionListBySrcGMEID[gmeSrcId][gmeDstId][connType][len];

            //if a pointer with a specific name should be removed
            //clear out the connectionID if this connection is not the representation of that pointer
            if (connType === MetaRelations.META_RELATIONS.POINTER &&
                pointerName &&
                pointerName !== '' &&
                this._connectionListByID[connectionID].name !== pointerName) {
                connectionID = undefined;
            }

            //if the connectionID is still valid
            if (connectionID) {
                this._connectionListByID[connectionID].name = connTexts.name;
                this._connectionListByID[connectionID].connTexts = connTexts;

                this.diagramDesigner.updateConnectionTexts(connectionID, connTexts);

                found = true;
            }
        }

        if (!found) {
            //try to find it in the connection waiting list
            if (this._GMENodes.indexOf(gmeSrcId) !== -1 && this._GMENodes.indexOf(gmeDstId) === -1) {
                //#1 - the destination object is missing from the screen
                len = this._connectionWaitingListByDstGMEID[gmeDstId][gmeSrcId].length;
                for (idx = 0; idx < len; idx += 1) {
                    connDesc = this._connectionWaitingListByDstGMEID[gmeDstId][gmeSrcId][idx];
                    if (connDesc[0] === connType) {
                        if (connType !== MetaRelations.META_RELATIONS.POINTER ||
                            (connType === MetaRelations.META_RELATIONS.POINTER &&
                            pointerName &&
                            pointerName !== '' &&
                            connDesc[1].name === pointerName)) {
                            connDesc[1] = connTexts;
                        }
                    }
                }
            } else if (this._GMENodes.indexOf(gmeSrcId) === -1 && this._GMENodes.indexOf(gmeDstId) !== -1) {
                //#2 -  the source object is missing from the screen
                len = this._connectionWaitingListBySrcGMEID[gmeSrcId][gmeDstId].length;
                for (idx = 0; idx < len; idx += 1) {
                    connDesc = this._connectionWaitingListBySrcGMEID[gmeSrcId][gmeDstId][idx];
                    if (connDesc[0] === connType) {
                        if (connType !== MetaRelations.META_RELATIONS.POINTER ||
                            (connType === MetaRelations.META_RELATIONS.POINTER &&
                            pointerName &&
                            pointerName !== '' &&
                            connDesc[1].name === pointerName)) {
                            connDesc[1] = connTexts;
                        }
                    }
                }
            } else {
                //#3 - both gmeSrcId and gmeDstId is missing from the screen
            }
        }
    };
    /*****************************************************************************/
    /*               END OF --- UPDATE CONNECTION TEXT                           */
    /*****************************************************************************/

    /**************************************************************************/
    /*  HANDLE OBJECT UPDATE  --- DISPLAY IT WITH ALL THE POINTERS / SETS / ETC */
    /**************************************************************************/
    MetaEditorControl.prototype._processNodeUpdate = function (gmeID) {
        var componentID,
            decClass,
            objDesc = {};

        if (this._GMEID2ComponentID.hasOwnProperty(gmeID)) {
            componentID = this._GMEID2ComponentID[gmeID];

            decClass = this._client.decoratorManager.getDecoratorForWidget(META_DECORATOR, WIDGET_NAME);

            objDesc.decoratorClass = decClass;
            objDesc.preferencesHelper = PreferencesHelper.getPreferences([{
                containerID: this.metaAspectContainerNodeID,
                setID: MetaEditorConstants.META_ASPECT_SET_NAME
            }]);

            this.diagramDesigner.updateDesignerItem(componentID, objDesc);

            //update set relations
            this._processNodeMetaContainment(gmeID);
            this._processNodeMetaPointers(gmeID, false);
            this._processNodeMetaInheritance(gmeID);
            this._processNodeMetaPointers(gmeID, true);
        }
    };
    /**************************************************************************/
    /*                   END OF --- HANDLE OBJECT UPDATE                      */
    /**************************************************************************/


    /***********************************************************************************/
    /*  DISPLAY META CONTAINMENT RELATIONS AS A CONNECTION FROM CONTAINER TO CONTAINED */
    /***********************************************************************************/
    MetaEditorControl.prototype._processNodeMetaContainment = function (gmeID) {
        var containmentMetaDescriptor = this._client.getValidChildrenItems(gmeID) || [],
            containmentOwnTypes = this._client.getOwnValidChildrenTypes(gmeID) || [],
            len,
            oldMetaContainment,
            newMetaContainment = {targets: []},
            diff,
            containmentTarget,
            idx;

        this._nodeMetaContainment[gmeID] = this._nodeMetaContainment[gmeID] || {targets: []};
        oldMetaContainment = this._nodeMetaContainment[gmeID];

        len = containmentMetaDescriptor.length;
        while (len--) {
            if (containmentOwnTypes.indexOf(containmentMetaDescriptor[len].id) !== -1) {
                newMetaContainment.targets.push(containmentMetaDescriptor[len].id);
                newMetaContainment[containmentMetaDescriptor[len].id] = {
                    multiplicity: '' + (containmentMetaDescriptor[len].min || 0) + '..' +
                    (containmentMetaDescriptor[len].max || '*')
                };
            }
        }

        //compute updated connections
        diff = _.intersection(oldMetaContainment.targets, newMetaContainment.targets);
        len = diff.length;
        while (len--) {
            containmentTarget = diff[len];
            if (oldMetaContainment[containmentTarget].multiplicity !==
                newMetaContainment[containmentTarget].multiplicity) {
                //update accounting
                oldMetaContainment[containmentTarget].multiplicity = newMetaContainment[containmentTarget].multiplicity;

                //update connection text
                this._updateConnectionText(gmeID, containmentTarget, MetaRelations.META_RELATIONS.CONTAINMENT, {
                    dstText: newMetaContainment[containmentTarget].multiplicity,
                    dstTextEdit: true
                });
            }
        }

        //compute deleted pointers
        diff = _.difference(oldMetaContainment.targets, newMetaContainment.targets);
        len = diff.length;
        while (len--) {
            containmentTarget = diff[len];
            this._removeConnection(gmeID, containmentTarget, MetaRelations.META_RELATIONS.CONTAINMENT);

            idx = oldMetaContainment.targets.indexOf(containmentTarget);
            oldMetaContainment.targets.splice(idx, 1);
            delete oldMetaContainment[containmentTarget];
        }

        //compute added pointers
        diff = _.difference(newMetaContainment.targets, oldMetaContainment.targets);
        len = diff.length;
        while (len--) {
            containmentTarget = diff[len];

            oldMetaContainment.targets.push(containmentTarget);
            oldMetaContainment[containmentTarget] = {multiplicity: newMetaContainment[containmentTarget].multiplicity};

            this._createConnection(gmeID, containmentTarget, MetaRelations.META_RELATIONS.CONTAINMENT, {
                dstText: newMetaContainment[containmentTarget].multiplicity,
                dstTextEdit: true
            });
        }
    };
    /**********************************************************************************************/
    /*  END OF --- DISPLAY META CONTAINMENT RELATIONS AS A CONNECTION FROM CONTAINER TO CONTAINED */
    /**********************************************************************************************/


    /*******************************************************************************/
    /*  DISPLAY META POINTER RELATIONS AS A CONNECTION FROM CONTAINER TO CONTAINED */
    /*******************************************************************************/
    MetaEditorControl.prototype._processNodeMetaPointers = function (gmeID, isSet) {
        var node = this._client.getNode(gmeID),
            pointerNames = isSet === true ? node.getSetNames() : node.getPointerNames(),
            pointerMetaDescriptor,
            pointerOwnMetaTypes,
            len,
            oldMetaPointers,
            newMetaPointers = {names: [], combinedNames: []},
            diff,
            pointerTarget,
            pointerName,
            idx,
            lenTargets,
            combinedName,
            ptrType = isSet === true ? MetaRelations.META_RELATIONS.SET : MetaRelations.META_RELATIONS.POINTER;

        if (isSet !== true) {
            this._nodeMetaPointers[gmeID] = this._nodeMetaPointers[gmeID] || {names: [], combinedNames: []};
            oldMetaPointers = this._nodeMetaPointers[gmeID];
        } else {
            this._nodeMetaSets[gmeID] = this._nodeMetaSets[gmeID] || {names: [], combinedNames: []};
            oldMetaPointers = this._nodeMetaSets[gmeID];
        }

        len = pointerNames.length;
        while (len--) {
            pointerMetaDescriptor = this._client.getValidTargetItems(gmeID, pointerNames[len]);
            pointerOwnMetaTypes = this._client.getOwnValidTargetTypes(gmeID, pointerNames[len]);


            if (pointerMetaDescriptor) {
                lenTargets = pointerMetaDescriptor.length;
                while (lenTargets--) {
                    if (pointerOwnMetaTypes.indexOf(pointerMetaDescriptor[lenTargets].id) !== -1) {
                        combinedName = pointerNames[len] + '_' + pointerMetaDescriptor[lenTargets].id;

                        newMetaPointers.names.push(pointerNames[len]);

                        newMetaPointers.combinedNames.push(combinedName);

                        newMetaPointers[combinedName] = {
                            name: pointerNames[len],
                            target: pointerMetaDescriptor[lenTargets].id
                        };

                        if (isSet) {
                            newMetaPointers[combinedName].multiplicity = '' +
                                (pointerMetaDescriptor[lenTargets].min || 0) +
                                '..' +
                                (pointerMetaDescriptor[lenTargets].max || '*');
                        }

                    }
                }
            }
        }

        //compute updated connections
        diff = _.intersection(oldMetaPointers.combinedNames, newMetaPointers.combinedNames);
        len = diff.length;
        while (len--) {
            combinedName = diff[len];
            if (oldMetaPointers[combinedName].multiplicity !== newMetaPointers[combinedName].multiplicity) {
                pointerName = oldMetaPointers[combinedName].name;
                pointerTarget = oldMetaPointers[combinedName].target;

                oldMetaPointers[combinedName].multiplicity = newMetaPointers[combinedName].multiplicity;

                this._updateConnectionText(gmeID, pointerTarget, ptrType, {
                    name: pointerName,
                    dstText: newMetaPointers[combinedName].multiplicity,
                    dstTextEdit: true
                });
            }
        }

        //compute deleted pointers
        diff = _.difference(oldMetaPointers.combinedNames, newMetaPointers.combinedNames);
        len = diff.length;
        while (len--) {
            combinedName = diff[len];
            pointerName = oldMetaPointers[combinedName].name;
            pointerTarget = oldMetaPointers[combinedName].target;

            this._removeConnection(gmeID, pointerTarget, ptrType, pointerName);

            idx = oldMetaPointers.combinedNames.indexOf(combinedName);
            oldMetaPointers.combinedNames.splice(idx, 1);
            delete oldMetaPointers[combinedName];
        }

        //compute added pointers
        diff = _.difference(newMetaPointers.combinedNames, oldMetaPointers.combinedNames);
        len = diff.length;
        while (len--) {
            combinedName = diff[len];
            pointerName = newMetaPointers[combinedName].name;
            pointerTarget = newMetaPointers[combinedName].target;

            oldMetaPointers.names.push(pointerName);
            oldMetaPointers.combinedNames.push(combinedName);
            oldMetaPointers[combinedName] = {
                name: newMetaPointers[combinedName].name,
                target: newMetaPointers[combinedName].target,
                multiplicity: newMetaPointers[combinedName].multiplicity
            };

            this._createConnection(gmeID, pointerTarget, ptrType, {
                name: pointerName,
                dstText: newMetaPointers[combinedName].multiplicity,
                dstTextEdit: true
            });
        }
    };
    /******************************************************************************************/
    /*  END OF --- DISPLAY META POINTER RELATIONS AS A CONNECTION FROM CONTAINER TO CONTAINED */
    /******************************************************************************************/


    /***********************************************************************************/
    /*  DISPLAY META INHERITANCE RELATIONS AS A CONNECTION FROM PARENT TO OBJECT       */
    /***********************************************************************************/
    MetaEditorControl.prototype._processNodeMetaInheritance = function (gmeID) {
        var node = this._client.getNode(gmeID),
            oldMetaInheritance,
            newMetaInheritance = node.getBaseId();

        //if there was a valid old that's different than the current, delete the connection representing the old
        oldMetaInheritance = this._nodeMetaInheritance[gmeID];
        if (oldMetaInheritance && (oldMetaInheritance !== newMetaInheritance)) {
            this._removeConnection(gmeID, oldMetaInheritance, MetaRelations.META_RELATIONS.INHERITANCE);

            delete this._nodeMetaInheritance[gmeID];
        }

        if (newMetaInheritance && (oldMetaInheritance !== newMetaInheritance)) {
            this._nodeMetaInheritance[gmeID] = newMetaInheritance;
            this._createConnection(gmeID, newMetaInheritance, MetaRelations.META_RELATIONS.INHERITANCE, undefined);
        }
    };
    /**********************************************************************************************/
    /*  END OF --- DISPLAY META CONTAINMENT RELATIONS AS A CONNECTION FROM PARENT TO OBJECT       */
    /**********************************************************************************************/


    /****************************************************************************/
    /*        CREATE NEW CONNECTION BUTTONS AND THEIR EVENT HANDLERS            */
    /****************************************************************************/

    MetaEditorControl.prototype._setNewConnectionType = function (connType) {
        var connProps = MetaRelations.getLineVisualDescriptor(connType),
            temp;

        if (this._connType !== connType) {
            this._connType = connType;

            if (connType === MetaRelations.META_RELATIONS.CONTAINMENT) {
                //for METACONTAINMENT AND INHERITANCE flip the visual end arrow style for drawing only
                temp = connProps[DiagramDesignerWidgetConstants.LINE_START_ARROW];

                connProps[DiagramDesignerWidgetConstants.LINE_START_ARROW] =
                    connProps[DiagramDesignerWidgetConstants.LINE_END_ARROW];

                connProps[DiagramDesignerWidgetConstants.LINE_END_ARROW] = temp;
            }

            this.diagramDesigner.connectionDrawingManager.setConnectionInDrawProperties(connProps);
            this.diagramDesigner.setFilterChecked(this._connType);
        }
    };

    /****************************************************************************/
    /*    END OF --- CREATE NEW CONNECTION BUTTONS AND THEIR EVENT HANDLERS     */
    /****************************************************************************/


    /****************************************************************************/
    /*    CREATE NEW CONNECTION BETWEEN TWO ITEMS                               */
    /****************************************************************************/
    MetaEditorControl.prototype._onCreateNewConnection = function (params) {
        var sourceId = this._ComponentID2GMEID[params.src],
            targetId = this._ComponentID2GMEID[params.dst];

        switch (this._connType) {
            case MetaRelations.META_RELATIONS.CONTAINMENT:
                this._createContainmentRelationship(targetId, sourceId);
                break;
            case MetaRelations.META_RELATIONS.INHERITANCE:
                this._createInheritanceRelationship(sourceId, targetId);
                break;
            case MetaRelations.META_RELATIONS.POINTER:
                this._createPointerRelationship(sourceId, targetId, false);
                break;
            case MetaRelations.META_RELATIONS.SET:
                this._createPointerRelationship(sourceId, targetId, true);
                break;
            default:
                break;
        }
    };


    MetaEditorControl.prototype._createContainmentRelationship = function (containerID, objectID) {
        var containerNode = this._client.getNode(containerID),
            objectNode = this._client.getNode(objectID);

        if (containerNode && objectNode) {
            this._client.updateValidChildrenItem(containerID, {id: objectID});
        }
    };


    MetaEditorControl.prototype._deleteContainmentRelationship = function (containerID, objectID) {
        var containerNode = this._client.getNode(containerID),
            objectNode = this._client.getNode(objectID);

        if (containerNode && objectNode) {
            this._client.removeValidChildrenItem(containerID, objectID);
        }
    };


    MetaEditorControl.prototype._createPointerRelationship = function (sourceID, targetID, isSet) {
        var sourceNode = this._client.getNode(sourceID),
            targetNode = this._client.getNode(targetID),
            pointerMetaDescriptor,
            existingPointerNames,
            notAllowedPointerNames,
            self = this;

        if (sourceNode && targetNode) {
            if (isSet === true) {
                //this is a pointer list
                existingPointerNames = sourceNode.getSetNames() || [];
                notAllowedPointerNames = sourceNode.getPointerNames() || [];
            } else {
                //this is a single pointer
                //get the list of existing pointers and show them in a dialog so the user can choose
                existingPointerNames = sourceNode.getPointerNames() || [];
                notAllowedPointerNames = sourceNode.getSetNames() || [];
            }

            //handle RESERVED pointer names
            existingPointerNames = _.difference(existingPointerNames, MetaEditorConstants.RESERVED_POINTER_NAMES);
            notAllowedPointerNames = notAllowedPointerNames.concat(MetaEditorConstants.RESERVED_POINTER_NAMES);

            //query pointer name from user
            this.diagramDesigner.selectNewPointerName(existingPointerNames,
                notAllowedPointerNames,
                isSet,
                function (userSelectedPointerName) {
                    self._client.startTransaction();
                    pointerMetaDescriptor = self._client.getValidTargetItems(sourceID, userSelectedPointerName);
                    if (!pointerMetaDescriptor) {
                        if (isSet !== true) {
                            //single pointer
                            self._client.setPointerMeta(sourceID, userSelectedPointerName, {
                                min: 1,
                                max: 1,
                                items: [
                                    {
                                        id: targetID,
                                        max: 1
                                    }
                                ]
                            });
                            self._client.makePointer(sourceID, userSelectedPointerName, null);
                        } else {
                            //pointer list
                            self._client.setPointerMeta(sourceID, userSelectedPointerName, {
                                items: [
                                    {
                                        id: targetID
                                    }
                                ]
                            });
                            self._client.createSet(sourceID, userSelectedPointerName);
                        }
                    } else {
                        if (isSet !== true) {
                            //single pointer
                            self._client.updateValidTargetItem(sourceID,
                                userSelectedPointerName,
                                {
                                    id: targetID,
                                    max: 1
                                });
                        } else {
                            //pointer list
                            self._client.updateValidTargetItem(sourceID, userSelectedPointerName, {id: targetID});
                        }
                    }

                    self._client.completeTransaction();
                });
        }
    };


    MetaEditorControl.prototype._deletePointerRelationship = function (sourceID, targetID, pointerName, isSet) {
        var sourceNode = this._client.getNode(sourceID),
            targetNode = this._client.getNode(targetID),
            pointerMetaDescriptor;

        //NOTE: this method is called from inside a transaction, don't need to start/complete one

        if (sourceNode && targetNode) {
            this._client.removeValidTargetItem(sourceID, pointerName, targetID);
            pointerMetaDescriptor = this._client.getValidTargetItems(sourceID, pointerName);
            if (pointerMetaDescriptor && pointerMetaDescriptor.length === 0) {
                if (isSet === false) {
                    //single pointer
                    this._client.deleteMetaPointer(sourceID, pointerName);
                    this._client.delPointer(sourceID, pointerName);
                } else {
                    //pointer list
                    this._client.deleteMetaPointer(sourceID, pointerName);
                    this._client.deleteSet(sourceID, pointerName);
                }
            }
        }
    };


    MetaEditorControl.prototype._createInheritanceRelationship = function (objectID, newBaseID) {
        var newBaseNode = this._client.getNode(newBaseID),
            objectNode = this._client.getNode(objectID),
            objectBase;

        if (newBaseNode && objectNode) {
            objectBase = objectNode.getBaseId();

            if (objectBase && !_.isEmpty(objectBase)) {
                this.logger.debug('InheritanceRelationship from "' +
                    objectNode.getAttribute(nodePropertyNames.Attributes.name) +
                    '" (' + objectID + ') to parent "' + objectBase + '" already exists, but overwriting to "' +
                    newBaseNode.getAttribute(nodePropertyNames.Attributes.name) + '" (' + newBaseID + ')"');
            }

            this._client.setBase(objectID, newBaseID);
        }
    };


    MetaEditorControl.prototype._deleteInheritanceRelationship = function (parentID, objectID) {
        var objectNode = this._client.getNode(objectID),
            objectBaseId,
            baseNode;

        if (objectNode) {
            objectBaseId = objectNode.getBaseId();

            if (objectBaseId) {
                baseNode = this._client.getNode(objectBaseId);
                if (baseNode) {
                    objectBaseId = baseNode.getAttribute(nodePropertyNames.Attributes.name) + ' (' + objectBaseId + ')';
                }
                /*this.logger.debug('Deleting InheritanceRelationship from "' +
                 objectNode.getAttribute(nodePropertyNames.Attributes.name) + '" (' + objectID + ') to parent "' +
                 objectBaseId + '"');
                 this._client.delBase(objectID);*/
                //TEMPORARILY DO NOT ALLOW DELETING INHERITANCE RELATIONSHIP
                this.logger.error('Deleting InheritanceRelationship from "' +
                    objectNode.getAttribute(nodePropertyNames.Attributes.name) + '" (' + objectID +
                    ') to parent "' + objectBaseId + '" is not allowed...');
            }
        }
    };
    /****************************************************************************/
    /*    END OF --- CREATE NEW CONNECTION BETWEEN TWO ITEMS                    */
    /****************************************************************************/


    /****************************************************************************/
    /*                  POINTER FILTER PANEL AND EVENT HANDLERS                 */
    /****************************************************************************/
    MetaEditorControl.prototype._initFilterPanel = function () {
        var filterIcon;

        filterIcon = MetaRelations.createButtonIcon(16, MetaRelations.META_RELATIONS.CONTAINMENT);
        this.diagramDesigner.addFilterItem('Containment', MetaRelations.META_RELATIONS.CONTAINMENT, filterIcon);

        filterIcon = MetaRelations.createButtonIcon(16, MetaRelations.META_RELATIONS.POINTER);
        this.diagramDesigner.addFilterItem('Pointer', MetaRelations.META_RELATIONS.POINTER, filterIcon);

        filterIcon = MetaRelations.createButtonIcon(16, MetaRelations.META_RELATIONS.INHERITANCE);
        this.diagramDesigner.addFilterItem('Inheritance', MetaRelations.META_RELATIONS.INHERITANCE, filterIcon);

        filterIcon = MetaRelations.createButtonIcon(16, MetaRelations.META_RELATIONS.SET);
        this.diagramDesigner.addFilterItem('Set', MetaRelations.META_RELATIONS.SET, filterIcon);
    };

    MetaEditorControl.prototype._onConnectionTypeFilterCheckChanged = function (value, isChecked) {
        var idx;

        if (isChecked === true) {
            //type should be enabled
            idx = this._filteredOutConnTypes.indexOf(value);
            this._filteredOutConnTypes.splice(idx, 1);
            this._unfilterConnType(value);
        } else {
            this._filteredOutConnTypes.push(value);
            this._filterConnType(value);
        }
    };

    MetaEditorControl.prototype._filterConnType = function (connType) {
        var len = this._connectionListByType &&
            this._connectionListByType.hasOwnProperty(connType) ? this._connectionListByType[connType].length : 0,
            connComponentId,
            gmeSrcId,
            gmeDstId,
            connTexts,
            pointerName;

        this._filteredOutConnectionDescriptors[connType] = [];

        this.diagramDesigner.beginUpdate();

        while (len--) {
            connComponentId = this._connectionListByType[connType][len];

            gmeSrcId = this._connectionListByID[connComponentId].GMESrcId;
            gmeDstId = this._connectionListByID[connComponentId].GMEDstId;
            connTexts = this._connectionListByID[connComponentId].connTexts;

            if (connType === MetaRelations.META_RELATIONS.POINTER) {
                pointerName = this._connectionListByID[connComponentId].name;
            }

            this._filteredOutConnectionDescriptors[connType].push([gmeSrcId, gmeDstId, connTexts]);

            this._removeConnection(gmeSrcId, gmeDstId, connType, pointerName);
        }

        this.diagramDesigner.endUpdate();
    };

    MetaEditorControl.prototype._unfilterConnType = function (connType) {
        //FIXME: What does this mean?
        var len = this._filteredOutConnectionDescriptors &&
            this._filteredOutConnectionDescriptors.hasOwnProperty(connType) ?
                this._filteredOutConnectionDescriptors[connType].length : 0,
            gmeSrcId,
            gmeDstId,
            connTexts;

        this.diagramDesigner.beginUpdate();

        while (len--) {
            gmeSrcId = this._filteredOutConnectionDescriptors[connType][len][0];
            gmeDstId = this._filteredOutConnectionDescriptors[connType][len][1];
            connTexts = this._filteredOutConnectionDescriptors[connType][len][2];

            this._createConnection(gmeSrcId, gmeDstId, connType, connTexts);
        }

        delete this._filteredOutConnectionDescriptors[connType];

        this.diagramDesigner.endUpdate();
    };

    /****************************************************************************/
    /*          END OF --- POINTER FILTER PANEL AND EVENT HANDLERS              */
    /****************************************************************************/

    /****************************************************************************/
    /*                    CONNECTION DESTINATION TEXT CHANGE                    */
    /****************************************************************************/

    MetaEditorControl.prototype._onConnectionDstTextChanged = function (connectionID, oldValue, newValue) {
        var connDesc = this._connectionListByID[connectionID];

        if (connDesc.type === MetaRelations.META_RELATIONS.CONTAINMENT) {
            this._containmentRelationshipMultiplicityUpdate(connDesc.GMESrcId, connDesc.GMEDstId, oldValue, newValue);
        } else if (connDesc.type === MetaRelations.META_RELATIONS.POINTER) {
            this._pointerRelationshipMultiplicityUpdate(connDesc.GMESrcId,
                connDesc.GMEDstId,
                connDesc.name,
                oldValue,
                newValue);
        } else if (connDesc.type === MetaRelations.META_RELATIONS.INHERITANCE) {
            //never can happen
        } else if (connDesc.type === MetaRelations.META_RELATIONS.SET) {
            this._setRelationshipMultiplicityUpdate(connDesc.GMESrcId,
                connDesc.GMEDstId,
                connDesc.name,
                oldValue,
                newValue);
        }
    };

    MetaEditorControl.prototype._containmentRelationshipMultiplicityUpdate = function (containerID, objectID, oldValue,
                                                                                       newValue) {
        var containerNode = this._client.getNode(containerID),
            objectNode = this._client.getNode(objectID),
            multiplicity,
            multiplicityValid;

        multiplicityValid = function (value) {
            var result = null,
                pattNum = /^\d+$/g,
                pattMinToMax = /^\d+\.\.\d+$/g,
                pattMinToMany = /^\d+\.\.\*$/g;

            //valid values for containment are 1, x..*, x..y
            if (pattNum.test(value)) {
                //#1: single digit number
                result = {
                    min: parseInt(value, 10),
                    max: parseInt(value, 10)
                };
            } else if (pattMinToMax.test(value)) {
                //#2: x..y
                result = {
                    min: parseInt(value, 10),
                    max: parseInt(value.substring(value.indexOf('..') + 2), 10)
                };
            } else if (pattMinToMany.test(value)) {
                //#3: x..*
                result = {
                    min: parseInt(value, 10),
                    max: -1
                };
            }

            return result;
        };

        if (containerNode && objectNode) {
            multiplicity = multiplicityValid(newValue);
            if (multiplicity) {

                multiplicity.id = objectID;

                this._client.updateValidChildrenItem(containerID, multiplicity);
            } else {
                this._updateConnectionText(containerID, objectID, MetaRelations.META_RELATIONS.CONTAINMENT, {
                    dstText: oldValue,
                    dstTextEdit: true
                });
            }
        }
    };

    MetaEditorControl.prototype._pointerRelationshipMultiplicityUpdate = function (sourceID, targetID, pointerName,
                                                                                   oldValue, newValue) {
        var sourceNode = this._client.getNode(sourceID),
            targetNode = this._client.getNode(targetID),
            multiplicityValid,
            multiplicity;

        multiplicityValid = function (value) {
            var result,
                pattOne = '1',
                pattZeroOne = '0..1';

            //valid values for pointer are: 1, 0..1
            if (value === pattOne) {
                //#1: single digit number
                result = {
                    min: 1,
                    max: 1
                };
            } else if (value === pattZeroOne) {
                result = {
                    min: 0,
                    max: 1
                };
            }

            return result;
        };

        if (sourceNode && targetNode) {
            multiplicity = multiplicityValid(newValue);
            if (multiplicity) {
                multiplicity.id = targetID;
                this._client.updateValidTargetItem(sourceID, pointerName, multiplicity);
            } else {
                this._updateConnectionText(sourceID, targetID, MetaRelations.META_RELATIONS.POINTER, {
                    name: pointerName,
                    dstText: oldValue,
                    dstTextEdit: true
                });
            }
        }
    };

    MetaEditorControl.prototype._setRelationshipMultiplicityUpdate = function (sourceID, targetID, pointerName,
                                                                               oldValue, newValue) {
        var sourceNode = this._client.getNode(sourceID),
            targetNode = this._client.getNode(targetID),
            multiplicityValid,
            multiplicity;

        multiplicityValid = function (value) {
            var result = null,
                pattNum = /^\d+$/g,
                pattMinToMax = /^\d+\.\.\d+$/g,
                pattMinToMany = /^\d+\.\.\*$/g;

            //valid value for pointer list are: 1, x..*, x..y
            if (pattNum.test(value)) {
                //#1: single digit number
                result = {
                    min: parseInt(value, 10),
                    max: parseInt(value, 10)
                };
            } else if (pattMinToMax.test(value)) {
                //#2: x..y
                result = {
                    min: parseInt(value, 10),
                    max: parseInt(value.substring(value.indexOf('..') + 2), 10)
                };
            } else if (pattMinToMany.test(value)) {
                //#3: x..*
                result = {
                    min: parseInt(value, 10),
                    max: -1
                };
            }

            return result;
        };

        if (sourceNode && targetNode) {
            multiplicity = multiplicityValid(newValue);
            if (multiplicity) {
                multiplicity.id = targetID;
                this._client.updateValidTargetItem(sourceID, pointerName, multiplicity);
            } else {
                this._updateConnectionText(sourceID, targetID, MetaRelations.META_RELATIONS.SET, {
                    name: pointerName,
                    dstText: oldValue,
                    dstTextEdit: true
                });
            }
        }
    };
    /****************************************************************************/
    /*               END OF --- CONNECTION DESTINATION TEXT CHANGE              */
    /****************************************************************************/

    MetaEditorControl.prototype._attachClientEventListeners = function () {
    };

    MetaEditorControl.prototype._detachClientEventListeners = function () {
    };

    MetaEditorControl.prototype.onActivate = function () {
        this._attachClientEventListeners();
        this._displayToolbarItems();
    };

    MetaEditorControl.prototype.onDeactivate = function () {
        this._detachClientEventListeners();
        this._hideToolbarItems();
    };

    MetaEditorControl.prototype._displayToolbarItems = function () {
        if (this._toolbarInitialized !== true) {
            this._initializeToolbar();
        } else {
            for (var i = 0; i < this._toolbarItems.length; i++) {
                this._toolbarItems[i].show();
            }
        }
    };

    MetaEditorControl.prototype._hideToolbarItems = function () {
        if (this._toolbarInitialized === true) {
            for (var i = 0; i < this._toolbarItems.length; i++) {
                this._toolbarItems[i].hide();
            }
        }
    };

    MetaEditorControl.prototype._removeToolbarItems = function () {
        if (this._toolbarInitialized === true) {
            for (var i = 0; i < this._toolbarItems.length; i++) {
                this._toolbarItems[i].destroy();
            }
        }
    };

    MetaEditorControl.prototype._initializeToolbar = function () {
        var toolBar = WebGMEGlobal.Toolbar,
            self = this;

        this._toolbarItems = [];

        /****************** ADD BUTTONS AND THEIR EVENT HANDLERS TO DIAGRAM DESIGNER ******************/

        /************** CREATE META RELATION CONNECTION TYPES *****************/
        this._radioButtonGroupMetaRelationType = toolBar.addRadioButtonGroup(function (data) {
            self._setNewConnectionType(data.connType);
        });
        this._toolbarItems.push(this._radioButtonGroupMetaRelationType);

        this._radioButtonGroupMetaRelationType.addButton({
            title: 'Containment',
            selected: true,
            data: {connType: MetaRelations.META_RELATIONS.CONTAINMENT},
            icon: MetaRelations.createButtonIcon(16, MetaRelations.META_RELATIONS.CONTAINMENT)
        });

        this._radioButtonGroupMetaRelationType.addButton({
            title: 'Inheritance',
            selected: false,
            data: {connType: MetaRelations.META_RELATIONS.INHERITANCE},
            icon: MetaRelations.createButtonIcon(16, MetaRelations.META_RELATIONS.INHERITANCE)
        });

        this._radioButtonGroupMetaRelationType.addButton({
            title: 'Pointer',
            selected: false,
            data: {connType: MetaRelations.META_RELATIONS.POINTER},
            icon: MetaRelations.createButtonIcon(16, MetaRelations.META_RELATIONS.POINTER)
        });

        this._radioButtonGroupMetaRelationType.addButton({
            title: 'Set',
            selected: false,
            data: {connType: MetaRelations.META_RELATIONS.SET},
            icon: MetaRelations.createButtonIcon(16, MetaRelations.META_RELATIONS.SET)
        });

        /************** END OF - CREATE META RELATION CONNECTION TYPES *****************/


        /************** PRINT NODE DATA *****************/
        // TODO removed, but could be reimplemented if needed such function
        //this._btnPrintNodeMetaData = toolBar.addButton({ "title": "Print node META data",
        //    "icon": "glyphicon glyphicon-share",
        //    "clickFn": function (/*data*/){
        //        self._printNodeData();
        //    }});
        //this._toolbarItems.push(this._btnPrintNodeMetaData);
        /************** END OF - PRINT NODE DATA *****************/


        /****************** END OF - ADD BUTTONS AND THEIR EVENT HANDLERS TO DESIGNER CANVAS ******************/


        this._toolbarInitialized = true;
    };

    MetaEditorControl.prototype._getAssociatedConnections = function (objectID) {
        var result = {src: [], dst: []},
            len,
            cID,
            checkConnections;

        checkConnections = function (cList, res) {

            var otherID,
                connType;

            //check objectID as source
            if (cList.hasOwnProperty(objectID)) {
                for (otherID in cList[objectID]) {
                    if (cList[objectID].hasOwnProperty(otherID)) {
                        for (connType in cList[objectID][otherID]) {
                            if (cList[objectID][otherID].hasOwnProperty(connType)) {
                                len = cList[objectID][otherID][connType].length;
                                while (len--) {
                                    cID = cList[objectID][otherID][connType][len];
                                    if (res.indexOf(cID) === -1) {
                                        res.push(cID);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        };

        checkConnections(this._connectionListBySrcGMEID, result.src);
        checkConnections(this._connectionListByDstGMEID, result.dst);

        return result;
    };

    MetaEditorControl.prototype._processMetaAspectSheetsRegistry = function () {
        var aspectNode = this._client.getNode(this.metaAspectContainerNodeID),
            metaAspectSheetsRegistry = aspectNode.getEditableRegistry(REGISTRY_KEYS.META_SHEETS) || [],
            i,
            len,
            sheetID,
            selectedSheetID,
            setName,
            j,
            gmeID;

        //save old positions
        var oldMetaAspectMembersCoordinatesPerSheet = this._metaAspectMembersCoordinatesPerSheet;

        this._sheets = {};
        this._metaAspectMembersPerSheet = {};
        this._metaAspectMembersCoordinatesPerSheet = {};
        this.diagramDesigner.clearTabs();
        this._metaAspectSheetsPerMember = {};

        metaAspectSheetsRegistry.sort(function (a, b) {
            if (a.order < b.order) {
                return -1;
            } else {
                return 1;
            }
        });

        //here we have the metaAspectRegistry ordered by user defined order
        this.diagramDesigner.addMultipleTabsBegin();
        len = metaAspectSheetsRegistry.length;
        for (i = 0; i < len; i += 1) {
            setName = metaAspectSheetsRegistry[i].SetID;

            sheetID = this.diagramDesigner.addTab(metaAspectSheetsRegistry[i].title, true, true);

            this._sheets[sheetID] = setName;

            //get the most up-to-date member list for each set
            this._metaAspectMembersPerSheet[setName] = aspectNode.getMemberIds(setName);

            //TODO: debug check to see if root for any reason is present among the members list
            //TODO: remove, not needed, just for DEGUG reasons...
            //TODO: it should never happen because it leads to double refresh when ROOT changes
            //TODO: when onOneEvent will be eliminated this will not be an issue anymore
            if (this._metaAspectMembersPerSheet[setName].indexOf(CONSTANTS.PROJECT_ROOT_ID) > -1) {
                this.logger.error('ROOT is in MetaSet: ' + setName);
            }

            //get the sheet coordinates
            this._metaAspectMembersCoordinatesPerSheet[setName] = {};
            j = this._metaAspectMembersPerSheet[setName].length;
            while (j--) {
                gmeID = this._metaAspectMembersPerSheet[setName][j];
                this._metaAspectMembersCoordinatesPerSheet[setName][gmeID] = aspectNode.getMemberRegistry(setName,
                    gmeID,
                    REGISTRY_KEYS.POSITION);
                this._metaAspectSheetsPerMember[gmeID] = this._metaAspectSheetsPerMember[gmeID] || [];
                this._metaAspectSheetsPerMember[gmeID].push(setName);
            }

            if (this._selectedMetaAspectSet &&
                this._selectedMetaAspectSet === metaAspectSheetsRegistry[i].SetID) {
                selectedSheetID = sheetID;
            }
        }
        this.diagramDesigner.addMultipleTabsEnd();

        //figure out whose position has changed
        var positionUpdated = [];
        if (this._selectedMetaAspectSet) {
            var oldPositions = oldMetaAspectMembersCoordinatesPerSheet[this._selectedMetaAspectSet];
            var newPositions = this._metaAspectMembersCoordinatesPerSheet[this._selectedMetaAspectSet];
            if (oldPositions && newPositions) {
                for (var oldItemId in oldPositions) {
                    if (oldPositions.hasOwnProperty(oldItemId) && newPositions.hasOwnProperty(oldItemId)) {
                        if (oldPositions[oldItemId].x !== newPositions[oldItemId].x ||
                            oldPositions[oldItemId].y !== newPositions[oldItemId].y) {
                            positionUpdated.push(oldItemId);
                        }
                    }
                }
            }
        }

        //setting selectedSheetID from global STATE
        if (WebGMEGlobal.State.get(CONSTANTS.STATE_ACTIVE_TAB) !== null &&
            WebGMEGlobal.State.get(CONSTANTS.STATE_ACTIVE_TAB) !== undefined &&
            metaAspectSheetsRegistry.length > WebGMEGlobal.State.get(CONSTANTS.STATE_ACTIVE_TAB)) {
            selectedSheetID = WebGMEGlobal.State.get(CONSTANTS.STATE_ACTIVE_TAB);
        }

        if (!selectedSheetID) {
            for (selectedSheetID in this._sheets) {
                if (this._sheets.hasOwnProperty(selectedSheetID)) {
                    break;
                }
            }
        }

        this.diagramDesigner.selectTab(selectedSheetID);

        return positionUpdated;
    };


    MetaEditorControl.prototype._initializeSelectedSheet = function () {
        var len,
            self = this;

        this.logger.debug('_initializeSelectedSheet');

        //delete everything from model editor
        this.diagramDesigner.clear();

        //clean up local hash map
        this._GMENodes = [];

        this._GMEID2ComponentID = {};
        this._ComponentID2GMEID = {};

        this._connectionWaitingListByDstGMEID = {};
        this._connectionWaitingListBySrcGMEID = {};

        this._connectionListBySrcGMEID = {};
        this._connectionListByDstGMEID = {};
        this._connectionListByType = {};
        this._connectionListByID = {};

        this._nodeMetaContainment = {};
        this._nodeMetaPointers = {};
        this._nodeMetaSets = {};
        this._nodeMetaInheritance = {};

        this._selectedMetaAspectSheetMembers = [];

        this._filteredOutConnectionDescriptors = {};
        len = this._filteredOutConnTypes.length;
        while (len--) {
            this._filteredOutConnectionDescriptors[this._filteredOutConnTypes[len]] = [];
        }

        //remove current territory patterns
        if (this._metaAspectMembersTerritoryId) {
            this._client.removeUI(this._metaAspectMembersTerritoryId);
        }

        this._metaAspectMemberPatterns = {};

        if (this._selectedMetaAspectSet && this._metaAspectMembersPerSheet[this._selectedMetaAspectSet]) {
            len = this._metaAspectMembersPerSheet[this._selectedMetaAspectSet].length;
            if (len > 0) {
                this.diagramDesigner.showProgressbar();
            }
            while (len--) {
                this._selectedMetaAspectSheetMembers.push(
                    this._metaAspectMembersPerSheet[this._selectedMetaAspectSet][len]);

                this._metaAspectMemberPatterns[this._metaAspectMembersPerSheet[this._selectedMetaAspectSet][len]] =
                {children: 0};
            }
        }

        this._metaAspectMembersTerritoryId = this._client.addUI(this, function (events) {
            self._eventCallback(events);
        });

        this._client.updateTerritory(this._metaAspectMembersTerritoryId, this._metaAspectMemberPatterns);
    };

    MetaEditorControl.prototype.setReadOnly = function (isReadOnly) {
        this._radioButtonGroupMetaRelationType.enabled(!isReadOnly);
    };

    //attach MetaEditorControl - DiagramDesigner event handler functions
    _.extend(MetaEditorControl.prototype, MetaEditorControlDiagramDesignerWidgetEventHandlers.prototype);


    return MetaEditorControl;
});
