/*
 * Copyright 2020 - All rights reserved.
 * Rodrigo Tapia-McClung
 *
 * August 2020
 */

/* globals Promise */
/* exported layerControl, baseUrl */

let timeDimensionControl,
    map,
    overlay,
    glmap,
    osmLayer, cartoLightLayer, cartoDarkLayer,
    timeLayer,
    layerControl,
    tiles;

const windowUrl = new URL(window.location.href);
const baseUrl = windowUrl.hostname == "localhost" ?
    `${windowUrl.origin}` : // returns "http://localhost:8090"
    `${windowUrl.origin}/fordecyt_2019`; // returns "http://servername/fordecyt_2019"
// use `${baseUrl}/something`

const getMinMax = table => {
    return new Promise(resolve => {
        let minmaxQuery = `${baseUrl}/query/${table}?columns=${`min(yeartrimes), max(yeartrimes)`}`;
        d3.json(minmaxQuery).then(minmax => {
            resolve(minmax[0]);
        });
    });
}

const setupMap = (dates) => {
    return new Promise(resolve => {
        // make body tag to have style height: 100%
        $("body").css("height", "100%");
        osmLayer = L.tileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png", {
            maxZoom: 19,
            attribution: "&copy; <a href=\"http://osm.org/copyright\" target=\"_blank\">OpenStreetMap</a> contributors"
        });
        cartoLightLayer = L.tileLayer("https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png", {
            maxZoom: 19,
            attribution: "Map tiles by Carto, under CC BY 3.0. Data by OpenStreetMap, under ODbL."
        });
        cartoDarkLayer = L.tileLayer("https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png", {
            maxZoom: 19,
            attribution: "Map tiles by Carto, under CC BY 3.0. Data by OpenStreetMap, under ODbL."
        });

        let southWest = L.latLng(19.87, -102.87),
            northEast = L.latLng(23.02, -99.31),
            bounds = L.latLngBounds(southWest, northEast);

        let datesArray = [dates.min, dates.max]; // [2014.2, 2019.4]

        // convert year.Q dates array to initil and end Date object to explode in timeDimension
        let times = datesArray.map( date => {
            let q = (date - parseInt(date))*10;
            return new Date(parseInt(date), parseInt(q*3-3), 1);
        });

        map = L.map("mexmap", {
            //center: [17.22, -92.28],
            minZoom: 8,
            zoom: 6,
            attributionControl: false,
            timeDimension: true,
            //timeDimensionControl: true,
            timeDimensionOptions: {
                times: L.TimeDimension.Util.explodeTimeRange(times[0], times[1], "P3M")
                //currentTime: dates.min
            },
            maxBounds: bounds
        }).setView([21.15, -100.94], 8);

        cartoDarkLayer.addTo(map);

        // set sync map on right
        let osmLayerOverlay = L.tileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png", {
                maxZoom: 19,
                attribution: "&copy; <a href=\"http://osm.org/copyright\" target=\"_blank\">OpenStreetMap</a> contributors"
            }),
            cartoLightLayerOverlay = L.tileLayer("https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png", {
                maxZoom: 19,
                attribution: "Map tiles by Carto, under CC BY 3.0. Data by OpenStreetMap, under ODbL."
            }),
            cartoDarkLayerOverlay = L.tileLayer("https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png", {
                maxZoom: 19,
                attribution: "Map tiles by Carto, under CC BY 3.0. Data by OpenStreetMap, under ODbL."
            });

        overlay = L.map("overlaydiv", {
            zoomControl: false,
            inertia: false,
            keyboard: false,
            //dragging: false,
            scrollWheelZoom: true,
            attributionControl: false,
            zoomAnimation: true
        }).setView([16.96, -92.97], 8);

        cartoDarkLayerOverlay.addTo(overlay);
        L.control.attribution({ position: "bottomright" }).addTo(overlay);

        // on base layer on main map, change base layer on overlay map
        map.on("baselayerchange", event => {
            overlay.eachLayer(l => overlay.removeLayer(l));
            if (event.name == "Carto Dark") {
                cartoDarkLayerOverlay.addTo(overlay);
            } else if (event.name == "Carto Light") {
                cartoLightLayerOverlay.addTo(overlay);
            } else if (event.name == "OpenStreetMap") {
                osmLayerOverlay.addTo(overlay);
            }
        });

        const offsetGlobal = (center, zoom, refMap, tgtMap) => {
            let refC = refMap.getContainer();
            let tgtC = tgtMap.getContainer();
            let pt = refMap.project(center, zoom)
                .subtract([refC.offsetLeft, refC.offsetTop])
                .subtract(refMap.getSize().divideBy(2))
                .add([tgtC.offsetLeft, tgtC.offsetTop])
                .add(tgtMap.getSize().divideBy(2));
            return refMap.unproject(pt, zoom);
        }

        map.sync(overlay, { offsetFn: offsetGlobal });

        resolve({ "map": map });
    });
}

