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

/* globals Promise, makeBaseMap */
/* exported indicators. timeParse, layerControl, intervals */

let timeParse,
    timeFormat,
    timeDimensionControl,
    map,
    overlay,
    glmap,
    osmLayer, cartoLightLayer, cartoDarkLayer,
    layerControl;

// define empty objects and indicators
    indicators = ["ocupadosDesde", "ocupadosHacia", "ocupadosEntre", "poicDesde", "poicHacia", "poicEntre"],
    indicatorsNames = ["Viajes de ocupados desde centros de mercado", "Viajes de ocupados hacia centros de mercado",
        "Viajes de ocupados entre zonas", "Viajes de personas en OIC desde centros de mercado",
        "Viajes de personas en OIC hacia centros de mercado", "Viajes de personas en OIC entre zonas"
    ],
    indicatorsShortNames = ["Ocupados desde <br/> centros de mercado", "Ocupados hacia <br/> centros de mercado",
        "Ocupados entre zonas", "OIC desde centros <br/> de mercado",
        "OIC hacia centros <br/> de mercado", "OIC entre zonas"
    ],
    indicatorVars = {};

// Add options to combo box, and set their chart variables
// chart containers must already exist in index.php
indicators.forEach((indicator, index) => {
    $("#indicatorSelect").append(`<option value="${indicator}"> ${indicatorsNames[index]}</option>`);
    // chart variables
    indicatorVars[indicator] = {
        "chart": `${indicator}Chart`,
        "chartData": `${indicator}Data`,
        "container": `#${indicator}-graph`
    };
});

let intervals = {
    "ocupadosDesde": {
        "classes": ["Menos de 1,000", "1,001 - 2,000", "2,001 - 6,000", "6,001 - 7,200"],
        "values": [1000, 2000, 6000, 7200],
        "colors": ["#00c5ff", "#008fdc", "#005ce6", "#4c0073"],
        "thickness": [0.5, 1.5, 2, 10],
        "animThickness": [0.5, 1.5, 4, 15]
    },
    "ocupadosHacia": {
        "classes": ["Menos de 1,000", "1,001 - 2,000", "2,001 - 6,000", "6,001 - 32,000", "32,001 - 53,300"],
        "values": [1000, 2000, 6000, 32000, 53300],
        "colors": ["#ffbee8", "#ff73df", "#ff00c5", "#ad027d", "#80006b"],
        "thickness": [0.5, 1.5, 2, 4, 10],
        "animThickness": [0.5, 1.5, 4, 10, 15]
    },
    "ocupadosEntre": {
        "classes": ["Menos de 1,000", "1,001 - 2,000", "2,001 - 3,500", "3,501 - 8,000"],
        "values": [1000, 2000, 3500, 8000],
        "colors": ["#70a800", "#ffaa00", "#e64c00", "#a80000"],
        "thickness": [0.5, 1.5, 2, 4],
        "animThickness": [0.5, 1.5, 4, 10]
    },
    "poicDesde": {
        "classes": ["Menos de 150", "151 - 500", "501 - 1,000", "1,001 - 1,500"],
        "values": [150, 500, 1000, 1500],
        "colors": ["#00c5ff", "#008fe6", "#005ce6", "#002673"],
        "thickness": [0.5, 1.5, 2, 4],
        "animThickness": [0.5, 1.5, 4, 10]
    },
    "poicHacia": {
        "classes": ["Menos de 200", "201 - 400", "401 - 600", "601 - 3,000", "3,001 - 10,700"],
        "values": [200, 400, 600, 3000, 10700],
        "colors": ["#ffbee8", "#ff73df", "#ff00c5", "#ad027d", "#80006b"],
        "thickness": [0.5, 1.5, 2, 4, 10],
        "animThickness": [0.5, 1.5, 4, 10, 15]
    },
    "poicEntre": {
        "classes": ["150 - 500", "501 - 700", "701 - 950", "951 - 1,200"],
        "values": [500, 700, 950, 1200],
        "colors": ["#70a800", "#ffaa00", "#e64c00", "#a80000"],
        "thickness": [0.5, 1.5, 2, 4],
        "animThickness": [0.5, 1.5, 4, 10]
    }
}

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

    d3.timeFormatDefaultLocale(locale);

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

    // TODO: don't need outer call to d3.json, do I? Move this to the bottom after functions definitions
    $("#startHeader").remove();
    $("#initial-backdrop").remove();
    $("#topRightChart")[0].style.setProperty("display", "none");
    $("#regionSelect").remove();
    $("#mainContainer")[0].style.setProperty("display", "flex", "important");
    $("#mexmap").show();

    setupMap()
        .then(map => populateMap(map));
});

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

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 bounds = cuencaBufferMask.getBounds();
        let southWest = L.latLng(19.87, -102.87),
            northEast = L.latLng(23.02, -99.31),
            bounds = L.latLngBounds(southWest, northEast);

        map = L.map("mexmap", {
            //center: [17.22, -92.28],
            minZoom: 8,
            zoom: 6,
            attributionControl: false,
            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);
    });
}

