import { AbstractMapWithMarkers } from '../abstract-map-with-markers';
import { loadScript } from '../../../common/load-utilities';
import { stripMultilineIndention } from '../../../common/string-utilities';
import { Lock } from '../../../common/lock.class';
import { TourDrawer } from './tour-drawer';
import { GeolocationHandler } from './google-geolocation-handler';
import { InfoWindowHandler } from './info-window-handler';

/**
 * @implements { MapWithTour }
 * @implements { MapWithInfoWindow }
 */
export class GoogleMap extends AbstractMapWithMarkers {

    /**
     * @param {object} options
     * @returns {object}
     * @protected
     */
    prepareOptions (options) {
        options = super.prepareOptions(options);

        options.googleMapStyle = options.googleMapStyle || [ 'roadmap' ];
        options.googleMapsApiVersion = options.googleMapsApiVersion || 'quarterly';
        if (!options.googleMapsApiKey) {
            console.warn(stripMultilineIndention(`
                ## Toubiz Map Configuration Warning
                No google maps api key supplied
            `));
        }

        return options;
    }

    initialize() {
        this.renderLock = new Lock();
        this.polylines = [];
    }

    /**
     * @protected
     * @returns {Promise}
     */
    render (markers) {
        return this.renderLock.lock(async () => {
            if (!this.map) {
                /** @private {google.maps.Map} */
                this.map = await this.initializeMap();

                this.spiderfier = await this.initializeSpiderfier();

                if (!this.options.clustering.disabled) {
                    this.markerClusterer = await this.initializeMarkerClusterer();
                }

                this.tourDrawer = new TourDrawer(this.map);
                this.infoWindowHandler = new InfoWindowHandler(this.map, this.markerClusterer || null);
                if (this.options.geolocation && this.options.geolocation.enabled) {
                    this.geolocationHandler = new GeolocationHandler(this.map, this.options.geolocation);
                }
            }

            const mapMarkers = await this.initializeMapsMarkers(markers);
            this.spiderfier.clearMarkers();
            if (!this.options.clustering.disabled) {
                this.markerClusterer.clearMarkers();
            }
            for (const marker of mapMarkers) {
                this.spiderfier.addMarker(marker);
                if (!this.options.clustering.disabled) {
                    this.markerClusterer.addMarker(marker);
                }
            }
        });
    }

    /**
     * @public
     * @returns {Promise|void}
     */
    centerMapOnMarkers (markers) {
        if (!this.map) {
            setTimeout(() => this.centerMapOnMarkers(markers), 250);
            return;
        }
        if (!markers || markers.length === 0) {
            return;
        }
        if (markers.length === 1) {
            this.map.setCenter(this.coordinatesToLatLng(markers[ 0 ].coordinates));
            this.map.setZoom(this.options.zoom * 18 / 100);
            return;
        }

        const bounds = new google.maps.LatLngBounds();
        for (const marker of markers) {
            bounds.extend(this.coordinatesToLatLng(marker.coordinates));
        }
        this.map.fitBounds(bounds);
    }

    drawTourOntoMap(marker, fitBounds) {
        this.tourDrawer.drawTourOntoMap(marker, fitBounds);
    }

    clearTours () {
        this.tourDrawer.clearTours();
    }

    /**
     * @param {Marker} marker
     * @param {string} content
     */
    openInfoWindow (marker, content) {
        if (this.infoWindowHandler) {
            this.infoWindowHandler.openInfoWindow(marker, content);
        }
    }

    closeInfoWindow() {
        if (this.infoWindowHandler) {
            this.infoWindowHandler.closeInfoWindow();
        }
    }

    /**
     * @private
     * @returns {Promise<void>}
     */
    async initializeMarkerClusterer () {
        const MarkerClusterer = await import(/* webpackChunkName: "google-maps" */ './marker-clusterer.override')
            .then(module => module.MarkerClusterer);

        const markerClusterer = new MarkerClusterer(this.map, [], {
            gridSize: 60,
            size: 36,
            zoomOnClick: true,
            clusterClass: 'tb-map-marker-cluster',
            imageSizes: [ 36, 45, 54, 63 ],
            maxZoom: 14,
        });

        markerClusterer.setClusterColors(
            this.options.clustering.color,
            this.options.clustering.backgroundColor,
        );

        return markerClusterer;
    }