const populateMap = async(mapData) => {

    tiles = mapboxLayer("urbanization_year");

    let map = mapData.map;
    glmap = L.mapboxGL({
        "accessToken": "no-token",
        "style": {
            "version": 8,
            "sources": {},
            "layers": []
        }
    }).addTo(map);

    // after mapboxGL map is ready with styles do this:
    glmap.getMapboxMap().on("style.load", () => {

        glmap.getMapboxMap().addLayer(tiles);
        //glmap.getMapboxMap().setPaintProperty(tiles, "fill-opacity", 0.7);

        timeDimensionControl.addTo(map);
        // Pass dummy geojson layer to timeDimension in order to register and sync
        timeLayer = L.timeDimension.layer.Tile(L.geoJSON(), {
            updateTimeDimension: true,
            updateTimeDimensionMode: "replace",
            waitForReady: true,
            duration: "P1M"
        });
        timeLayer.addTo(map);

        let baseLayers = {
            "Carto Dark": cartoDarkLayer,
            "Carto Light": cartoLightLayer,
            "OpenStreetMap": osmLayer
        };
        let overlays = {
        //    "<span id=\"cuencaOverlay\">Agua en la cuenca del Grijalva</span>": timeLayer
        };

        layerControl = L.control.layers(baseLayers, overlays).addTo(map);
        //legend.addTo(map);

        // fix for leaflet-mapbox-gl v. 0.0.11 that adds map's to tile pane:
        // get children of map tile pane, create array from it and iterate
        // setting their z-index
        let glmapChildren = map.getPanes().tilePane.children,
            children = Array.from(glmapChildren);
        children.forEach( c => c.style.zIndex = "inherit" );

        let legend = L.control({
            position: "bottomright"
        });

        let years = [],
            colors = [];

        legend.onAdd = () => {
            let div = L.DomUtil.create("div", "info legend leaflet-bar");
            // get layer style from mapbox map
            let layerStyle = glmap.getMapboxMap().getStyle().layers[0];
            if (layerStyle.id == "urbanization_year") {
                let paint = layerStyle.paint;
                let legendStuff = paint["fill-color"].slice(3);
                legendStuff.forEach(entry => {
                    if (typeof(entry) === "number") {
                        years.push(entry);
                    } else {
                        colors.push(entry);
                    }
                });
            }
            let html = "<div><h6>A&ntilde;o</h6></div><div class=\"colors\">";
            // create legend items
            years.forEach( y => {
                html += `<div class="yearLegendItem">${y}</div>`;
            });
            html += "</div>";

            div.innerHTML = html;

            return div;
        };

        legend.addTo(map);
        // once legend is added and divs exist, style background with color gradient
        $(".colors").css({background: `linear-gradient(to bottom, ${colors.join(",")})`, width: "5px", marginRight: "35px"});
        $(".yearLegendItem").css({paddingLeft: "10px"});
    });
}

// define MVT layer for given table and all trimester column
const mapboxLayer = (table) => {
    let pbfLayer = {
        "id": table,
        "source": {
            "type": "vector",
            "tiles": [`${baseUrl}/${table}/mvt/{z}/{x}/{y}?geom_column=geom&columns=yeartrimes`],
            "maxzoom": 19,
            "minzoom": 6
        },
        "source-layer": table,
        "type": "fill",
        "paint": {
            "fill-opacity": 0.7,
            "fill-color": [
                "interpolate",
                ["linear"],
                ["get", "yeartrimes"],
                    2014, "rgb(49,54,149)",
                    2016, "rgb(255,255,191)",
                    2019, "rgb(158,1,66)"
            ]
        }
    }
    return pbfLayer;
}