const odClick = (e) => {
    e.target.clearAllPathSelections();
    let origins = "";
    // select and draw flows from origins
    if (e.sharedOriginFeatures.length) {
        e.target.selectFeaturesForPathDisplay(e.sharedOriginFeatures, "SELECTION_NEW");
        /*e.sharedOriginFeatures.forEach( origin => {
        /    origins += `${origin.properties.muni_origen} &rarr; ${origin.properties.muni_destino}: ${origin.properties.viajes} viajes <br>`;
        });*/
    }
    // select and add flows toward those origins, i.e., for dots that are both origins and destinations
    let dests = e.target.originAndDestinationGeoJsonPoints.features.filter(f => f.geometry.coordinates[0] === e.latlng.lng && f.geometry.coordinates[1] === e.latlng.lat);
    e.target.selectFeaturesForPathDisplay(dests, "SELECTION_ADD");
    dests.forEach(dest => {
        //origins += `${dest.properties.muni_origen} &rarr; ${dest.properties.muni_destino}: ${dest.properties.viajes} viajes <br>`;
        // style viajes thousands with #,###
        origins += `<tr><td>${dest.properties.muni_origen}</td><td>${dest.properties.muni_destino}</td><td>${dest.properties.viajes.toLocaleString()}</td></tr>`;
    });
    $("#odCard").hide();
    $("#odTableRow").removeClass("align-self-center");
    $("#tblViajes").show();
    $("#tblViajes tbody").html(origins);

    if (e.sharedDestinationFeatures.length) {
        //e.target.selectFeaturesForPathDisplay(e.sharedDestinationFeatures, "SELECTION_ADD");
    }

    // if dots are only destinations, add paths when clicked on
    /*if (e.sharedDestinationFeatures.length) {
        e.target.selectFeaturesForPathDisplay(e.sharedDestinationFeatures, "SELECTION_NEW");
    }*/

    // TODO: trigger chart update to only draw selected origins and destinations?
}

const populateMap = async (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", () => {

        let baseLayers = {
            "Carto Dark": cartoDarkLayer,
            "Carto Light": cartoLightLayer,
            "OpenStreetMap": osmLayer
        };

        layerControl = L.control.layers(baseLayers).addTo(map);
        makeBaseMap() // basemap.js
        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");
    });
}

// When selecting indicator from dropdown, style tiles.
$("#indicatorSelect").on("change", function() {
    let option = this.value; // option selected from dropdrown

    flowMapsArray.forEach(layer => {
        if (layer.options.customLayerId === option) {
            map.addLayer(layer);
            layer.on("click", odClick);

            // TODO: Recover each layer state and populate table according to that
            // layer._layers[i].feature.properties._isSelectedforPathDisplay
        } else {
            layer.off("click", odClick);
            map.removeLayer(layer);
        }

        $("#odTableRow").addClass("align-self-center");
        $("#odCard").show();
        $("#tblViajes").hide();
        $("#tblViajes tbody").html("");

    });

    // on select, update chart data
    let odChart = am4core.registry.baseSprites.find(c => c.htmlContainer.id === "topLeftChart");
    odChart.data = odData[option];

    let chordChart = am4core.registry.baseSprites.find(c => c.htmlContainer.id === "bottomLeftChart");
    chordChart.data = odData[option];
    chordChart.data = chordChart.data.filter( e => e.flowCount > 0 );

    legend.addTo(map); // add legend control -> it updates
    // FIXME: re-adding control updates its contents... why?
    // Highlight plot title according to selected option
    indicators.forEach(indicator => {
        d3.select(indicatorVars[indicator].container).select("svg text.title").classed("active", indicator === option ? true : false);
    });
});

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

legend.onAdd = () => {
    let div = L.DomUtil.create("div", "info legend leaflet-bar");
    let option = $("#indicatorSelect").val();
    let optionIndex = indicators.indexOf(option);

    //title
    let title = L.DomUtil.create("h6", "");
    title.innerHTML = `${indicatorsShortNames[optionIndex]}`;
    div.appendChild(title);

    // list
    let list = L.DomUtil.create("ul", "");
    div.appendChild(list);

    let classes = intervals[option].classes;

    // create stuff for each type of flow
    classes.forEach((c, idx) => {
        // create list element
        let item = L.DomUtil.create("li");
        // create canvas element with arc of appropriate thickness ad color
        let canvas = L.DomUtil.create("canvas");

        let originProp = L.DomUtil.testProp(["transformOrigin", "WebkitTransformOrigin", "msTransformOrigin"]);
        canvas.style[originProp] = "50% 50%";
        canvas.width = 18;
        canvas.height = 18;

        let ctx = canvas.getContext("2d");
        ctx.beginPath();
        ctx.arc(18, 18, 15, 1.5 * Math.PI, Math.PI, true);
        ctx.lineWidth = intervals[option].thickness[idx];
        ctx.lineCap = "round";
        // line color
        ctx.strokeStyle = intervals[option].colors[idx];;
        ctx.stroke();

        // append canvas to list element
        item.appendChild(canvas);

        // create legend item text
        let span = L.DomUtil.create("span", "legendItem");
        span.innerText = c;

        // append text to list item
        item.appendChild(span);
            // lastly, append item to list
        list.appendChild(item);
    });

    return div;
};