/**
 *  map system functions
 */


define([
    'jquery',
    'app/init',
    'app/util',
    'bootbox',
    'app/map/util',
    'app/map/magnetizing'
], ($, Init, Util, bootbox, MapUtil, Magnetizer) => {
    'use strict';

    let config = {
        newSystemOffset: {
            x: 130,
            y: 0
        },

        systemHeadInfoClass: 'pf-system-head-info',                                     // class for system info
        systemHeadInfoLeftClass: 'pf-system-head-info-left',                            // class for left system info
        systemHeadInfoRightClass: 'pf-system-head-info-right',                          // class for right system info
        systemHeadRegionClass: 'pf-system-head-region',                                 // class for "region" in system info

        systemTooltipInnerIdPrefix: 'pf-system-tooltip-inner-',                         // id prefix for system tooltip content
        systemTooltipInnerClass: 'pf-system-tooltip-inner',                             // class for system tooltip content

        // dialogs
        dialogSystemId: 'pf-system-dialog',                                             // id for system dialog
        dialogSystemSelectClass: 'pf-system-dialog-select',                             // class for system select element
        dialogSystemStatusSelectId: 'pf-system-dialog-status-select',                   // id for "status" select
        dialogSystemLockId: 'pf-system-dialog-lock',                                    // id for "locked" checkbox
        dialogSystemRallyId: 'pf-system-dialog-rally',                                  // id for "rally" checkbox

        dialogSystemSectionInfoId: 'pf-system-dialog-section-info',                     // id for "info" section element
        dialogSystemSectionInfoStatusId: 'pf-system-dialog-section-info-status',        // id for "status" message in "info" element
        dialogSystemAliasId: 'pf-system-dialog-alias',                                  // id for "alias" static element
        dialogSystemSignaturesId: 'pf-system-dialog-signatures',                        // id for "signatures" count static element
        dialogSystemDescriptionId: 'pf-system-dialog-description',                      // id for "description" static element
        dialogSystemCreatedId: 'pf-system-dialog-created',                              // id for "created" static element
        dialogSystemUpdatedId: 'pf-system-dialog-updated',                              // id for "updated" static element

        dialogRallyId: 'pf-rally-dialog',                                               // id for "Rally point" dialog
        dialogRallyPokeDesktopId: 'pf-rally-dialog-poke-desktop',                       // id for "desktop" poke checkbox
        dialogRallyPokeSlackId: 'pf-rally-dialog-poke-slack',                           // id for "Slack" poke checkbox
        dialogRallyPokeDiscordId: 'pf-rally-dialog-poke-discord',                       // id for "Discord" poke checkbox
        dialogRallyPokeMailId: 'pf-rally-dialog-poke-mail',                             // id for "mail" poke checkbox
        dialogRallyMessageId: 'pf-rally-dialog-message',                                // id for "message" textarea

        dialogRallyMessageDefault: '' +
            'I need some help!\n\n' +
            '- Potential PvP options around\n' +
            '- DPS and Logistic ships needed'
    };

    /**
     * open "new system" dialog and add the system to map
     * optional the new system is connected to a "sourceSystem" (if available)
     * @param map
     * @param options
     * @param callback
     */
    let showNewSystemDialog = (map, options, callback) => {
        let mapContainer = $(map.getContainer());
        let mapId = mapContainer.data('id');

        /**
         * update new system dialog with some "additional" data
         * -> if system was mapped before
         * @param dialogElement
         * @param systemData
         */
        let updateDialog = (dialogElement, systemData = null) => {
            let labelEmpty = '<span class="editable-empty">empty</span>';
            let labelUnknown = '<span class="editable-empty">unknown</span>';
            let labelExist = '<span class="txt-color txt-color-success">loaded</span>';

            let showInfoHeadline = 'fadeOut';
            let showInfoSection = 'hide';
            let info = labelEmpty;

            let statusId = false;   // -> no value change
            let alias = labelEmpty;
            let signaturesCount = 0;
            let description = labelEmpty;
            let createdTime = labelUnknown;
            let updatedTime = labelUnknown;

            if(systemData){
                // system data found for selected system
                showInfoHeadline = 'fadeIn';
                showInfoSection = 'show';
                info = labelExist;
                statusId = parseInt(Util.getObjVal(systemData, 'status.id')) || statusId;
                alias = systemData.alias.length ? Util.htmlEncode(systemData.alias) : alias;
                signaturesCount = (Util.getObjVal(systemData, 'signatures') || []).length;
                description = systemData.description.length ? systemData.description : description;

                let dateCreated = new Date(systemData.created.created * 1000);
                let dateUpdated = new Date(systemData.updated.updated * 1000);
                let dateCreatedUTC = Util.convertDateToUTC(dateCreated);
                let dateUpdatedUTC = Util.convertDateToUTC(dateUpdated);

                createdTime = Util.convertDateToString(dateCreatedUTC);
                updatedTime = Util.convertDateToString(dateUpdatedUTC);

            }else if(systemData === null){
                // no system found for selected system
                showInfoHeadline = 'fadeIn';
            }

            // update new system dialog with new default data
            dialogElement.find('#' + config.dialogSystemSectionInfoStatusId).html(info);
            if(statusId !== false){
                dialogElement.find('#' + config.dialogSystemStatusSelectId).val(statusId).trigger('change');
            }
            dialogElement.find('#' + config.dialogSystemAliasId).html(alias);
            dialogElement.find('#' + config.dialogSystemSignaturesId).toggleClass('txt-color-green', signaturesCount > 0).html(signaturesCount);
            dialogElement.find('#' + config.dialogSystemDescriptionId).html(description);
            dialogElement.find('#' + config.dialogSystemCreatedId).html('<i class="fas fa-fw fa-plus"></i>&nbsp' + createdTime);
            dialogElement.find('#' + config.dialogSystemUpdatedId).html('<i class="fas fa-fw fa-pen"></i>&nbsp' + updatedTime);
            dialogElement.find('[data-target="#' + config.dialogSystemSectionInfoId + '"]').velocity('stop').velocity(showInfoHeadline, {duration: 120});
            dialogElement.find('[data-type="spinner"]').removeClass('in');
            dialogElement.find('#' + config.dialogSystemSectionInfoId).collapse(showInfoSection);
        };

        /**
         * request system data from server for persistent data -> update dialog
         * @param dialogElement
         * @param mapId
         * @param systemId
         */
        let requestSystemData = (dialogElement, mapId, systemId) => {
            // show loading animation
            dialogElement.find('[data-type="spinner"]').addClass('in');

            Util.request('GET', 'System', systemId, {mapId: mapId, isCcpId: 1}, {dialogElement: dialogElement})
                .then(payload => updateDialog(payload.context.dialogElement, payload.data))
                .catch(payload => updateDialog(payload.context.dialogElement));
        };

        // format system status for form select -----------------------------------------------------------------------
        // "default" selection (id = 0) prevents status from being overwritten
        // -> e.g. keep status information if system was just inactive (active = 0)
        let statusData = [{id: 0, text: 'auto'}];

        // get current map data ---------------------------------------------------------------------------------------
        let mapData = mapContainer.getMapDataFromClient(['hasId']);
        let mapSystems = mapData.data.systems;
        let mapSystemCount = mapSystems.length;
        let mapTypeName = mapContainer.data('typeName');
        let maxAllowedSystems = Init.mapTypes[mapTypeName].defaultConfig.max_systems;

        // show error if system max count reached ---------------------------------------------------------------------
        if(mapSystemCount >= maxAllowedSystems){
            Util.showNotify({title: 'Max system count exceeded', text: 'Limit of ' + maxAllowedSystems + ' systems reached', type: 'warning'});
            return;
        }

        // disable systems that are already on it ---------------------------------------------------------------------
        let mapSystemIds = mapSystems.map(systemData => systemData.systemId);

        // dialog data ------------------------------------------------------------------------------------------------
        let data = {
            id: config.dialogSystemId,
            select2Class: Util.config.select2Class,
            systemSelectClass: config.dialogSystemSelectClass,
            statusSelectId: config.dialogSystemStatusSelectId,
            lockId: config.dialogSystemLockId,
            rallyId: config.dialogSystemRallyId,

            sectionInfoId: config.dialogSystemSectionInfoId,
            sectionInfoStatusId: config.dialogSystemSectionInfoStatusId,
            aliasId: config.dialogSystemAliasId,
            signaturesId: config.dialogSystemSignaturesId,
            descriptionId: config.dialogSystemDescriptionId,
            createdId: config.dialogSystemCreatedId,
            updatedId: config.dialogSystemUpdatedId,
            statusData: statusData
        };

        // check for pre-selected system ------------------------------------------------------------------------------
        let systemData;
        if(options.systemData){
            systemData = options.systemData;
        }else{
            // ... check for current active system (characterLog) -----------------------------------------------------
            let currentCharacterLog = Util.getCurrentCharacterData('log');
            if(currentCharacterLog !== false){
                // set system from 'characterLog' data as pre-selected system
                systemData = Util.getObjVal(currentCharacterLog, 'system');
            }
        }

        // check if pre-selected system is NOT already on this map
        if(mapSystemIds.indexOf(Util.getObjVal(systemData, 'id')) === -1){
            data.currentSystem = systemData;
        }

        requirejs(['text!templates/dialog/system.html', 'mustache'], (template, Mustache) => {

            let content = Mustache.render(template, data);

            let systemDialog = bootbox.dialog({
                title: 'Add new system',
                message: content,
                show: false,
                buttons: {
                    close: {
                        label: 'cancel',
                        className: 'btn-default'
                    },
                    success: {
                        label: '<i class="fas fa-fw fa-check"></i> save',
                        className: 'btn-success',
                        callback: function(e){
                            // get form Values
                            let form = this.find('form');

                            let formData = $(form).getFormValues();

                            // validate form
                            form.validator('validate');

                            // check whether the form is valid
                            let formValid = form.isValidForm();

                            // don't close dialog on invalid data
                            if(formValid === false) return false;

                            // calculate new system position ----------------------------------------------------------
                            let newPosition;

                            // add new position
                            let sourceSystem = null;
                            let connectionData = null;
                            if(options.sourceSystem !== undefined){
                                // new position based on sourceSystem´s position
                                sourceSystem = options.sourceSystem;
                                connectionData = options.connectionData || null;

                                newPosition = newSystemPositionBySystem(sourceSystem);
                            }else if(options.position){
                                // new position based on coordinated (e.g. mouse event)
                                newPosition = {
                                    x: options.position.x,
                                    y: options.position.y
                                };
                            }else{
                                // new position based on current map scroll offset
                                newPosition = MapUtil.newSystemPositionsByMapOffset(mapContainer)[0];
                            }

                            formData.position = newPosition;
                            formData.mapId = mapId;

                            // ----------------------------------------------------------------------------------------

                            this.find('.modal-content').showLoadingAnimation();

                            Util.request('PUT', 'System', [], formData, {
                                systemDialog: systemDialog,
                                formElement: form,
                                map: map,
                                sourceSystem: sourceSystem,
                                connectionData: connectionData
                            }, context => {
                                // always do
                                context.systemDialog.find('.modal-content').hideLoadingAnimation();
                            }).then(
                                payload => {
                                    Util.showNotify({title: 'New system', text: payload.data.name, type: 'success'});

                                    callback(payload.context.map, payload.data, payload.context.sourceSystem, payload.context.connectionData);
                                    bootbox.hideAll();
                                },
                                Util.handleAjaxErrorResponse
                            );

                            return false;
                        }
                    }
                }
            });

            systemDialog.on('show.bs.modal', function(e){
                let dialogElement = $(this);

                // init "status" select2 ------------------------------------------------------------------------------
                for(let [statusName, data] of Object.entries(Init.systemStatus)){
                    statusData.push({id: data.id, text: data.label, class: data.class});
                }

                dialogElement.find('#' + config.dialogSystemStatusSelectId).initStatusSelect({
                    data: statusData,
                    iconClass: 'fa-tag'
                });

                // initial dialog update with persistent system data --------------------------------------------------
                // -> only if system is preselected (e.g. current active system)
                let systemId = parseInt(dialogElement.find('.' + config.dialogSystemSelectClass).val()) || 0;
                if(systemId){
                    requestSystemData(dialogElement, mapId, systemId);
                }
            });

            systemDialog.on('shown.bs.modal', function(e){
                let dialogElement = $(this);

                // no system selected
                updateDialog(dialogElement, false);

                dialogElement.initTooltips();

                // init system select live search  - some delay until modal transition has finished
                let selectElement = dialogElement.find('.' + config.dialogSystemSelectClass);
                selectElement.delay(240).initSystemSelect({
                    key: 'id',
                    disabledOptions: mapSystemIds,
                    onChange: systemId => {
                        // on system select -> update dialog with persistent system data
                        if(systemId){
                            requestSystemData(dialogElement, mapId, systemId);
                        }else{
                            // no system selected
                            updateDialog(dialogElement, false);
                        }
                    }
                });
            });

            // show dialog
            systemDialog.modal('show');
        });
    };

    /**
     * show "set rally point" dialog for system
     * @param system
     */
    $.fn.showRallyPointDialog = (system) => {
        let mapId = system.data('mapid');
        let systemId = system.data('id');
        let mapData = Util.getCurrentMapData(mapId);

        requirejs(['text!templates/dialog/system_rally.html', 'mustache'], function(template, Mustache){

            let setCheckboxObserver = (checkboxes) => {
                checkboxes.each(function(){
                    $(this).on('change', function(){
                        // check all others
                        let allUnchecked = true;
                        checkboxes.each(function(){
                            if(this.checked){
                                allUnchecked = false;
                            }
                        });
                        let textareaElement = $('#' + config.dialogRallyMessageId);
                        if(allUnchecked){
                            textareaElement.prop('disabled', true);
                        }else{
                            textareaElement.prop('disabled', false);
                        }
                    });
                });
            };

            let sendPoke = (requestData, context) => {
                // lock dialog
                let dialogContent = context.rallyDialog.find('.modal-content');
                dialogContent.showLoadingAnimation();

                $.ajax({
                    type: 'POST',
                    url: Init.path.pokeRally,
                    data: requestData,
                    dataType: 'json',
                    context: context
                }).done(function(data){

                }).fail(function(jqXHR, status, error){
                    let reason = status + ' ' + error;
                    Util.showNotify({title: jqXHR.status + ': sendPoke', text: reason, type: 'warning'});
                }).always(function(){
                    this.rallyDialog.find('.modal-content').hideLoadingAnimation();
                });
            };

            let data = {
                id: config.dialogRallyId,

                dialogRallyPokeDesktopId: config.dialogRallyPokeDesktopId,
                dialogRallyPokeSlackId: config.dialogRallyPokeSlackId,
                dialogRallyPokeDiscordId: config.dialogRallyPokeDiscordId,
                dialogRallyPokeMailId: config.dialogRallyPokeMailId,
                dialogRallyMessageId: config.dialogRallyMessageId ,

                desktopRallyEnabled: true,
                slackRallyEnabled: Boolean(Util.getObjVal(mapData, 'config.logging.slackRally')),
                discordRallyEnabled: Boolean(Util.getObjVal(mapData, 'config.logging.discordRally')),
                mailRallyEnabled: Boolean(Util.getObjVal(mapData, 'config.logging.mailRally')),
                dialogRallyMessageDefault: config.dialogRallyMessageDefault,

                systemUrl: MapUtil.getMapDeeplinkUrl(mapId, systemId),
                systemId: systemId
            };

            let content = Mustache.render(template, data);

            let rallyDialog = bootbox.dialog({
                message: content,
                title: 'Set rally point in "' + system.getSystemInfo( ['alias'] ) + '"',
                buttons: {
                    close: {
                        label: 'cancel',
                        className: 'btn-default'
                    },
                    success: {
                        label: '<i class="fas fa-fw fa-volume-up"></i> set rally point',
                        className: 'btn-success',
                        callback: function(){
                            let form = $('#' + config.dialogRallyId).find('form');
                            // get form data
                            let formData = form.getFormValues();

                            // update map
                            system.setSystemRally(1, {
                                poke: Boolean(formData.pokeDesktop)
                            });
                            MapUtil.markAsChanged(system);

                            // send poke data to server
                            sendPoke(formData, {
                                rallyDialog: this
                            });
                        }
                    }
                }
            });

            rallyDialog.initTooltips();

            // after modal is shown ==================================================================================
            rallyDialog.on('shown.bs.modal', function(e){
                // set event for checkboxes
                setCheckboxObserver(rallyDialog.find(':checkbox'));
            });
        });
    };

    /**
     * shows delete dialog for systems that should be deleted
     * @param map
     * @param systems
     * @returns {*}
     */
    $.fn.showDeleteSystemDialog = (map, systems = []) => {
        let mapContainer = $( map.getContainer() );
        let validDeleteSystems = [];
        let activeCharacters = 0;
        // check if systems belong to map -> security check
        for(let system of systems){
            let systemElement = $(system);
            if(
                systemElement.data('mapid') === mapContainer.data('id')  &&
                !systemElement.data('locked')
            ){
                // system belongs to map -> valid system
                validDeleteSystems.push(system);

                activeCharacters += (systemElement.data('userCount') ? parseInt( systemElement.data('userCount') ) : 0);
            }
        }

        if(validDeleteSystems.length){
            let msg = '';
            if(validDeleteSystems.length === 1){
                let deleteSystem = $(validDeleteSystems[0]);
                let systemName = deleteSystem.data('name');
                let systemAlias = deleteSystem.getSystemInfo( ['alias'] );

                let systemNameStr = (systemName === systemAlias) ? '"' + systemName + '"' : '"' + systemAlias + '" (' + systemName + ')';
                systemNameStr = '<span class="txt-color txt-color-warning">' + systemNameStr + '</span>';
                msg = 'Delete system ' + systemNameStr + ' and all its connections?';
            }else{
                msg = 'Delete ' + validDeleteSystems.length + ' selected systems and their connections?';
            }

            // add warning for active characters
            if(activeCharacters > 0){
                msg += ' <span class="txt-color txt-color-warning">Warning: ' + activeCharacters + ' active characters</span>';
            }

            let systemDeleteDialog = bootbox.confirm(msg, result => {
                if(result){
                    deleteSystems(map, validDeleteSystems, (deletedSystems) => {
                        // callback function after deleted -> close dialog
                        systemDeleteDialog.modal('hide');

                        // check whether all systems were deleted properly
                        if(deletedSystems.length !== validDeleteSystems.length){
                            let notDeletedCount = validDeleteSystems.length - deletedSystems.length;

                            Util.showNotify({
                                title: 'Failed to delete systems',
                                text: '(' + notDeletedCount + '/' +  validDeleteSystems.length + ') systems could not be deleted',
                                type: 'warning'}
                            );
                        }else if(deletedSystems.length === 1){
                            Util.showNotify({title: 'System deleted', text: $(deletedSystems[0]).data('name'), type: 'success'});
                        }else{
                            Util.showNotify({title: systems.length + ' systems deleted', type: 'success'});
                        }
                    });
                }
            });
        }else{
            Util.showNotify({title: 'No systems selected', type: 'warning'});
        }

        return this;
    };

    /**
     * toggle system tooltip (current pilot count)
     * @param show
     * @param tooltipOptions
     * @returns {*}
     */
    $.fn.toggleSystemTooltip = function(show, tooltipOptions){
        let highlightData = {
            good: {
                colorClass: 'txt-color-green',
                iconClass: 'fa-caret-up'
            },
            bad: {
                colorClass: 'txt-color-red',
                iconClass: 'fa-caret-down'
            }
        };

        let getHighlightClass = (highlight) => {
            return Util.getObjVal(highlightData, highlight + '.colorClass') || '';
        };

        let getHighlightIcon = (highlight) => {
            return Util.getObjVal(highlightData, highlight + '.iconClass') || '';
        };

        let getTitle = (userCounter, highlight) => {
            return '<i class="fas ' + getHighlightIcon(highlight) + '"></i>&nbsp;' + userCounter + '';
        };

        return this.each(function(){
            let system = $(this);
            switch(show){
                case 'destroy':
                    //destroy tooltip and remove some attributes which are not deleted by 'destroy'
                    system.tooltip('destroy').removeAttr('title data-original-title');
                    break;
                case 'hide':
                    system.tooltip('hide');
                    break;
                case 'show':
                    // initial "show" OR "update" open tooltip
                    // -> do not update tooltips while a system is dragged
                    let showTooltip = !system.hasClass('jsPlumb_dragged');

                    if(system.data('bs.tooltip')){
                        // tooltip initialized but could be hidden
                        // check for title update
                        if(
                            tooltipOptions.hasOwnProperty('userCount') &&
                            tooltipOptions.hasOwnProperty('highlight')
                        ){
                            let currentTitle = system.attr('data-original-title');
                            let newTitle = getTitle(tooltipOptions.userCount, tooltipOptions.highlight);

                            if(currentTitle !== newTitle){
                                // update tooltip
                                let tooltipInner = system.attr('title', newTitle).tooltip('fixTitle').data('bs.tooltip').$tip.find('.tooltip-inner');
                                tooltipInner.html(newTitle);

                                // change highlight class
                                let highlightClass = getHighlightClass(tooltipOptions.highlight);
                                if( !tooltipInner.hasClass(highlightClass) ){
                                    tooltipInner.removeClass( getHighlightClass('good') + ' ' + getHighlightClass('bad')).addClass(highlightClass);
                                }
                            }
                        }

                        let tip = system.data('bs.tooltip').tip();
                        if( !tip.hasClass('in') && showTooltip){
                            // update tooltip placement based on system position
                            system.data('bs.tooltip').options.placement = getSystemTooltipPlacement(system);

                            system.tooltip('show');
                        }
                    }else{
                        // no tooltip initialized
                        // "some" config data is required
                        if(
                            tooltipOptions.hasOwnProperty('systemId') &&
                            tooltipOptions.hasOwnProperty('userCount') &&
                            tooltipOptions.hasOwnProperty('highlight')
                        ){
                            let innerTooltipId = config.systemTooltipInnerIdPrefix + tooltipOptions.systemId;

                            let template = '<div class="tooltip" role="tooltip">' +
                                '<div class="tooltip-arrow"></div>' +
                                '<div id="' + innerTooltipId + '" class="tooltip-inner txt-color ' + config.systemTooltipInnerClass + '"></div>' +
                                '</div>';

                            let options = {
                                trigger: 'manual',
                                placement: getSystemTooltipPlacement(system),
                                html: true,
                                animation: true,
                                template: template,
                                container: system.closest('.' + Util.config.mapClass)
                            };

                            // init new tooltip -> Do not show automatic maybe system is currently dragged
                            system.attr('title', getTitle(tooltipOptions.userCount, tooltipOptions.highlight));
                            system.tooltip(options);

                            system.one('shown.bs.tooltip', function(){
                                // set highlight only on FIRST show
                                $('#' + this.innerTooltipId).addClass(this.highlightClass);
                            }.bind({
                                highlightClass: getHighlightClass(tooltipOptions.highlight),
                                innerTooltipId: innerTooltipId
                            }));

                            if(showTooltip){
                                system.tooltip('show');
                            }
                        }
                    }
                    break;
            }
        });
    };

    /**
     * get tooltip position (top, bottom) based on system position
     * @param system
     * @returns {string}
     */
    let getSystemTooltipPlacement = system => {
        let offsetParent = system.parent().offset();
        let offsetSystem = system.offset();

        return (offsetSystem.top - offsetParent.top < 27)  ? 'bottom' : 'top';
    };

    /**
     * delete system(s) with all their connections
     * (ajax call) remove system from DB
     * @param map
     * @param systems
     * @param callback function
     */
    let deleteSystems = (map, systems = [], callback = systems => {}) => {
        let mapContainer = $( map.getContainer() );
        let systemIds = systems.map(system => $(system).data('id'));

        Util.request('DELETE', 'System', systemIds, {
            mapId: mapContainer.data('id')
        }, {
            map: map,
            systems: systems
        }).then(
            payload => {
                // check if all systems were deleted that should get deleted
                let deletedSystems = payload.context.systems.filter(
                    function(system){
                        return this.indexOf( $(system).data('id') ) !== -1;
                    }, payload.data
                );

                // remove systems from map
                removeSystems(payload.context.map,  deletedSystems);

                callback(deletedSystems);
            },
            Util.handleAjaxErrorResponse
        );
    };

    /**
     * remove system(s) from map (no backend requests)
     * @param map
     * @param systems
     */
    let removeSystems = (map, systems) => {
        let removeSystemCallback = deleteSystem => {
            map.remove(deleteSystem);
        };

        for(let system of systems){
            system = $(system);
            let mapId = parseInt(system.data('mapid')) || 0;

            // check if system is "active"
            if(system.hasClass(MapUtil.config.systemActiveClass)){
                Util.deleteCurrentSystemData(mapId);

                // get parent Tab Content and fire clear modules event
                let tabContentElement = MapUtil.getTabContentElementByMapElement(system);
                $(tabContentElement).trigger('pf:removeSystemModules');
            }

            // remove connections do not fire a "connectionDetached" event
            map.deleteConnectionsForElement(system, {fireEvent: false});

            // unregister from "magnetizer"
            Magnetizer.removeElement(mapId, system[0]);

            // destroy tooltip/popover
            system.toggleSystemTooltip('destroy', {});
            system.destroyPopover(true);

            // remove system
            system.velocity('transition.whirlOut', {
                duration: Init.animationSpeed.mapDeleteSystem,
                complete: removeSystemCallback
            });
        }
    };

    /**
     * calculate the x/y coordinates for a new system - relative to a source system
     * -> in case no coordinates found -> return default calculated coordinates
     * @param sourceSystem
     * @returns {{x: *, y: *}}
     */
    let newSystemPositionBySystem = sourceSystem => {
        let x = 0;
        let y = 0;

        let dimensions = MapUtil.newSystemPositionBySystem(sourceSystem);
        if(dimensions.length){
            //... empty map space found
            x = dimensions[0].left;
            y = dimensions[0].top;
        }else{
            //... fallback
            // related system is available
            let currentX = sourceSystem.css('left');
            let currentY = sourceSystem.css('top');

            // remove "px"
            currentX = parseInt( currentX.substring(0, currentX.length - 2) );
            currentY = parseInt( currentY.substring(0, currentY.length - 2) );
            x = currentX + config.newSystemOffset.x;
            y = currentY + config.newSystemOffset.y;
        }

        return {x: x, y: y};
    };

    /**
     * get new dom element for systemData that shows "info" data (additional data)
     * -> this is show below the system base data on map
     * @param data
     * @returns {HTMLDivElement|undefined}
     */
    let getHeadInfoElement = data => {
        let infoEl;
        let headInfoLeft = [];
        let headInfoRight = [];

        if(data.drifter){
            headInfoLeft.push(Object.assign(document.createElement('i'), {
                className: `fas fa-fw fa-wave-square ${Util.getSecurityClassForSystem(data.security)}`,
                title: 'drifter'
            }));
        }

        if(data.shattered){
            headInfoLeft.push(Object.assign(document.createElement('i'), {
                className: `fas fa-fw fa-chart-pie ${Util.getSecurityClassForSystem('SH')}`,
                title: 'shattered'
            }));
        }

        if(data.type.id === 2){
            headInfoLeft.push(Object.assign(document.createElement('span'), {
                className: config.systemHeadRegionClass,
                textContent: data.region.name
            }));
        }

        // check systemData if headInfo element is needed
        if(data.statics && data.statics.length){
            // format wh statics
            for(let wormholeName of data.statics){
                let staticData = Object.assign({}, Init.wormholes[wormholeName]);
                let staticEl = Object.assign(document.createElement('span'), {
                    className: [
                        Util.getSecurityClassForSystem(staticData.security),
                        Util.config.popoverTriggerClass
                    ].join(' '),
                    textContent: staticData.security
                });
                staticEl.dataset.name = staticData.name;
                headInfoRight.push(staticEl);
            }
        }

        if(headInfoLeft.length || headInfoRight.length){
            let leftEl = Object.assign(document.createElement('div'), {
                className: config.systemHeadInfoLeftClass
            });
            leftEl.append(...headInfoLeft);

            let rightEl = Object.assign(document.createElement('div'), {
                className: config.systemHeadInfoRightClass
            });
            rightEl.append(...headInfoRight);

            infoEl = Object.assign(document.createElement('div'), {
                className: config.systemHeadInfoClass
            });
            infoEl.append(leftEl, rightEl);
        }

        return infoEl;
    };

    return {
        showNewSystemDialog: showNewSystemDialog,
        deleteSystems: deleteSystems,
        removeSystems: removeSystems,
        getHeadInfoElement: getHeadInfoElement
    };
});