const setupTimeDimensionControl = () => {
    L.Control.TimeDimensionCustom = L.Control.TimeDimension.extend({
        _getDisplayDateFormat: date => {
            let d = new Date(date);
            let year = d.getFullYear().toString();
            let month = d.getUTCMonth();
            return `Q${month/3+1} ${year}`;
        }
    });

    timeDimensionControl = new L.Control.TimeDimensionCustom({
        loopButton: true,
        /*minSpeed: 1,
        maxSpeed: 5,*/
        timeSteps: 1,
        playReverseButton: true,
        //limitSliders: true,
        playerOptions: {
            //buffer: 5,
            //minBufferReady: 5,
            transitionTime: 125,
            loop: true
        },
        timeZones: ["Local"]
    });
}

L.TimeDimension.Layer.Tile = L.TimeDimension.Layer.extend({
    _setAvailableTimes: function() {
        if (this.options.times) {
            return L.TimeDimension.Util.parseTimesExpression(this.options.times);
        } else if (this.options.timeInterval) {
            let tiArray = L.TimeDimension.Util.parseTimeInterval(this.options.timeInterval);
            let period = this.options.period || "P1D";
            let validTimeRange = this.options.validTimeRange || undefined;
            return L.TimeDimension.Util.explodeTimeRange(tiArray[0], tiArray[1], period, validTimeRange);
        } else {
            return [];
        }
    },

    onAdd: function(map) {
        // Don't call prototype so this_update() does not get called twice
        //L.TimeDimension.Layer.prototype.onAdd.call(this, map);
        this._map = map;
        if (!this._timeDimension && map.timeDimension) {
            this._timeDimension = map.timeDimension;
        }
        this._timeDimension.on("timeloading", this._onNewTimeLoading, this);
        this._timeDimension.on("timeload", this._update, this);
        this._timeDimension.registerSyncedLayer(this);
        map.addLayer(this._baseLayer);
        this._update();
    },

    onRemove: function(map) {
        this._timeDimension.unregisterSyncedLayer(this);
        this._timeDimension.off("timeloading", this._onNewTimeLoading, this);
        this._timeDimension.off("timeload", this._update, this);
        //this._baseLayer.getContainer().style.display = "none";
        /*Object.keys(allTiles).forEach(layer => { // hide all tiles
            glmap.getMapboxMap().setPaintProperty(layer, "fill-opacity", 0);
        });*/
        //this.eachLayer(map.removeLayer, map);
        //this._map = null;
    },

    isReady: function(time) {
        // to be implemented for each type of layer
        return true;
    },

    _update: function() {
        if (!this._baseLayer || !this._map) {
            return;
        }

        let time = this._timeDimension.getCurrentTime();

        // get current time, parse quarter and compare tile value. Incremental add over time
        let d = new Date(time),
            year = d.getFullYear().toString(),
            m = d.getUTCMonth(),
            yeartrimester = year + "." + (m / 3 + 1),
            yearQ = `Q${m/3+1} ${year}`;

        // Update title
        let title = $("#title");
        title.html(`<h2>Crecimiento urbano en la regi&oacute;n metropolitana centro pa&iacute;s en ${yearQ}</h2>`);
        
        // filter tiles based on yeartrimester value
        let filter =  ["<=", ["get", "yeartrimes"], parseFloat(yeartrimester)];
        glmap.getMapboxMap().setFilter("urbanization_year", filter);
    }
});

L.timeDimension.layer.Tile = (layer, options) => {
    return new L.TimeDimension.Layer.Tile(layer, options);
};

// hide initial screen and show map
$("#startHeader").remove();
$(".picker").remove();
$("#initial-backdrop").remove();
$("#topRightChart")[0].style.setProperty("display", "none");
$("#mainContainer")[0].style.setProperty("display", "flex", "important");
$("#mexmap").show();

$("#odTableRow").removeClass("align-self-center");
$("#odCard").hide();

setupTimeDimensionControl();
getMinMax("urbanization_year") // get minmax year.Q values to popoulate timeDimension
    .then(dates => setupMap(dates))
    .then(map => populateMap(map));