/**
 * Map "magnetizing" feature
 * jsPlumb extension used: https://github.com/ThomasChan/farahey
 */

define([
    'jquery',
    'app/map/util',
    'farahey'
], ($, MapUtil) => {

    'use strict';

    /**
     * active magnetizer instances (cache object)
     * @type {{}}
     */
    let magnetizerInstances = {};

    /**
     * magnetizer instance exists for mapId
     * @param mapId
     * @returns {boolean}
     */
    let hasInstance = mapId => magnetizerInstances.hasOwnProperty(mapId);

    /**
     * get magnetizer instance by mapId
     * @param mapId
     * @returns {null}
     */
    let getInstance = mapId => hasInstance(mapId) ? magnetizerInstances[mapId] : null;

    /**
     * set new magnetizer instance for mapId
     * @param mapId
     * @param magnetizer
     */
    let setInstance = (mapId, magnetizer) => {
        if(mapId && magnetizer){
            magnetizerInstances[mapId] = magnetizer;
        }
    };

    /**
     * init new magnetizer instance for a map
     * @param mapContainer
     */
    let initMagnetizer = mapContainer => {
        let mapId = mapContainer.data('id');

        if(!hasInstance(mapId)){
            // magnetizer not exist -> new instance
            let systems = mapContainer.getSystems();

            /**
             * function that takes an element from your list and returns its position as a JS object
             * @param system
             * @returns {{top: number, left: number}}
             * @private
             */
            let _offset = system => {
                let _ = p => {
                    let v = system.style[p];
                    return parseInt(v.substring(0, v.length - 2));
                };
                return {left: _('left'), top: _('top')};
            };

            /**
             * function that takes an element id and position, and applies that position to the related element
             * @param system
             * @param o
             * @private
             */
            let _setOffset = (system, o) => {
                o.left = Math.round(o.left);
                o.top = Math.round(o.top);
                let left = o.left + 'px';
                let top = o.top + 'px';
                let markAsUpdated = false;

                // new position must be within parent container
                // no negative offset!
                if(
                    o.left >= 0 && o.left <= 2300 &&
                    system.style.left !== left
                ){
                    system.style.left = left;
                    markAsUpdated = true;
                }

                if(
                    o.top >= 0 && o.top <= 1400 &&
                    system.style.top !== top
                ){
                    system.style.top = top;
                    markAsUpdated = true;
                }

                if(markAsUpdated){
                    MapUtil.markAsChanged($(system));
                }
            };

            /**
             * filter some element8s) from being moved
             * @param systemId
             * @returns {boolean}
             * @private
             */
            let _dragFilter = systemId => {
                let filterClasses = ['jtk-drag', MapUtil.config.systemLockedClass];
                return ![...document.getElementById(systemId).classList].some(className => filterClasses.indexOf(className) >= 0);
            };

            /**
             * grid snap constraint
             * @param gridX
             * @param gridY
             * @returns {Function}
             */
            let gridConstrain = (gridX, gridY) => {
                return (id, current, delta) => {
                    if(mapContainer.hasClass(MapUtil.config.mapGridClass)){
                        // active grid
                        return {
                            left: (gridX * Math.floor( (Math.round(current[0]) + delta.left) / gridX )) - current[0],
                            top:  (gridY * Math.floor( (Math.round(current[1]) + delta.top) / gridY )) - current[1]
                        };
                    }else{
                        // no grid
                        delta.left = Math.round(delta.left);
                        delta.top = Math.round(delta.top);
                        return delta;
                    }
                };
            };

            // create new magnetizer instance -------------------------------------------------------------------------
            setInstance(mapId, window.Farahey.getInstance({
                container: mapContainer,
                getContainerPosition: mapContainer => mapContainer.offset(),
                getPosition:_offset,
                getSize: system => {
                    let clientRect = system.getBoundingClientRect();
                    return [Math.floor(clientRect.width), Math.floor(clientRect.height)];
                },
                getId : system => system.id,
                setPosition:_setOffset,
                elements: systems.toArray(),
                filter: _dragFilter,
                padding: [3, 3],
                constrain: gridConstrain(MapUtil.config.mapSnapToGridDimension, MapUtil.config.mapSnapToGridDimension),
                executeNow: false,                               // no initial rearrange after initialization
                excludeFocus: true
            }));
        }
    };

    /**
     * destroy Magnetizer instance
     */
    let destroyMagnetizer = mapContainer => {
        let mapId = mapContainer.data('id');
        let magnetizer = getInstance(mapId);
        if(magnetizer){
            magnetizer.reset();
            delete magnetizerInstances[mapId];
        }
    };

    /**
     * update system positions for "all" systems that are effected by drag&drop
     * @param map
     * @param e
     */
    let executeAtEvent = (map, e) => {
        let mapContainer = $(map.getContainer());
        let mapId = mapContainer.data('id');
        let magnetizer = getInstance(mapId);

        if(magnetizer && e){
            magnetizer.executeAtEvent(e, {
                iterations: 2,
                excludeFocus: true
            });
            map.repaintEverything();
        }
    };

    /**
     * add system to magnetizer instance
     * @param mapId
     * @param system
     * @param doNotTestForDuplicates
     */
    let addElement = (mapId, system, doNotTestForDuplicates) => {
        let magnetizer = getInstance(mapId);
        if(magnetizer){
            magnetizer.addElement(system, doNotTestForDuplicates);
        }
    };

    /**
     * remove system element from magnetizer instance
     * @param mapId
     * @param system
     */
    let removeElement = (mapId, system) => {
        let magnetizer = getInstance(mapId);
        if(magnetizer){
            magnetizer.removeElement(system);
        }
    };

    return {
        initMagnetizer: initMagnetizer,
        destroyMagnetizer: destroyMagnetizer,
        executeAtEvent: executeAtEvent,
        addElement: addElement,
        removeElement: removeElement
    };
});