    /**
     * @private
     * @returns {Promise<google.maps.Map>}
     */
    async initializeMap () {
        let url = 'https://maps.googleapis.com/maps/api/js';
        if (this.options.googleMapsApiKey) {
            url += `?key=${this.options.googleMapsApiKey}`;
        }
        let apiVersionPart = '';
        if (this.options.googleMapsApiVersion) {
            apiVersionPart = `${this.options.googleMapsApiKey ? '&' : '?'}v=${this.options.googleMapsApiVersion}`;
        }
        url += apiVersionPart;
        await loadScript(url, 'google-maps');

        const config = {
            zoom: Math.round(this.options.zoom / 100 * 18),
            mapTypeId: this.options.googleMapStyle[ 0 ].identifier,
            mapTypeControl: false,
            zoomControlOptions: {
                position: google.maps.ControlPosition.RIGHT_TOP,
            },
            clickableIcons: false,
            scaleControl: true,
        };

        const mapCenterLatLng = this.coordinatesToLatLng(this.options.center);
        if (mapCenterLatLng) {
            config.center = mapCenterLatLng;
        }

        if (this.options.googleMapStyle.length > 1) {
            config.mapTypeControl = true;
            config.mapTypeControlOptions = {
                mapTypeIds: this.options.googleMapStyle.map(style => style.identifier),
                position: google.maps.ControlPosition.TOP_RIGHT,
                style: google.maps.MapTypeControlStyle.DROPDOWN_MENU,
            };
        }

        return new google.maps.Map(this.node, config);
    }

    /**
     * @private
     * @returns {google.maps.Marker[]}
     */
    initializeMapsMarkers (markers) {
        const mapMarkers = [];

        for (const marker of markers) {
            if (!marker.latLng) {
                marker.latLng = new google.maps.LatLng(
                    this.coordinatesToLatLng(marker.coordinates),
                );
            }
            if (!marker.mapsMarker) {
                marker.mapsMarker = new google.maps.Marker({ position: marker.latLng, optimized: false });

                marker.mapsMarker.setMap(this.map);

                this.spiderfier.addListener('click', mapsMarker => {
                    if (mapsMarker === marker.mapsMarker) {
                        this.callMarkerClickListeners(marker);
                    }
                });

                marker.mapsMarker.addListener('spider_format', status => {
                    if (!this.options.clustering.spiderficationIcon) {
                        return;
                    }
                    switch (status) {
                        case 'SPIDERFIABLE':
                            this.setMarkerIcon(marker, this.options.clustering.spiderficationIcon);
                            break;
                        case 'UNSPIDERFIED':
                        case 'SPIDERFIED':
                            this.setMarkerIcon(marker);
                    }
                });
            }

            this.setMarkerIcon(marker);
            mapMarkers.push(marker.mapsMarker);
        }

        return mapMarkers;
    }

    /**
     * @private
     * @returns {Promise<OverlappingMarkerSpiderfier>}
     */
    async initializeSpiderfier () {
        const module = await import(
            /* webpackChunkName: "google-maps" */
            'exports-loader?this.OverlappingMarkerSpiderfier!../../../vendor/googlemaps/spiderfier'
        );
        const OverlappingMarkerSpiderfier = module.default;
        const spiderfier = new OverlappingMarkerSpiderfier(this.map, {
            legWeight: 3,
            markersWontHide: true,
            basicFormatEvents: true,
            keepSpiderfied: true,
            nudgeStackedMarkers: false,
            nearbyDistance: 65,
        });

        return spiderfier;
    }


    /**
     * @param {Marker~Coordinates} coordinates
     * @returns {{lat: number, lng: number}}
     * @private
     */
    coordinatesToLatLng (coordinates) {
        if (!coordinates || isNaN(coordinates.latitude) || isNaN(coordinates.longitude)) {
            console.warn(stripMultilineIndention(`
                ## Toubiz Map Warning
                Invalid coordinates when transforming to LatLng
            `));
            return null;
        }

        return { lat: coordinates.latitude, lng: coordinates.longitude };
    }

    /**
     * @private
     * @param {Marker} marker
     * @param {string} iconUrl
     */
    setMarkerIcon (marker, iconUrl = null) {
        if (!iconUrl && !marker.style.iconUrl) {
            return;
        }

        marker.mapsMarker.setIcon({
            url: iconUrl || marker.style.iconUrl,
            scaledSize: new google.maps.Size(marker.style.width, marker.style.height),
            anchor: new google.maps.Point(marker.style.offsetX, marker.style.offsetY),
        });
    }

}
