/*
 * Copyright 2021 - All rights reserved.
 * Rodrigo Tapia-McClung
 *
 * January-March 2021
 */

/* globals Promise */
/* exported layerControl */

let dateArray = [],
    userFiles = [],
    dateMin,
    dateMax,
    timeDimensionControl,
    map,
    overlay,
    glmap,
    osmLayer, cartoLightLayer, cartoDarkLayer,
    timeLayer,
    layerControl;

let regions = {
        "Aguascalientes": {"color": "#f40b1f"},
        "San Luis Potosi": {"color": "#f40b1f"},
        "Rioverde": {"color": "#f40b1f"},
        "La Piedad-Pénjamo": {"color": "#f40b1f"},
        "Moroleón-Uriangato": {"color": "#f40b1f"},
        "Corredor del Bajío": {"color": "#f40b1f"},
        "Tramo San Juan del Rio - Querétaro": {"color": "#fddfb9"},
        "Tramo Querétaro - Celaya": {"color": "#fb9a99"},
        "Tramo Celaya - Salamanca": {"color": "#33a09b"},
        "Tramo Salamanca - Irapuato": {"color": "#de6eb4"},
        "Tramo Irapuato - León": {"color": "#4db0ec"},
        "Tramo León - San Fracisco del Rincón": {"color": "#2be829"}
    },
    previousClick,
    currentClick;

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`

let timeParse = d3.timeParse("%Y");

let currentTiles = {},
    allTiles = {};

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

const sortInitialDateAscending = (a, b) =>  a - b;
// Dates will be cast to numbers automagically:

// query available dates on DB
const setupDates = () => {
    return new Promise(resolve => {
        let layersQuery = `${baseUrl}/list_layers`;
        d3.json(layersQuery).then(layers => {
            layers.forEach(layer => {
                if (layer.f_table_name.startsWith("periurbano_")) {
                    let table = layer.f_table_name.split("periurbano_")[1];
                    dateArray.push(timeParse(table)); // convert table names to dates
                    userFiles.push(layer.f_table_name);
                }
            });

            dateArray = dateArray.sort(sortInitialDateAscending); // order dates
            userFiles = userFiles.sort();
            dateMin = d3.min(dateArray);
            dateMax = d3.max(dateArray);
            //userFiles = dateArray.map( month => timeFormat(month)); // order table names by date

            const dates = { min: dateMin, max: dateMax, dates: dateArray };
            resolve(dates);
        });
    });
}

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.65, -103.10),
            northEast = L.latLng(22.65, -99.12),
            bounds = L.latLngBounds(southWest, northEast);

        map = L.map("mexmap", {
            minZoom: 8,
            zoom: 6,
            attributionControl: false,
            timeDimension: true,
            timeDimensionOptions: {
                times: L.TimeDimension.Util.explodeTimeRange(dates.min, dates.max, "P1Y"),
                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([21.15, -100.94], 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) => {
            var refC = refMap.getContainer();
            var tgtC = tgtMap.getContainer();
            var 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) => {

    let map = mapData.map;

    // get regions bounding boxes and populate combobox
    $("#regionSelect").append("<option disabled value=\"0\"> -- Selecciona una regi&oacute;n --</option>");

    let bboxQuery = `${baseUrl}/query/periurbanoBajio?columns=region,st_extent(geom)%20as%20bbox&group=region&sort=region`;
    d3.json(bboxQuery).then( boxes => {
        boxes.forEach(  (box) => {
            let latLngs = box.bbox.slice(4,-1).split(",");
            let northEast = latLngs[1].split(" "),
                southWest = latLngs[0].split(" ");
            regions[box.region].bbox = [ [+northEast[1], +northEast[0]], [+southWest[1], +southWest[0]] ];

            // add regions to combobox
            $("#regionSelect").append(`<option value="${box.region}"> ${box.region}</option>`);
        });
    });
    // select initially disabled option
    $("#regionSelect")[0].selectedIndex = 0
    // zoom to region when user selects something
    $("#regionSelect").on("change", () => {
        let region = $("#regionSelect").val();
        map.fitBounds(regions[region].bbox);
        previousClick = region;
    });

    // create mvt layers
    userFiles.forEach(f => {
        mapboxLayer(f);
    });

    // define leaflet pane with higher z-index so tiles are on top of other stuff
    /*map.createPane("urbanizationPane");
    map.getPane("urbanizationPane").style.zIndex = 650;*/

    glmap = L.mapboxGL({
        accesToken: "no-token",
        style: {
            "version": 8,
            "sources": {},
            "layers": []
        },
        interactive: true
        //pane: "urbanizationPane"
    }).addTo(map);

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

        // change default pointer
        glmap.getMapboxMap().getCanvas().style.cursor = "grab";

        userFiles.forEach(year => {
            glmap.getMapboxMap().addLayer(currentTiles[year]);
        });

        Object.keys(allTiles).forEach(layer => {
            if (layer == userFiles[0]) {
                glmap.getMapboxMap().setPaintProperty(layer, "fill-opacity", 0.7);
            }
        });

        glmap.getMapboxMap().addSource("regionesBajioSrc", {
            "type": "vector",
            "tiles": [`${baseUrl}/periurbanoBajio/mvt/{z}/{x}/{y}?geom_column=geom&columns=region`],
            //"type": "geojson",
            //"data": "http://localhost:8090/centropais/data/municipios.geojson",
            "tolerance": 5
        });

        let regionesBajioLyrColor = ["match", ["get", "region"]];
        Object.keys(regions).forEach( region => {
            regionesBajioLyrColor.push(region, regions[region].color);
        });
        regionesBajioLyrColor.push("#ccc");

        glmap.getMapboxMap().addLayer({
            "id": "regionesBajioFill",
            "source": "regionesBajioSrc",
            "source-layer": "periurbanoBajio", //table name
            "type": "fill",
            "paint": {
                "fill-opacity": 0.2,
                "fill-color": regionesBajioLyrColor
            }
        }).addLayer({
            "id": "regionesBajioLine",
            "source": "regionesBajioSrc",
            "source-layer": "periurbanoBajio", //table name
            "type": "line",
            "paint": {
                "line-opacity": 0.2,
                "line-color": regionesBajioLyrColor
            }
        });

        glmap.getMapboxMap().on("mouseenter", "regionesBajioFill", () => {
            glmap.getMapboxMap().getCanvas().style.cursor = "pointer";
        }).on("mouseleave", "regionesBajioFill", () => {
            glmap.getMapboxMap().getCanvas().style.cursor = "grab";
        }).on("click", "regionesBajioFill", e => {
            let region = e.features[0].properties.region;

            currentClick = region;
            if (currentClick != previousClick) {
                map.fitBounds(regions[region].bbox);
                $("#regionSelect").val(region).change();
            } else {
                map.setView([21.15, -100.94], 8);
                $("#regionSelect")[0].selectedIndex = 0;
                currentClick = undefined;
            }
            previousClick = currentClick;
        });

        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: "P1Y"
        });
        timeLayer.addTo(map);
        legend.addTo(map);

        let baseLayers = {
            "Carto Dark": cartoDarkLayer,
            "Carto Light": cartoLightLayer,
            "OpenStreetMap": osmLayer
        };
        var overlays = {
            "<span id=\"gradoUrbanizacion\">&nbsp; Grado de urbanizaci&oacute;n</span>": timeLayer,
            // TODO: add periurbanoLayer to layer control here...
            //"<span id=\"periurbanoBajio\">&nbsp; Regi&oacute;n periurbana y del corredor del Baj&iacute;o</span>": periurbanoBajio,
            /*"<span id=\"bufferSin\"><svg height=\"15\" width=\"15\"><path d=\"M0 0 L15 0 L15 10 L0 10 Z \" x=\"5\" y=\"5\" fill=\"rgba(196,60,57,0.25)\" stroke=\"#c43c39\"></rect></svg>&nbsp; Interfaz periurbana del Corredor del Baj&iacute;o de<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2km <b>sin</b> localidades de m&aacute;s 10mil habitantes</span>": bufferSin,
            "<span id=\"bufferCon\"><svg height=\"15\" width=\"15\"><path d=\"M0 0 L15 0 L15 10 L0 10 Z \" x=\"5\" y=\"5\" fill=\"rgba(213,190,245,0.25)\" stroke=\"#d5bef5\"></rect></svg>&nbsp; Interfaz periurbana del Corredor del Baj&iacute;o de<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2km <b>con</b> localidades de m&aacute;s 10mil habitantes</span>": bufferCon,
            "<span id=\"corredor\"><svg height=\"15\" width=\"15\"><path d=\"M0 0 L15 0 L15 10 L0 10 Z \" x=\"5\" y=\"5\" fill=\"rgba(0,240,2,0.05)\" stroke=\"#00ff00\"></rect></svg>&nbsp; Corredor del Baj&iacute;o</span>": corredor,
            "<span id=\"munis\"><svg height=\"15\" width=\"15\"><path d=\"M0 0 L15 0 L15 10 L0 10 Z \" x=\"5\" y=\"5\" fill=\"rgba(128,128,128, 0.05)\" stroke=\"#808080\"></rect></svg>&nbsp; Contexto municipal</span>": munis,
        */};

        // 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");

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

// 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=grd_urb`],
            maxzoom: 19,
            minzoom: 6
        },
        "source-layer": table,
        type: "fill",
        "paint": {
            "fill-opacity": 0,
            "fill-color": [
                "match",
                ["get", "grd_urb"],
                "Bajo", "rgb(49,149,54)",
                "Medio", "rgb(255,255,191)",
                "Alto", "rgb(158,1,66)",
                "#ccc" // other values
            ]
        }
    }
    currentTiles[table] = pbfLayer;
    allTiles[table] = pbfLayer;
    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 `${monthArray[month]} ${year}`;
            return `${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;
            //alert("times");
            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;
        }

        var time = this._timeDimension.getCurrentTime();

        let d = new Date(time),
            year = d.getFullYear().toString();

        // Update title
        let title = $("#title");
        title.html(`<h2>Crecimiento periurbano en la regi&oacute;n metropolitana centro pa&iacute;s en ${year}</h2>`);

        Object.keys(allTiles).forEach(layer => {
            if (layer.split("periurbano_")[1] !== year) { // hide all other years
                glmap.getMapboxMap().setPaintProperty(layer, "fill-opacity", 0);
            } else {
                glmap.getMapboxMap().setPaintProperty(layer, "fill-opacity", 0.7);
            }
        });
    }
});

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


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

legend.onAdd = () => {
    let div = L.DomUtil.create("div", "info legend leaflet-bar");
    let html = "<h6>Urbanizaci&oacute;n</h6><ul>";
    let colorGrados = {
        "Baja": "rgb(49,149,54)",
        "Media": "rgb(255,255,191)",
        "Alta": "rgb(158,1,66)"
    };
    Object.keys(colorGrados).forEach( grado => {
        html += `<li><i style="background: ${colorGrados[grado]}"></i>${grado}</li>`;
    });
    html += "</ul>";
    div.innerHTML = html;
    return div;
};

// hide initial screen and show map
$("#startHeader").remove();
$("#initial-backdrop").remove();
$("#indicatorSelect").remove();
$("#mainContainer")[0].style.setProperty("display", "flex", "important");
let title = $("#title");
title.html(`<h2>Comportamiento periurbano en la regi&oacute;n metropolitana centro pa&iacute;s</h2>`);
$("#mexmap").show();

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

d3.json("https://unpkg.com/d3-time-format@2.1.1/locale/es-MX.json").then(async locale => {

    d3.timeFormatDefaultLocale(locale);

    //let timeParse = d3.timeParse("%B_%Y");
    //let timeFormat = d3.timeFormat("cuerpos_%B_%Y");

    setupTimeDimensionControl();
    let years = await setupDates(), // get available years
        mapData = await setupMap(years);

    populateMap(mapData);
});