/* Minification failed. Returning unminified contents.
(1,1): run-time error CSS1019: Unexpected token, found '('
(1,11): run-time error CSS1031: Expected selector, found '('
(1,11): run-time error CSS1025: Expected comma or open brace, found '('
(2347,2): run-time error CSS1019: Unexpected token, found ')'
(2347,3): run-time error CSS1019: Unexpected token, found '('
(2347,10): run-time error CSS1031: Expected selector, found ')'
(2347,10): run-time error CSS1025: Expected comma or open brace, found ')'
 */
(function ($, undefined) {
    var history = window.history || {};

    var $body = $(document.body);

    var head = document.getElementsByTagName('head')[0];

    var insertBefore = head.insertBefore;

    head.insertBefore = function (newElement, referenceElement) {

        if (newElement.href && newElement.href.indexOf('//fonts.googleapis.com/') > -1) {

            console.info('Prevented Loading Google Font!');
            return;
        }

        insertBefore.call(head, newElement, referenceElement);
    };

    var TIMEOUT = window.TIMEOUT || 60000;

    var DEFAULT_DEBOUNCE_WAIT = window.MAP_DEFAULT_DEBOUNCE_WAIT || 200;
    var DEFAULT_CACHE_DURATION = window.MAP_DEFAULT_CACHE_DURATION || 60000;

    var MAP_UPDATE_INTERVAL = window.MAP_UPDATE_INTERVAL || 5000;
    var FASCICLES_UPDATE_INTERVAL = window.MAP_FASCICLES_UPDATE_INTERVAL || 30000;

    var KIOSK_MODE = window.KIOSK_MODE || false;

    var TOTEM_MODE = window.TOTEM_MODE || false;

    var iconSize, iconScaledSize, iconOrigin, iconAnchor, iconLabelOrigin;

    var lang = document.documentElement.lang;

    moment.locale(lang);

    _.templateSettings.imports = { lang: lang };

    var SHIP_GROUP_PASSENGERS = 1;
    var SHIP_GROUP_CRUISE = 2;
    var SHIP_GROUP_CARGO = 3;

    var shipGroups = {};

    shipGroups[SHIP_GROUP_PASSENGERS] = { it: "Passeggeri", en: "Passengers" }[lang];
    shipGroups[SHIP_GROUP_CRUISE] = { it: "Crociera", en: "Cruise" }[lang];
    shipGroups[SHIP_GROUP_CARGO] = { it: "Cargo", en: "Cargo" }[lang];


    var getGmaps = new Promise(function (resolve, reject) {
        window.initMap = function () {
            window.initMap = undefined;

            var gmaps = google.maps;

            iconSize = new gmaps.Size(64, 64);
            iconScaledSize = new gmaps.Size(48, 48);
            iconOrigin = new gmaps.Point(0, 0);
            iconAnchor = new gmaps.Point(40, 47);
            iconLabelOrigin: new gmaps.Point(48, 48);

            $.getScript("//unpkg.com/@googlemaps/markerwithlabel@1.3.11/dist/index.min.js").then(function () {
                resolve(google.maps);
            });
        };
        $body.append('<script src="//maps.googleapis.com/maps/api/js?key=' + GOOGLE_MAPS_API_KEY + '&callback=initMap&language=' + lang + '" />');
    }).timeout(TIMEOUT);

    var getTemplate = (function (promises) {
        return function (name) {
            if (promises[name] === undefined) {
                promises[name] = new Promise(function (resolve, reject) {
                    $.ajax({
                        method: 'GET',
                        url: JS_TEMPLATES_PATH + name + '.jst',
                        dataType: 'text'
                    }).then(function (tmpl) {
                        resolve(_.template(tmpl));
                    });
                }).timeout(TIMEOUT);
            }

            return promises[name];
        };
    })({});

    var preLoadImage = (function (promises) {
        return function (url) {
            if (promises[url] === undefined) {
                promises[url] = new Promise(function (resolve, reject) {
                    var img = new Image();

                    img.onload = function () {
                        resolve(url);
                    };
                    img.onerror = reject;
                    img.src = url;
                });
            }

            return promises[url];
        };
    })({});

    var loadIcon = (function (promises) {
        return function (url) {
            if (promises[url] === undefined) {
                promises[url] = new Promise(function (resolve, reject) {
                    Promise.all([getGmaps, preLoadImage(url)]).then(function () {
                        resolve({
                            url: url,
                            size: iconSize,
                            scaledSize: iconScaledSize,
                            origin: iconOrigin,
                            anchor: iconAnchor
                        });
                    }).catch(reject);
                });
            }

            return promises[url];
        };
    })({});

    var shipTypeToGroups = function (type) {
        if (type === null) {
            return [];
        }

        if (type === 'Passenger/Cruise') {
            return [SHIP_GROUP_CRUISE];
        }

        var groups = [];

        if (_.toLower(type).indexOf('cargo') > -1) {
            groups.push(SHIP_GROUP_CARGO);
        }

        if (_.toLower(type).indexOf('passenger') > -1) {
            groups.push(SHIP_GROUP_PASSENGERS);
        }

        return groups;
    };

    var _lastPushedHash = '';

    var _setHistoryHash = (function () {
        //compatibilit� browser
        var setHash = history.replaceState !== undefined ? function (hash, push) {
            if (push) {
                history.push(undefined, undefined, hash);
            } else {
                history.replaceState(undefined, undefined, hash);
            }
        } : function (hash, push) {
            if (!push) {
                window.location.hash = hash;
            }
        };

        return function (h) {
            _lastPushedHash = h;
            setHash('#' + h || '');
        };
    })();

    var _getLocationHash = function () {
        return window.location.hash.replace(/^#/, '');
    };

    var _createCachedDataDefaultOptions = {
        idField: 'id',
        createQueryArgs: function (ids, idField) {
            var q = {};
            q[idField + 's'] = ids;
            return $.param(q);
        },
        cacheDuration: DEFAULT_CACHE_DURATION,
        deBounce: DEFAULT_DEBOUNCE_WAIT
    };

    var _cacheDeleter = function (cache, ids) {
        _forEach(ids, function (id) {
            delete cache[id];
        });
    };

    var _createCachedDataGetter = function (url, options) {
        var op = _.defaults(options, _createCachedDataDefaultOptions);

        var cache = {};
        var resolvers = {};

        var fn = function (keys) {

            if (keys === undefined) {
                return Promise.resolve([]);
            } else if (!_.isArray(keys)) {
                keys = [keys];
            }

            var values = [];
            var c = 0;

            _.forEach(_.uniq(keys), function (key) {
                if (key === null) {
                    return;
                }

                if (op.cacheDuration === -1 || !_.has(cache, key)) {
                    cache[key] = new Promise(function (resolve, reject) {
                        resolvers[key.toString()] = resolve;
                    });
                    c++;
                }
                values.push(cache[key.toString()]);
            });

            if (c) {

                if (op.cacheDuration > 0) {
                    _.delay(function (cache) {
                        _.forEach(cache, function (key) {
                            delete cache[key.toString()];
                        });
                    }, op.cacheDuration, cache);
                }

                getJsonData();
            }

            return Promise.all(values);
        };

        var getJsonData = function () {
            var nonCached = resolvers;
            resolvers = {};

            var p = $.getJSON(url + '?' + op.createQueryArgs(_.keys(nonCached), op.idField)).then(function (data) {
                //_.forEach(data, function (dt) {
                //    var key = dt[op.idField].toString();

                //    nonCached[key](dt);

                //    if (op.cacheDuration !== 0) {
                //        cache[key] = dt;
                //    } else {
                //        delete cache[key];
                //    }
                //});
                var dataByKey = _.keyBy(data, op.idField);

                _.forEach(nonCached, function (nc, key) {
                    //var key = dt[op.idField].toString();

                    if (_.has(dataByKey, key)) {
                        var dt = dataByKey[key];
                        nc(dt);

                        if (op.cacheDuration !== 0) {
                            cache[key] = dt;
                        } else {
                            delete cache[key];
                        }
                    } else {
                        nc(null);
                    }
                });
            });
            return p;
        };

        if (op.deBounce > 0) {
            getJsonData = _.debounce(getJsonData, { wait: op.deBounce });
        }

        fn.cache = cache;
        fn.options = op;

        return fn;
    };


    /*
     * Collection
     */
    var Collection = function (keyField, createInstance) {
        if (arguments.length === 1) {
            this._createInstance = keyField;
            this._keyField = 'id';
        } else {
            this._createInstance = createInstance;
            this._keyField = keyField;
        }
        this._collection = {};
        this._promises = {};
        this._resolvers = {};
    };

    Collection.prototype.get = function (k) {
        var key = k.toString();
        var self = this;

        if (!_.has(self._collection, key)) {
            if (!_.has(self._promises, key)) {
                self._promises[key] = new Promise(function (resolve, reject) {
                    self._resolvers[key] = resolve;
                });
            }
            return self._promises[key];
        }

        return Promise.resolve(self._collection[key]);
    };

    Collection.prototype.getValue = function (k) {
        var key = k.toString();

        if (_.has(this._collection, key)) {
            return this._collection[key];
        }

        return null;
    };

    Collection.prototype.getValues = function (k) {
        return _.values(this._collection);
    };

    Collection.prototype.update = function (data, remove) {

        return new Promise(_.bind(function (resolve, reject) {
            var kf = this._keyField;
            var coll = this._collection;

            if (data === undefined) {
                return null;
            } else if (!_.isArray(data)) {
                data = [data];
            }

            var added = [];
            var changed = [];
            var removed = [];
            var keys = [];

            _.forEach(data, _.bind(function (d) {
                if (d === undefined || d === null) {
                    return;
                }
                try {
                    var instance;
                    var key = d[kf].toString();
                    keys.push(key);

                    if (_.has(coll, key)) {
                        instance = coll[key];

                        var c = false;
                        //var changedFields = [];

                        if (_.forOwn(instance, function (v, k) {
                            if (!_.isEqual(v, d[k])) {
                                c = true;
                                instance[k] = d[k];
                            }
                        }));

                        if (c) {
                            changed.push(instance);
                        }

                    } else {
                        instance = this._createInstance(d);
                        added.push(instance);
                        coll[key] = instance;
                        if (_.has(this._resolvers, key)) {
                            this._resolvers[key](instance);
                            delete this._resolvers[key];
                            delete this._promises[key];
                        }
                    }
                } catch (e) {
                    console.error(e);
                }
            },this));

            if (remove === true) {
                _.forEach(_.difference(_.keys(coll), keys), function (k) {
                    try {
                        removed.push(coll[k]);
                        delete coll[k];
                    } catch (e) {
                        console.error(e);
                    }
                });
            }

            resolve({ added: added, changed: changed, removed: removed });

            return null;
        }, this));
    };

    /*
     * Position
     */
    var Position = function (pos) {
        $.extend(this, pos);
    };

    /*
     * PositionsCollection
     */
    var PositionsCollection = function () {
        this._collection = {};
        this.lastWriteTime = null;
    };

    PositionsCollection.prototype = new Collection('shipId', function (data) {
        return new Position(data);
    });

    /*
     * Ship
     */
    var Ship = function (ship) {
        $.extend(this, ship);
    };

    /*
     * ShipsCollection
     */
    var ShipsCollection = function () {
        this._collection = {};
    };

    ShipsCollection.prototype = new Collection(function (data) {
        return new Ship(data);
    });

    /*
     * ShipDetails
     */
    var ShipDetails = function (shipdetails) {
        $.extend(this, shipdetails);
    };

    /*
     * ShipsCollection
     */
    var ShipDetailsCollection = function () {
        this._collection = {};
    };

    ShipDetailsCollection.prototype = new Collection('shipId', function (data) {
        return new ShipDetails(data);
    });

    /*
     * Company
     */
    var Company = function (company) {
        $.extend(this, company);
    };

    /*
     * CompaniesCollection
     */
    var CompaniesCollection = function () {
        this._collection = {};
    };

    CompaniesCollection.prototype = new Collection('name', function (data) {
        return new Company(data);
    });

    /*
     * Harbor
     */
    var Harbor = function (harbor) {
        $.extend(this, harbor);
    };


    /*
     * HarborsCollection
     */
    var HarborsCollection = function () {
        this._collection = {};
    };

    HarborsCollection.prototype = new Collection('id', function (data) {
        return new Harbor(data);
    });

    /*
    * Pier
    */
    var Pier = function (pier) {
        $.extend(this, pier);
    };

    /*
     * PiersCollection
     */
    var PiersCollection = function () {
        this._collection = {};
    };

    PiersCollection.prototype = new Collection('id', function (data) {
        return new Pier(data);
    });

    /*
     * Fascicle
     */
    var Fascicle = function (fascicle) {
        $.extend(this, fascicle);
    };

    /*
     * FasciclesCollection
     */
    var FasciclesCollection = function () {
        this._collection = {};
    };

    FasciclesCollection.prototype = new Collection('id', function (data) {
        return new Fascicle(data);
    });

    FasciclesCollection.prototype.getByShipId = function (shipid) {
        return _.orderBy(_.filter(this._collection, ['shipId', shipid]), 'eta');
    };

    var dataApi = {
        getShips: _createCachedDataGetter(API_SHIPS_URL, {
            cacheDuration: 60 * 5
        }),
        getShipDetails: (function (promises) {
            return function (shipid) {
                if (!_.has(promises, shipid)) {
                    promises[shipid] = new Promise(function (resolve, reject) {
                        $.getJSON(API_SHIPDETAILS_URL.replace('{shipId}', shipid)).then(resolve).catch(reject);
                    });
                }
                return promises[shipid];
            };
        })({}),
        getCompanies: _createCachedDataGetter(API_COMPANIES_URL, {
            cacheDuration: 60 * 10,
            idField: 'name'
        }),
        getHarbors: _createCachedDataGetter(API_HARBORS_URL, {
            cacheDuration: 60 * 60
        }),
        getPiers: _createCachedDataGetter(API_PIERS_URL, {
            cacheDuration: 60 * 60
        }),
        getCurrentPositions: (function() {

            var lastWrite = moment(0);

            return function () {

                return new Promise(function (resolve, reject) {
                    var data = {};
                    if (lastWrite.valueOf() > 0) {
                        data.minTime = lastWrite.format();
                    }
                    $.getJSON(API_POSITIONS_URL,data).then(function (data) {
                        _.forEach(data, function (pos) {
                            var wt = moment(pos.writeTime);
                            if (wt.isAfter(lastWrite)) {
                                lastWrite = wt;
                            }
                        });

                        resolve(data);
                    }).catch(reject);
                });
            };
        }()),

        getCurrentFascicles: (function () {
            var promise = null;

            return function () {

                if (promise === null) {
                    promise = $.getJSON(API_FASCICLES_URL + '?minHoursDiff=-1&maxHoursDiff=8');
                    _.delay(function () {
                        promise = null;
                    }, 60 * 1000);
                }

                return promise;
            };
        }()),

        getCurrentFasciclesByShipId: (function (promises) {
            return function (shipid) {
                if (!_.has(promises, shipid)) {
                    promises[shipid] = new Promise(function (resolve, reject) {
                        $.getJSON(API_SHIPFASCICLES_URL.replace('{shipId}', shipid)).then(resolve).catch(reject);
                    });
                }
                return promises[shipid];
            };
        })({}),

        getShipFascicles: function (shipid) {
            return $.getJSON(API_SHIPS_URL + '/' + shipid + '/fascicles');
        },

        getFasciclePmisData: function (fascicleid) {
            return $.getJSON(API_FASCICLES_URL + '/' + fascicleid + '/pmisdata');
        },

        getFasciclePmisCargoArrival: function (fascicleid) {
            return $.getJSON(API_FASCICLES_URL + '/' + fascicleid + '/pmis-merci-arrivo');
        },

        getFasciclePmisCargoDeparture: function (fascicleid) {
            return $.getJSON(API_FASCICLES_URL + '/' + fascicleid + '/pmis-merci-partenza');
        },

        getFasciclePmisDangerousGoods: function (fascicleid) {
            return $.getJSON(API_FASCICLES_URL + '/' + fascicleid + '/pmis-istanze-merci-pericolose');
        }
    };

    /*
     * Map
     */
    var Map = function (el, d) {
        var self = this;

        this.data = _.defaults(d, Map._defaultData);
        //var options = self.options = op || {};
        this.$el = $(el);

        this._shipsOnMap = {};

        this._positions = new PositionsCollection();
        this._ships = new ShipsCollection();
        this._companies = new CompaniesCollection();
        this._harbors = new HarborsCollection();
        this._fascicles = new FasciclesCollection();
        this._piers = new PiersCollection();
        this._shipDetails = new ShipDetailsCollection();
        this.markers = {};
        this.theme = THEME;
        this.visibleShips = [];
        this._searchOpen = false;

        this._paneOpen = false;

        this._followFocusedShip = true;
        //self._registeredEvents = {};

        _.forEach(this._events, _.bind(function (delegates, eventName) {
            var dlgts;
            if (_.isFunction(delegates)) {
                dlgts = { '': delegates };
            } else {
                dlgts = delegates;
            }

            _.forEach(dlgts, _.bind(function (handler, selector) {
                var boundhandler = _.bind(handler, this);

                if (selector === '') {
                    this.$el.on(eventName, boundhandler);
                } else {
                    this.$el.on(eventName, selector, boundhandler);
                }

                //self._registeredEvents[eventName] = buondhandler;
            },this));
        }, this));

        window.addEventListener('message', _.bind(function (ev) {
            try {

                var d = JSON.parse(ev.data);

                if (d.name === undefined && d.name === null) {
                    return;
                }

                switch (d.name) {
                    case 'resize':
                        if (this.map) {
                            getGmaps.then(function (gmaps) {
                                gmaps.event.trigger(this.map, 'resize');
                            });
                        }
                        break;

                    case 'setVisibleShips':
                        this.setVisibleShips.apply(this, d.params || []);
                        break;

                    case 'setTheme':
                        this.setTheme.apply(this, d.params || []);
                        break;

                    case 'setCenter':
                        this.setCenter.apply(this, d.params || []);
                        break;

                    case 'setZoom':
                        this.setZoom.apply(this, d.params || []);
                        break;
                }
            } catch (e) {
                console.warn(e);
            }
        }, this));

        this.mainMap = Map._instances.length === 0;

        if (this.mainMap) {
            $(window).on('hashchange', _.bind(function (ev) {
                var hash = _getLocationHash();

                if (hash !== _lastPushedHash) {
                    _lastPushedHash = hash;
                    this.$el.trigger('locationhashchanged', hash);
                }
            }, this));
        }

        //this._updateDataFromHash(_getLocationHash());

        //console.log(this.data.ship);

        Map._instances.push(this);

        this._render().then(_.bind(function () {
            this.start();
        },this));
    };

    Map._defaultData = {
        ship: null,
        visibleShips: [],
        companies: [],
        shipGroups: APP_MODE || KIOSK_MODE || TOTEM_MODE ? [SHIP_GROUP_PASSENGERS] : [],
        zoom: 10,
        center: {
            lat: 0,
            lng: 0
        }
    };

    Map._mapStyles = {
        'default': [{
            featureType: 'poi',
            elementType: 'labels',
            stylers: [{ visibility: 'off' }]
        }],
        'dark': [{
            featureType: 'poi',
            elementType: 'labels',
            stylers: [{ visibility: 'off' }]
        },
        { elementType: 'geometry', stylers: [{ color: '#242f3e' }] },
        { elementType: 'labels.text.stroke', stylers: [{ color: '#242f3e' }] },
        { elementType: 'labels.text.fill', stylers: [{ color: '#746855' }] },
        {
            featureType: 'administrative.locality',
            elementType: 'labels.text.fill',
            stylers: [{ color: '#d59563' }]
        },
        {
            featureType: 'poi',
            elementType: 'labels.text.fill',
            stylers: [{ color: '#d59563' }]
        },
        {
            featureType: 'poi.park',
            elementType: 'geometry',
            stylers: [{ color: '#263c3f' }]
        },
        {
            featureType: 'poi.park',
            elementType: 'labels.text.fill',
            stylers: [{ color: '#6b9a76' }]
        },
        {
            featureType: 'road',
            elementType: 'geometry',
            stylers: [{ color: '#38414e' }]
        },
        {
            featureType: 'road',
            elementType: 'geometry.stroke',
            stylers: [{ color: '#212a37' }]
        },
        {
            featureType: 'road',
            elementType: 'labels.text.fill',
            stylers: [{ color: '#9ca5b3' }]
        },
        {
            featureType: 'road.highway',
            elementType: 'geometry',
            stylers: [{ color: '#746855' }]
        },
        {
            featureType: 'road.highway',
            elementType: 'geometry.stroke',
            stylers: [{ color: '#1f2835' }]
        },
        {
            featureType: 'road.highway',
            elementType: 'labels.text.fill',
            stylers: [{ color: '#f3d19c' }]
        },
        {
            featureType: 'transit',
            elementType: 'geometry',
            stylers: [{ color: '#2f3948' }]
        },
        {
            featureType: 'transit.station',
            elementType: 'labels.text.fill',
            stylers: [{ color: '#d59563' }]
        },
        {
            featureType: 'water',
            elementType: 'geometry',
            stylers: [{ color: '#17263c' }]
        },
        {
            featureType: 'water',
            elementType: 'labels.text.fill',
            stylers: [{ color: '#515c6d' }]
        },
        {
            featureType: 'water',
            elementType: 'labels.text.stroke',
            stylers: [{ color: '#17263c' }]
        }]
    };

    Map._instances = [];

    var _searchBoxValueChanged = function (ev) {
        console.debug('change', ev);

        //var self = this;

        var search = _.trim($(ev.target).val());

        if (search !== this.data.search) {
            this.data.search = search;
            this.$el.trigger('searchchanged.map', [search]);
        }
    };

    /* ################# EVENTI ################# */
    Map.prototype._events = {

        'keyup.map': {
            '.map-search-box': _searchBoxValueChanged
        },

        'change.map': {
            '.map-search-box': _searchBoxValueChanged,

            '.filter-company-select': function (ev) {
                console.debug(ev);
                if (!ev.isDefaultPrevented()) {
                    ev.preventDefault();
                    this.data.companies = $(ev.target).val();
                    this.filterAll();
                }
            },

            '.filter-group-select': function (ev) {
                console.debug(ev);
                if (!ev.isDefaultPrevented()) {
                    ev.preventDefault();
                    this.data.shipGroups = _.map($(ev.target).val(), function (g) { return Number(g); });
                    this.filterAll();
                }
            }
        },

        'search.map': {
            '.map-search-box': _searchBoxValueChanged
        },

        'error': {
            'img': function (e) {
                alert(e);
            }
        },

        'click.map': {
            '.ship-fascicle button': function (ev) {
                var $btn = $(ev.currentTarget);
                var action = $btn.attr('action');
                var fascicleid = $btn.parents('.ship-fascicle').data('id');

                this._fascicles.get(fascicleid).then(_.bind(function (fascicle) {
                    switch (action) {
                        case 'show-pmis-cargo-arrival':
                            this._renderFasciclePmisArrivalCargo(fascicle);

                            break;
                        case 'show-pmis-cargo-departure':
                            this._renderFasciclePmisDepartureCargo(fascicle);

                            break;
                        case 'show-pmis-dangerous-goods':
                            this._renderFasciclePmisDangerousGoods(fascicle);

                            break;
                        default:
                            console.debug(ev);
                            break;
                    }
                }, this));
            },
            '.map-search .ship-list-item': function (ev) {
                if (!ev.isDefaultPrevented()) {
                    var $target = $(ev.target);

                    if (!$target.is('.ship-list-item')) {
                        $target = $target.parents('.ship-list-item').first();
                    }

                    this.focusShip($target.data('shipId'));
                }
            },
            '.map-tools-ship .follow-ship': function (ev) {
                if (!ev.isDefaultPrevented()) {
                    ev.preventDefault();
                    this.followFocusedShip(true);
                    this.map.panTo(this.markers[this.data.ship].getPosition());
                }
            },
            '.map-tools-ship .close-ship': function (ev) {
                if (!ev.isDefaultPrevented()) {
                    ev.preventDefault();
                    this.focusShip(null);
                }
            },
            '.map-ship-view .close': function (ev) {
                if (!ev.isDefaultPrevented()) {
                    ev.preventDefault();
                    this.closePane();
                }
            },
            '.map-tools-ship .form-control': function (ev) {
                if (!ev.isDefaultPrevented()) {
                    ev.preventDefault();
                    this.openPane();
                }
            },
            '.map-tools-search .toggle-search': function (ev) {
                if (!ev.isDefaultPrevented()) {
                    var $togglebtn = $(ev.target);
                    $togglebtn.prop('disabled', true);
                    this.togglePane().then(_.bind(function (paneOpen) {
                        this._searchOpen = paneOpen;
                        if (paneOpen) {
                            this._renderSearch();
                        }
                        $togglebtn.prop('disabled', false);
                    },this));
                }
            }
        },

        'started.followfocusedship': function (ev) {
            this.$el.find('.follow-ship').prop('disabled', true);
        },

        'stopped.followfocusedship': function (ev) {
            this.$el.find('.follow-ship').prop('disabled', false);
        },

        'searchchanged.map': function (ev, search) {
            var self = this;
            console.debug('searchboxchanged.map', search);

            self.filterAll();
        },

        'locationhashchanged.map': function (ev, hash) {
            console.debug('locationhashchanged.map', hash);

            //_setHistoryHash('');
            this._updateDataFromHash(hash);
        },

        'loaded.map': function (ev, map) {
            console.debug('loaded.map', arguments);
            //this.$el.removeClass('loading');
        },

        'populated.map': function (ev, updated) {
            console.debug('populated.map', arguments);
            this._updateDataFromHash(_getLocationHash());
            //console.log(this.data.ship);
            if (this.data.shipGroups.length > 0) {
                this.filterAll(true);
            }
            if (this.data.ship !== undefined && this.data.ship !== null) {
                var shipid = this.data.ship;
                if (this._ships.getValue(shipid) !== null) {
                    this.openPane();
                    this.focusShip(shipid);
                } else {
                    showAlert(ALERT_LEVEL_WARNING, { it: "La nave richiesta non &egrave; al momento disponibile su mappa", en: "The requested ship is not available at the moment" }[lang] );
                    this.data.ship = null;
                }
            }
            //self.$el.addClass('map-ready');
            this.$overlay.fadeOut();
            if (window.parent && window.parent.postMessage) {
                window.parent.postMessage("mapready", "*");
            }
        },

        'updated.positions': function (ev, updated, coll) {
            console.debug('updated.positions', arguments);

            var addorupdate = _.concat(updated.added, updated.changed);

            if (addorupdate.length > 0) {
                Promise.all(_.map(addorupdate, _.bind(function (position) {
                    return this.addOrUpdateMarker(position.shipId);
                }, this))).finally(this._mapPopulated ? _.noop : _.bind(function () {
                    this._mapPopulated = true;
                    this.$el.trigger('populated.map');
                },this));
            }

            this.updateShips(_.map(updated.added, 'shipId'), false);

            _.forEach(updated.removed, _.bind(function (position) {
                this.removeMarker(position.shipId);
            }, this));
        },

        'updated.ships': function (ev, updated, coll) {
            console.debug('updated.ships', arguments);

            var newCompanies = [];

            _.forEach(updated.added, _.bind(function (ship) {
                var companyName = ship.companyName;

                if (companyName !== null && !this._companies.get(companyName).isFulfilled()) {
                    newCompanies.push(companyName);
                }
            },this));

            if (newCompanies.length) {
                dataApi.getCompanies(newCompanies).then(_.bind(function (data) {
                    this._companies.update(data, false).then(_.bind(function (updated) {
                        if (updated.added.length + updated.changed.length + updated.removed.length > 0) {
                            this.$el.trigger('updated.companies', [updated, this._companies]);

                            if (this._searchOpen && this.focusedShip === null) {
                                this._renderSearch();
                            }
                        }
                    }, this));
                }, this));
            }
        },

        'updated.fascicles': function (ev, updated, coll) {
            console.debug('updated.positions', arguments);
            var newstuff = { piers: [], harbors: [] };

            if (updated.added.length > 0) {
                _.forEach(updated.added, _.bind(function (fascicle) {
                    if (_.indexOf(this.harbors, fascicle.harborId) < 0) {
                        this.harbors.push(fascicle.harborId);
                    }
                    if (fascicle.provenanceHarborId !== null && _.indexOf(this.harbors, fascicle.provenanceHarborId) < 0) {
                        this.harbors.push(fascicle.provenanceHarborId);
                    }
                    if (fascicle.destinationHarborId !== null && _.indexOf(this.harbors, fascicle.destinationHarborId) < 0) {
                        this.harbors.push(fascicle.destinationHarborId);
                    }

                    if (fascicle.expectedPierId !== null && _.indexOf(this.piers, fascicle.expectedPierId) < 0) {
                        this.piers.push(fascicle.expectedPierId);
                    }

                    if (fascicle.actualPierId !== null && _.indexOf(this.piers, fascicle.actualPierId) < 0) {
                        this.piers.push(fascicle.actualPierId);
                    }
                }, newstuff));

                if (newstuff.harbors.length > 0) {
                    dataApi.getHarbors(newstuff.harbors).then(_.bind(function (harbors) {
                        this._harbors.update(harbors);
                    },this));
                }

                if (newstuff.piers.length > 0) {
                    dataApi.getPiers(newstuff.piers).then(_.bind(function (piers) {
                        this._piers.update(piers);
                    },this));
                }
            }
        },

        'updated.companies': function (ev, updated, coll) {
            console.debug('updated.companies', arguments);
        },

        'clicked.map': function (ev, mapsEv) {
            console.debug('clicked.map', arguments);

            this.closePane();
            //self.focusShip(null);
            //self.map.panTo(mapsEv.latLng);
        },

        'dragstart.map': function (ev, mapsEv) {
            console.debug('dragstart.map', arguments);
            var self = this;

            self.followFocusedShip(false);
            //self.map.panTo(mapsEv.latLng);
        },

        'boundschanged.map': function (ev, mapsEv) {
            console.debug('boundschanged.map', arguments);
            var map = this.map;

            //console.log({ center: { lat: map.center.lat(), lng: map.center.lng() }, zoom: map.zoom});
            //self.map.panTo(mapsEv.latLng);
        },

        'clicked.marker': function (ev, ship, marker) {
            console.debug('clicked.marker', arguments);
            var self = this;

            if (self.data.ship !== ship.id) {
                self.focusShip(ship.id);
            } else {
                //self.map.panTo(marker.getPosition());
                self.openPane();
            }
        },

        'added.marker': function (ev, marker, ship) {
            console.debug('added.marker', arguments);
            this.$el.trigger('shown.ship', [ship]);
        },

        'updated.marker': function (ev, marker, ship) {
            console.debug('updated.marker', arguments);

            if (this.followFocusedShip() && ship.id === this.data.ship) {
                this.map.setCenter(marker.getPosition());
            }
        },

        'removed.marker': function (ev, marker, ship) {
            console.debug('removed.marker', arguments);
            this.$el.trigger('hidden.ship', [ship]);
        },

        'focused.ship': function (ev, ship, marker) {
            console.debug('focused.ship', arguments);

            marker.labelClass = 'ship-marker-label focused';
            this.followFocusedShip(true);
            this.map.panTo(marker.getPosition());

            this.$mapToolsShipName.html(ship.name);
        },

        'blurred.ship': function (ev, ship, marker) {
            console.debug('blurred.ship', arguments);

            marker.labelClass = 'ship-marker-label';
            marker.setPosition(marker.getPosition());
        },

        'shown.ship': function (ev, ship, marker) {
            console.debug('shown.ship', arguments);
            this.visibleShips.push(ship.id);
        },

        'hidden.ship': function (ev, ship, marker) {
            console.debug('hidden.ship', arguments);
            _.pull(this.visibleShips, ship.id);

            if (this.data.ship === ship.id) {
                this.focusShip(null);
            }
        }
    };

    Map.prototype.updatePositions = function () {
        return new Promise(_.bind(function (resolve, reject) {
            dataApi.getCurrentPositions().then(_.bind(function (positionsData) {
                this._positions.update(positionsData).then(_.bind(function (updated) {
                    if (updated.added.length + updated.changed.length + updated.removed.length > 0) {
                        this.$el.trigger('updated.positions', [updated, this._positions]);
                    }
                    resolve();
                    return null;
                }, this)).catch(reject);
                return null;
            }, this)).catch(reject);
        },this));
    };

    Map.prototype.updateShips = function (ids) {
        return new Promise(_.bind(function (resolve, reject) {
            dataApi.getShips(ids).then(_.bind(function (ships) {
                return this._ships.update(ships, false).then(_.bind(function (updated) {
                    if (updated.added.length + updated.changed.length > 0) {
                        this.$el.trigger('updated.ships', [updated, this._ships]);
                    }
                    resolve();

                    return null;
                }, this)).catch(reject);
            }, this)).catch(reject);
        }, this));
    };

    //Map.prototype.updatePiers = (function (ids) {
    //    var self = this;

    //    return new Promise(function (resolve, reject) {
    //        console.log(self);

    //        dataApi.getPiers(ids).then(function (piers) {
    //            self._piers.update(piers, false).then(function (updated) {
    //                if (updated.added.length + updated.changed.length > 0) {
    //                    self.$el.trigger('updated.piers', [updated, self._ships]);
    //                }
    //                resolve();
    //            }).catch(reject);
    //        }).catch(reject);
    //    });
    //})();

    Map.prototype.updateFascicles = (function () {
        var last = new Date(0);
        var pr;

        return function (shipid) {
            var self = this;

            var dt = new Date(Date.now() - 0);//FASCICLES_UPDATE_INTERVAL);

            if (dt > last) {
                last = Date.now();

                pr = new Promise(function (resolve, reject) {
                    var fn = shipid ? dataApi.getCurrentFasciclesByShipId : dataApi.getCurrentFascicles;

                    fn(shipid).then(function (fascicles) {
                        self._fascicles.update(fascicles).then(function (updated) {
                            if (updated.added.length + updated.changed.length > 0) {
                                self.$el.trigger('updated.fascicles', [updated, self._fascicles]);
                            }
                            resolve();
                        }).catch(reject);
                    }).catch(reject);
                });
            }

            return pr;
        };
    })();

    Map.prototype.addOrUpdateMarker = function (shipid) {
        var self = this;

        return new Promise(function (resolve, reject) {
            Promise.join(self._positions.get(shipid), self._ships.get(shipid), function (position, ship) {
                var marker = self.markers[shipid];
                if (marker === undefined) {
                    (ship.companyName === null ? Promise.resolve(null) : self._companies.get(ship.companyName)).then(function (company) {

                        Promise.join(loadIcon(company === null ? MAP_DEFAULT_SHIP_ICON_URL : company.shipIconUrl), self.filter(ship.id), getGmaps, function (icon, visible) {
                            _.defer(function () {
                                try {
                                    var marker = new MarkerWithLabel({
                                        position: position,
                                        map: visible ? self.map : null,
                                        icon: icon,
                                        labelAnchor: iconLabelOrigin,
                                        labelContent: ship.name,
                                        labelClass: 'ship-marker-label', // your desired CSS class
                                        labelInBackground: true,
                                        labelVisible: true
                                    });

                                    marker.addListener('click', _.partial(function (s, m, ev) {
                                        self.$el.trigger('clicked.marker', [s, m]);
                                    }, ship, marker));

                                    self.markers[shipid] = marker;

                                    self.$el.trigger('added.marker', [marker, ship]);

                                    resolve(marker, ship);
                                } catch (e) {
                                    reject(e);
                                }
                            });

                            return null;
                        }).catch(reject);

                        return null;
                    }).catch(reject);

                } else {
                    _.defer(function () {
                        marker.setPosition(position);
                        resolve(marker);
                        self.$el.trigger('updated.marker', [marker, ship]);
                    });
                }
                return null;
            }).catch(reject);
        }).catch(console.warn);
    };

    Map.prototype.removeMarker = function (shipid) {
        var self = this;
        var marker = self.markers[shipid];

        if (marker === undefined) {
            return;
        }

        Promise.join(getGmaps, self._ships.get(shipid), _defer(function (gmaps, ship) {
            gmaps.event.clearListeners(marker, 'click');
            marker.setMap(null);
            self.$el.trigger('removed.marker', [marker, ship]);
            delete self.markers[shipid];
        }));
    };

    Map.prototype.focusShip = function (shipid) {
        var self = this;

        return new Promise(function (resolve, reject) {
            var shipToBlurId = self.data.ship;

            if (shipToBlurId === shipid) {
                return resolve();
            }

            if (shipToBlurId !== null) {
                var blurredShip = self._ships.getValue(shipToBlurId);

                self.$el.trigger('blurred.ship', [blurredShip, self.markers[shipToBlurId]]);
            }

            if (shipid !== null) {
                //if (_.indexOf(self.markers, shipid) < 0) {
                //    alert({ it: 'Nave non trovata', en:'Ship not found'}[lang]);
                //    self.focusShip(null);
                //    return resolve();
                //}

                var focusedShip = self._ships.getValue(shipid);

                if (focusedShip !== null) {
                    self.$el.trigger('focused.ship', [focusedShip, self.markers[shipid]]);
                    self.followFocusedShip(true);
                } else {
                    return resolve();
                }
            }

            self.data.ship = shipid;
            if (!KIOSK_MODE) {
                self._showShipView(shipid);
                console.debug("Open Pane");
            }

            resolve();
        });
    };

    Map.prototype.openPane = function () {
        return this._openClosePane(true);
    };

    Map.prototype.closePane = function () {
        return this._openClosePane(false);
    };

    Map.prototype.togglePane = function () {
        return this._openClosePane(null);
    };

    Map.prototype._openClosePane = function (open) {
        if (KIOSK_MODE) {
            return Promise.resolve(false);
        }
        var self = this;
        var po = self._paneOpen;
        var $pane = self.$pane;

        self._paneOpen = new Promise(function (resolve, reject) {
            Promise.join(po, function (paneopen) {

                var startcls, transitioncls, endcls;
                if (open === null) {
                    open = !paneopen;
                }

                if (open === paneopen) {
                    resolve(open);
                } else {
                    var $el = self.$el;

                    startcls = open ? 'closed' : 'opened';
                    endcls = open ? 'opened' : 'closed';
                    transitioncls = open ? 'opening' : 'closing';

                    $pane.removeClass(startcls);
                    $el.removeClass('pane-' + startcls);

                    $el.trigger(transitioncls + '.pane');

                    if (Modernizr.csstransitions) {
                        $pane.addClass(transitioncls);
                        $el.addClass('pane-' + transitioncls);

                        $pane.one('transitionend', function () {
                            $pane.removeClass(transitioncls);
                            $el.removeClass('pane-' + transitioncls);
                            $pane.addClass(endcls);
                            $el.addClass('pane-' + endcls);
                            $el.trigger(endcls + '.pane');

                            resolve(open);
                        });
                    } else {
                        $pane.addClass(endcls);
                        $el.trigger(endcls + '.pane');
                        $el.addClass('pane-' + endcls);
                        resolve(open);
                    }
                }
            });
        });

        return self._paneOpen;
    };

    Map.prototype._setData = function (path, value, pushState) {
        var self = this;

        if (_.isPlainObject(path)) {
            _.forEach(path, function (v, p) {
                _.set(self.data, p, v);
            });
        } else {
            _.set(self.data, path, value);
        }

        if (self.mainMap) {
            _setHistoryHash(decodeURIComponent($.param(this.data, pushState)));
        }
    };

    Map.prototype._updateDataFromHash = function (hash) {
        var data = deparam(hash);
        var self = this;
        var ship, zoom, center, search, types;

        if (!_.isPlainObject(data)) {
            return;
        }

        _.forEach(data, function (v, k) {
            switch (k) {
                case 'ship':
                    ship = Number(v);

                    break;
                case 'zoom':
                    zoom = Number(v);

                    break;
                case 'center':
                    center = {
                        lat: Number(v.lat),
                        lng: Number(v.lng)
                    };

                    break;

                case 'search':
                    search = _.trim(v);

                    break;

                case 'types':
                    types = _.map(v.split(','), function (gr) {
                        return Number(_.trim(gr));
                    });

                    break;
            }
        });

        if (ship !== undefined) {
            self.focusShip(ship);
            self.data.ship = ship;
        }

        if (center !== undefined) {
            try {
                self.map.panTo(center);
                self.data.center = center;
            } catch (e) {
                console.error(e);
            }
        }

        if (zoom !== undefined) {
            self.map.setZoom(zoom);
            self.data.zoom = zoom;
        }

        if (types !== undefined) {
            self.data.shipGroups = types;
            //self.$filterGroupSelect.val(types).trigger('search.map');
        }

        if (search !== undefined) {
            self.$searchBox.val(search).trigger('search.map');
        }
    };

    //Map.prototype._updateHashFromData = function () {
    //    _setHistoryHash($.param(this.data));
    //};

    Map.prototype.followFocusedShip = function (value) {
        if (value === undefined) {
            return this._followFocusedShip;
        }

        if (value === true && this._followFocusedShip === false) {
            this._followFocusedShip = true;
            this.$el.trigger('started.followfocusedship');
        } else if (value === false && this._followFocusedShip === true) {
            this._followFocusedShip = false;
            this.$el.trigger('stopped.followfocusedship');
        }
    };

    Map.prototype.filterAll = function (nopan) {
        var self = this;
        if (self._filterAll === undefined) {
            self._filterAll = _.debounce(function () {
                self.followFocusedShip(false);

                return new Promise(function (resolve, reject) {

                    Promise.mapSeries(_.map(self.markers, function (m, s) { return [m, Number(s)]; }), function (ms) {
                        var marker = ms[0];
                        var shipid = ms[1];

                        return new Promise(function (res, rej) {
                            self.filter(shipid).then(function (result) {
                                self._ships.get(shipid).then(function (ship) {
                                    if (result === true) {
                                        if (marker.getMap() === null) {
                                            marker.setMap(self.map);
                                            self.$el.trigger('shown.ship', [ship, marker]);
                                        }
                                    } else {
                                        if (marker.getMap() !== null) {
                                            marker.setMap(null);
                                            self.$el.trigger('hidden.ship', [ship, marker]);
                                        }
                                    }
                                    res({
                                        marker: marker,
                                        ship: ship,
                                        result: result
                                    });
                                }).catch(rej);

                                return null;

                            }).catch(rej);

                            return null;
                        });

                    }).then(function (results) {
                        var positive = _.filter(results, 'result');

                        var n = Infinity;
                        var w = Infinity;
                        var s = -Infinity;
                        var e = -Infinity;

                        var l = positive.length;

                        if (nopan || l === 0) {
                            _.noop();
                        } else if (l > 1) {
                            _.forEach(positive, function (pos) {
                                var latlng = pos.marker.getPosition();

                                var lat = latlng.lat();
                                var lng = latlng.lng();

                                n = Math.min(n, lat);
                                w = Math.min(w, lng);
                                s = Math.max(s, lat);
                                e = Math.max(e, lng);
                            });

                            //console.log(n, w, s, e);

                            self.map.fitBounds({
                                north: n - 0.01,
                                south: s + 0.01,
                                east: e + 0.01,
                                west: w - 0.01
                            });
                        } else  {
                            self.map.panTo(positive[0].marker.getPosition());
                            self.map.setZoom(12);
                        }

                        self._renderShipsList().then(function () {
                            resolve(positive);
                        });

                        return null;
                    }).catch(reject);
                });
            }, DEFAULT_DEBOUNCE_WAIT);
        }

        return self._filterAll();
    };

    Map.prototype.setTheme = function (theme) {
        $body.removeClass('theme-' + this.theme);
        $body.addClass('theme-' + theme);
        this.map.setMapTypeId(theme);
        this.theme = theme;
    };

    Map.prototype.setCenter = function (lat,lng) {
        this.map.setCenter({ lat, lng });
    };

    Map.prototype.setZoom = function (zoom) {
        this.map.setZoom(zoom);
    };

    Map.prototype.filter = function (shipid) {
        var self = this;

        return new Promise(function (resolve) {
            Promise.reduce(Map.filters,
                function (val, filter, index, length) {
                    if (val === false) {
                        return val;
                    }

                    return _.bind(filter, self)(shipid, self.data);
                },
                true
            ).then(resolve).catch(function (msg) {
                console.warn(msg);

                resolve(true);
            });
        });
    };

    Map.prototype._renderSearch = function () {
        var self = this;

        if (self.$search === null) {
            self.$search = $('<div class="map-search" />').prependTo(self.$pane).hide();
        }

        return new Promise(function (resolve, reject) {
            Promise.join(self.$search, self._paneOpen, getTemplate('search'), function ($search, paneOpen, tmpl) {

                var selectedCompanies = self.data.companies;
                var selectedShipGroups = self.data.shipGroups;

                var filterCompanies = _.map(self._companies.getValues(), function (company) {
                    return {
                        company: company,
                        selected: _.indexOf(selectedCompanies, company.name) !== -1
                    };
                });

                var filterShipGroups = _.map(shipGroups, function (name, value) {
                    return {
                        name: name,
                        value: value,
                        selected: _.indexOf(selectedShipGroups, Number(value)) !== -1
                    };
                });

                $search.html(tmpl({
                    filterCompanies: filterCompanies,
                    filterShipGroups: filterShipGroups
                }));

                self.$filterCompanySelect = $search.find('filter-company-select');
                self.$filterGroupSelect = $search.find('filter-group-select');

                $search.find('.selectpicker').selectpicker();

                if (paneOpen) {
                    if ($search.is(':hidden')) {
                        $search.fadeIn();
                    }
                } else {
                    $search.show();
                    self.openPane();
                }

                self._renderShipsList().then(resolve);
            });
        });
    };


    Map.prototype._renderShipsList = function () {
        var self = this;

        if (self.data.ship !== null || self._searchOpen === false) {
            return Promise.resolve();
        }

        return new Promise(function (resolve, reject) {

            var shipviews = _.map(self.visibleShips, function (shipid) {
                var ship = self._ships.getValue(shipid);
                var company;

                if (ship.companyName !== null) {
                    company = self._companies.getValue(ship.companyName);
                } else {
                    company = null;
                }

                return {
                    ship: ship,
                    company: company
                };
            });

            Promise.join(getTemplate('shipslist'), function (tmpl) {
                self.$search.find('.map-ships-list').html(tmpl({ shipViews: _.orderBy(shipviews, 'ship.name') }));
                resolve();
            });
        });
    };

    Map.prototype._showShipView = function (shipid) {
        var self = this;

        return new Promise(function (resolve, reject) {
            var $previous = self.$shipView || null;

            Promise.join(self._paneOpen, function (paneOpen) {
                if (shipid) {
                    if ($previous === null) {
                        self.$mapToolsSearch.fadeOut();
                        self.$mapToolsShip.fadeIn();
                    }

                    Promise.join(self._ships.get(shipid), getTemplate('shipview'), function (ship, tmpl) {

                        self.$shipView = $view = $('<div class="map-ship-view" />');

                        var compr = ship.companyName === null ? Promise.resolve(null) : self._companies.get(ship.companyName);
                        var detpr = self._shipDetails.get(ship.id);
                        var imgpr;
                        var company;
                        var details;
                        var photoReady = false;

                        if (compr.isFulfilled()) {
                            company = compr.value();
                        } else {
                            company = new Company({
                                name: ship.companyName,
                                logoUrl: MAP_DEFAULT_COMPANY_LOGO_URL
                            });

                            compr.then(function (company) {
                                var $company;
                                if (self.data.ship === shipid) {
                                    $view.detach();

                                    $company = $view.find('.ship-company');
                                    $company.find('img').attr('src', company.logoUrl);
                                    $company.find('span').html(company.name);
                                    self.$pane.append($view);
                                }
                            });

                            dataApi.getCompanies([company]).then(function (data) {
                                self._companies.update(data, false);
                            });
                        }

                        if (detpr.isFulfilled()) {
                            details = detpr.value();
                            if (details.photoUrl !== null) {
                                imgpr = preLoadImage(details.photoUrl);
                            }
                        } else {
                            details = new ShipDetails({
                                photoUrl: ''
                            });

                            detpr.then(function (details) {
                                var $photo;

                                if (details.photoUrl !== null) {
                                    $photo = $view.find('.ship-photo');
                                    if (self.data.ship === shipid) {
                                        $photo.css('backgroundImage', 'url(' + details.photoUrl + ')');
                                    }
                                    imgpr = preLoadImage(details.photoUrl).then(function () {
                                        $photo.removeClass('loading');
                                    });
                                }
                            });

                            dataApi.getShipDetails(ship.id).then(function (data) {
                                self._shipDetails.update(data, false);
                            });
                        }

                        if (imgpr !== undefined) {
                            if (imgpr.isFulfilled()) {
                                photoReady = true;
                            } else {
                                imgpr.then(function () {
                                    if (self.data.ship === shipid) {
                                        $view.find('.ship-photo').removeClass('loading');
                                    }
                                });
                            }
                        }

                        $view.html(tmpl({ ship: ship, company: company, details: details, photoReady: photoReady }));

                        self.$pane.append($view);

                        if (paneOpen) {
                            $view.hide().fadeIn({
                                complete: function () {
                                    if ($previous !== null) {
                                        $previous.remove();
                                    }
                                }
                            });
                        } else {
                            self.openPane();
                            if ($previous !== null) {
                                $previous.remove();
                            }
                        }

                        self._renderShipActualFascicle();

                        resolve();
                    }).catch(reject);
                } else {

                    if ($previous !== null) {
                        self.$shipView = null;
                        self.$mapToolsSearch.show();
                        self.$mapToolsShip.fadeOut();

                        if (self._searchOpen) {
                            self._renderSearch().then(function () {
                                $previous.fadeOut(function () {
                                    $previous.remove();
                                });
                            });
                        } else {
                            self.closePane().then(function () {
                                $previous.remove();
                            });
                        }
                        //$previous.fadeOut().remove();
                    }
                }

            });

        });
    };

    Map.prototype._renderShipActualFascicle = function () {
        var self = this;
        var $shipView = self.$shipView;
        var $shipPhoto = $shipView.find('.ship-photo');
        var $fascicleview = $shipView.find('.ship-fascicle');


        return new Promise(function (resolve, reject) {
            var shipid = self.data.ship;

            if (shipid === null) {
                resolve();
                return;
            }

            self.updateFascicles(shipid).then(function () {
                var fascicles = self._fascicles.getByShipId(shipid);
                var now = moment();

                Promise.reduce(fascicles, function (previous, fascicle) {

                    if (previous === null || previous.deleted) {
                        return fascicle;
                    }

                    if (fascicle.ata !== null) {
                        return fascicle;
                    } else if (previous.unmooring === null) {
                        return previous;
                    } else {
                        var fdiff = Math.abs(now.diff(fascicle.ATA));
                        var pdiff = Math.abs(now.diff(previous.unmooring));
                        if (fdiff > pdiff) {
                            return previous;
                        }
                    }

                    return fascicle;

                }, null).then(function (fascicle) {

                    if (fascicle === null) {
                        var msg = {
                            it: 'Non sono stati trovati dati di viaggio per questa nave.',
                            en: 'No voyage data found for this ship.'
                        }[lang];
                        $fascicleview.html('<div class="alert alert-warning" role="alert">' + msg + '</div>');
                        resolve("");
                        return;
                    }

                    getTemplate('fascicleview').then(function (tmpl) {
                        var nullpr = Promise.resolve(null);
                        var hpr = self._harbors.get(fascicle.harborId);
                        var phpr = fascicle.provenanceHarborId === null ? nullpr : self._harbors.get(fascicle.provenanceHarborId);
                        var dhpr = fascicle.destinationHarborId === null ? nullpr : self._harbors.get(fascicle.destinationHarborId);
                        var arrived = fascicle.mooring !== null;
                        var departed = fascicle.unmooring !== null;
                        var pierid = arrived ? fascicle.actualPierId : fascicle.expectedPierId;

                        var ppr = pierid === null ? nullpr : self._piers.get(pierid);

                        Promise.join(hpr, phpr, dhpr, ppr, function (harbor, provenance, destination, pier) {

                            var arrival = moment(arrived ? fascicle.mooring : fascicle.eta);
                            var departure = moment(departed ? fascicle.unmooring : fascicle.etd);

                            //var arrivalTime = mnow.diff(arrival) == 0? arrival.format('LT') : arrival.format('LLL');
                            //var departureTime = moment(departure).format('LT');

                            var pierName = pier === null ? pierid : pier.nameOnBillboard;

                            $fascicleview.data('id', fascicle.id);

                            $fascicleview.html(tmpl({
                                harbor: harbor,
                                fascicle: fascicle,
                                provenanceHarbor: provenance,
                                destinationHarbor: destination,
                                arrived: arrived,
                                departed: departed,
                                arrival: arrival,
                                departure: departure,
                                pierName: pierName
                            }));

                            if (!APP_MODE) {
                                var $notificationsButton = $('<a><i class="fa fa-bell"></i></a>');
                                var notificationsUrl = FASCICLE_NOTIFICATIONS_URL_TMPL.replace('0', fascicle.id);

                                $notificationsButton.prop({
                                    title: { 'it': 'Notifiche', 'en': 'Notifications' }[lang],
                                    href: notificationsUrl,
                                    className: 'btn ship-notifications-button',
                                    target: '_blank'
                                });

                                $notificationsButton.addClass(departed ? 'btn-secondary' : 'btn-primary');

                                $notificationsButton.insertAfter($shipPhoto);
                            }
                            
                            if (_.indexOf(MONICA_FEATURES, 'DATI_PMIS') >= 0) {
                                dataApi.getFasciclePmisData(fascicle.id).then(function (pmisdata) {
                                    if (pmisdata !== null) {
                                        getTemplate('shippmisdataview').then(function (pmistmpl) {
                                            var $pmisDataContainer = $fascicleview.find('.fascice-pmis-data');
                                            $pmisDataContainer.html(pmistmpl({ pmisdata: pmisdata }));
                                            resolve();
                                        });
                                    } else {
                                        resolve();
                                    }
                                });
                            } else {
                                resolve();
                            }

                        }).catch(reject);
                    });
                });
            });
        }).catch(function () {
            var loadingHtml = $fascicleview.html();

            var msg = {
                it: 'Si &egrave; verificato un problema durante l\'acquisizione dei dati.',
                en: 'A problem occurred while acquiring data.'
            }[lang];

            var retryText = { it: 'Riprova', en: 'Retry' }[lang];

            var $retryButton = $('<button class="btn btn-light" type="button">' + retryText + '</button>');

            $fascicleview.html('');

            var $alert = $('<div class="alert alert-danger" role="alert">' + msg + ' </div>');

            $alert.appendTo($fascicleview);
            $retryButton.appendTo($alert);

            $retryButton.click(function () {
                $fascicleview.html(loadingHtml);
                self._renderShipActualFascicle();
            });
        });
    };

    Map.prototype._renderFasciclePmisArrivalCargo = function (fascicle) {
        var self = this;

        Promise.join(fascicle, this._ships.get(fascicle.shipId), dataApi.getFasciclePmisCargoArrival(fascicle.id), getTemplate('fasciclecargoview'), getTemplate('modal'), _.bind(function (fascicle, ship, data, tmpl, modalTmpl) {
            var body = tmpl({ cargo: data });

            self.$modalContent.html(modalTmpl({
                title: { it: 'PMIS Cargo Arrivo', en: 'PMIS Cargo Arrival' }[lang],
                body: body
            }));
            self.$modal.modal('show');
        }, this));
    };

    Map.prototype._renderFasciclePmisDepartureCargo = function (fascicle) {
        Promise.join(fascicle, this._ships.get(fascicle.shipId), dataApi.getFasciclePmisCargoDeparture(fascicle.id), getTemplate('fasciclecargoview'), getTemplate('modal'), _.bind(function (fascicle, ship, data, tmpl, modalTmpl) {
            var body = tmpl({ cargo: data });

            this.$modalContent.html(modalTmpl({
                title: { it: 'PMIS Cargo Partenza', en: 'PMIS Cargo Departure' }[lang],
                body: body
            }));

            this.$modal.modal('show');
        }, this));
    };

    Map.prototype._renderFasciclePmisDangerousGoods = function (fascicle) {
        Promise.join(fascicle, this._ships.get(fascicle.shipId), dataApi.getFasciclePmisDangerousGoods(fascicle.id), getTemplate('fascicledgview'), getTemplate('modal'), _.bind(function (fascicle, ship, data, tmpl, modalTmpl) {
            var body = tmpl({ dg: data });

            this.$modalContent.html(modalTmpl({
                title: { it: 'PMIS Merci Pericolose', en: 'PMIS Dangerous Goods' }[lang],
                body: body
            }));

            this.$modal.modal('show');
        }, this));
    };

    Map.prototype.start = function () {
        var self = this;

        return new Promise(function (resolve, reject) {
            self.updatePositions().then(resolve).finally(function () {
                return Promise.delay(MAP_UPDATE_INTERVAL).then(function () {
                    self.start();
                    return null;
                });
            }).catch(reject);
        });
    };

    Map.prototype._render = function () {
        var self = this;
        var $el = self.$el;


        if (!$el.hasClass('map')) {
            $el.addClass('map');
        }

        self._mapPopulated = false;

        return new Promise(function (resolve, reject) {
            getTemplate('map').then(function (mapTmpl) {
                $el.html(mapTmpl());
                //$el.addClass('loading');

                self.$search = null;

                self.$container = $el.find('.map-container');

                self.$searchBox = $el.find('.map-search-box');
                self.$pane = $el.find('.map-pane');
                self.$overlay = $el.find('.map-overlay');
                var $modal = self.$modal = $el.find('.map-modal');
                $modal.modal({ focus: false, show: false });
                self.$modalContent = $modal.find('.modal-content');

                self.$canvas = $el.find('.map-canvas');

                self.$mapToolsSearch = $el.find('.map-tools-search');
                self.$mapToolsShip = $el.find('.map-tools-ship');

                self.$mapToolsShip.hide();

                self.$mapToolsShipName = $el.find('.map-tools-ship-name');

                return getGmaps.then(function (gmaps) {

                    self.map = new gmaps.Map(self.$canvas.get(0), {
                        zoom: self.data.zoom || 10,
                        center: self.data.center,
                        disableDefaultUI: true,
                        mapTypeControl: false,
                        //mapTypeControlOptions: {
                        //    style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
                        //    position: google.maps.ControlPosition.TOP_CENTER
                        //},
                        zoomControl: !KIOSK_MODE&&!TOTEM_MODE,
                        //zoomControlOptions: {
                        //    position: google.maps.ControlPosition.LEFT_CENTER
                        //},
                        scaleControl: false,
                        streetViewControl: false,
                        fullscreenControl: false,
                        clickableIcons: false,
                        gestureHandling: 'greedy',
                        //backgroundColor: $body.css('background-color'),
                        //styles: Map._mapStyles[self.theme]
                    });

                    _.each(Map._mapStyles, function (style, name) {
                        self.map.mapTypes.set(name, new google.maps.StyledMapType(style, { name: name }));
                    });
                    self.map.setMapTypeId(self.theme);

                    self.$container.hide().fadeIn();

                    self.map.addListener('tilesloaded', function () {
                        self.$el.trigger('loaded.map', [self.map]);
                        gmaps.event.clearListeners(self.map, 'tilesloaded');
                    });

                    self.map.addListener('click', function (ev) {
                        self.$el.trigger('clicked.map', [ev]);
                    });

                    self.map.addListener('dragstart', function (ev) {
                        self.$el.trigger('dragstart.map', [ev]);
                    });

                    self.map.addListener('bounds_changed', function (ev) {
                        self.$el.trigger('boundschanged.map', [ev]);
                    });

                    self.$el.addClass(['pane-closed']);
                    //self.$container.hide();

                    resolve();

                    return null;
                }).catch(reject);
            }).catch(reject);
        });
    };

    Map.prototype.setVisibleShips = function (ships) {
        this.data.visibleShips = ships;
        return this.filterAll(true);
    };

    Map.filters = [
        function (shipId, data) {//FILTER SEARCH
            var self = this;
            return new Promise(function (resolve, reject) {
                var search = data.search;

                if (search === undefined || search === null || search.length === 0) {
                    resolve(true);
                    return;
                }

                var s = search.toLowerCase();

                self._ships.get(shipId).then(function (ship) {

                    if (ship.name.toLowerCase().indexOf(s) >= 0 || ship.imo.indexOf(s) >= 0) {
                        resolve(true);
                        return;
                    }

                    resolve(false);
                });
            });
        },

        function (shipId, data) {//FILTER SHIP GROUPS
            var self = this;

            return new Promise(function (resolve, reject) {

                var shipGroups = data.shipGroups;

                if (shipGroups.length === 0) {
                    resolve(true);
                } else {
                    self._ships.get(shipId).then(function (ship) {
                        resolve(_.some(shipTypeToGroups(ship.shipType), function (gr) {
                            return _.indexOf(shipGroups, gr) !== -1;
                        }));
                    });
                }
            });
        },

        function (shipId, data) {//FILTER COMPANIES
            var self = this;

            return new Promise(function (resolve, reject) {

                var companies = data.companies;

                if (companies.length === 0) {
                    resolve(true);
                } else {
                    self._ships.get(shipId).then(function (ship) {
                        resolve(_.indexOf(companies, ship.companyName) >= 0);
                    });
                }
            });
        },

        function (shipId, data) {//FILTER VISIBLE SHIPS LIST
            return new Promise(function (resolve, reject) {
                var visibleShips = data.visibleShips;

                if (visibleShips.length === 0) {
                    resolve(!TOTEM_MODE);
                } else {
                    resolve(_.indexOf(visibleShips, shipId) >= 0);
                }
            });
        }
    ];

    /*
     * Map.ShipView 
     */

    //Map.ShipView = function (map, ship) {

    //};

    /*
     * jQuery integration
     */
    $.fn.map = function (c, op) {
        var command;
        var options;
        var $this = $(this);

        if (_.isString(c)) {
            command = c;
            options = op || {};
        } else {
            command = "init";
            options = c || {};
        }

        switch (command) {
            case "init":
                //var data = deparam(window.location.hash.replace(/^#/, ''));
                $this.data('map', new Map($this, options));

                break;
            case "start":
                $this.data('map').start(options);

                break;
            default:
                console.warn('Command "' + command + '" not found.');
        }

        return $this;
    };



})(jQuery);
