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

/* global Promise, chroma */

let timeParse,
    timeFormat,
    timeDimensionControl,
    userFiles = [],
    monthArray = ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"],
    dateArray = [],
    dateMin,
    dateMax,
    minUserDate,
    maxUserDate,
    userDates,
    map;

// define empty objects and indicators
let maxIndicators = {},
    minIndicators = {},
    indicators = ["area", "perimeter", "costa", "df"],
    indicatorsNames = ["Área", "Perímetro", "Desarrollo de la línea de costa", "Dimensión fractal"];

let currentTiles = {};

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

    setupTimeDimensionControl();
    setupDates()
        .then(dates => populateDates(dates))
        .then(userDates => setupMap(userDates))
        .then(map => populateMap(map));
});

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

// query available dates on DB
const setupDates = () => {
    return new Promise( resolve => {
        let layersQuery = "http://localhost:8090/data/list_layers";
        d3.json(layersQuery).then(layers => {
            layers.forEach(layer => {
                dateArray.push(timeParse(layer.f_table_name)); // convert filenames to dates
            })

            dateArray = dateArray.sort(sortInitialDateAscending); // order dates
            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 populateDates = (dates) => { // fill out date pickers with available dates
    return new Promise( resolve => {
        $.datepicker.regional["es"] = {
            closeText: "Cerrar",
            prevText: "&#x3c;Ant",
            nextText: "Sig&#x3e;",
            currentText: "Hoy",
            monthNames: ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio",
                "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"
            ],
            monthNamesShort: ["Ene", "Feb", "Mar", "Abr", "May", "Jun",
                "Jul", "Ago", "Sep", "Oct", "Nov", "Dic"
            ],
            dayNames: ["Domingo", "Lunes", "Martes", "Mi&eacute;rcoles", "Jueves", "Viernes", "S&aacute;bado"],
            dayNamesShort: ["Dom", "Lun", "Mar", "Mi&eacute;", "Juv", "Vie", "S&aacute;b"],
            dayNamesMin: ["Do", "Lu", "Ma", "Mi", "Ju", "Vi", "S&aacute;"],
            weekHeader: "Sm",
            dateFormat: "yy/mm/dd",
            firstDay: 1,
            isRTL: false,
            showMonthAfterYear: false,
            yearSuffix: ""
        }
        $.datepicker.setDefaults($.datepicker.regional["es"]);

        // month pickers
        $("#date-initial").datepicker({
            minDate: dates.min,
            maxDate: dates.max,
            defaultDate: dates.min,
            changeMonth: true,
            changeYear: true,
            showButtonPanel: true,
            dateFormat: "M yy",
            onClose: function() {
                let month = $("#ui-datepicker-div .ui-datepicker-month :selected").val();
                let year = $("#ui-datepicker-div .ui-datepicker-year :selected").val();
                minUserDate = new Date(year, month, 1); // initial date
                $(this).datepicker("setDate", minUserDate);
                $("#date-final").datepicker("option", {
                    "minDate": minUserDate,
                    disabled: false
                });
                // hack to avoid needing to change date twice in second datepicker
                setTimeout(() => { $("#date-final").datepicker("show") }, 10);
            },
            beforeShow: el => {
                $("#ui-datepicker-div").toggleClass("hide-calendar", $(el).is("[data-calendar=\"false\"]"));
            }
        });

        $("#date-final").datepicker({
            maxDate: dates.max,
            defaultDate: dates.max,
            changeMonth: true,
            changeYear: true,
            showButtonPanel: true,
            disabled: true,
            dateFormat: "M yy",
            onClose: function() {
                let month = $("#ui-datepicker-div .ui-datepicker-month :selected").val();
                let year = $("#ui-datepicker-div .ui-datepicker-year :selected").val();
                maxUserDate = new Date(year, month, 1); // final date
                $(this).datepicker("setDate", maxUserDate);
                // use .setUTCHours(6,0,0) to adjust DST offset fror some months
                let startUserDate = new Date(minUserDate.setUTCHours(6,0,0));
                let endUserDate = new Date(maxUserDate.setUTCHours(6,0,0));

                // pass new timeinterval to timeDimension player
                userDates = L.TimeDimension.Util.explodeTimeRange(startUserDate, endUserDate, "P1M");
                userFiles = [];
                userDates.forEach(time => {
                    userFiles.push(timeFormat(time));
                });
                resolve({min: startUserDate, max: endUserDate});
            },
            beforeShow: (el, inst) => {
                inst.input.datepicker("refresh");
                $("#ui-datepicker-div").toggleClass("hide-calendar", $(el).is("[data-calendar=\"false\"]"));
            }
        });
    })
}

const setupMap = (dates) => {
    map = L.map("mapmex", {
        center: [17.22, -92.28],
        minZoom: 7,
        zoom: 7,
        timeDimension: true,
        timeDimensionOptions: {
            times: userDates,
            currentTime: dates.min
        }
    });

    let 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."
    });

    cartoDarkLayer.addTo(map);
    map.createPane("wb-Tiles");
    map.getPane("wb-Tiles").style.zIndex = 450;

    // initialize min and max objects to hold values for each indicator
    // and add select options
    indicators.forEach( (indicator, index) => {
        maxIndicators[indicator] = 0;
        minIndicators[indicator] = 1e30;
        $("#indicatorSelect").append("<option value=\""+ indicator + "\">" + indicatorsNames[index] + "</option>");
    });

    console.log(userFiles);
    let cols = [];
    indicators.forEach( (indicator) => {
        cols.push("min(" + indicator + ") as min" + indicator);
        cols.push("max(" + indicator + ") as max" + indicator);
    });

    // query db to get min/max values per month and indicator and store them in an object
    userFiles.forEach( (table) => {
        let minmaxQuery = "http://localhost:8090/data/query/" + table + "?columns=" + cols.join(", ");
        d3.json(minmaxQuery).then(minmax => {
            indicators.forEach( (indicator) => {
                minIndicators[indicator] = Math.min(minIndicators[indicator], minmax[0]["min"+indicator]);
                maxIndicators[indicator] = Math.max(maxIndicators[indicator], minmax[0]["max"+indicator]);
            });
        });
    });

    timeDimensionControl.addTo(map);

    return new Promise( resolve => {
        resolve(map);
    });
}

const populateMap = (map) => {

    // create mvt layers
    userFiles.forEach( f => {
        //f = mvtLayer(f, "area");
        f = mvtLayer(f);
        //abril_2018.addTo(map);
    });

    // style currentTiles
    let option = $("#indicatorSelect").val(); // option selected from dropdrown
    styleTiles(option);

    let timeLayer = L.timeDimension.layer.Tile(currentTiles[userFiles[0]], {
        updateTimeDimension: true,
        updateTimeDimensionMode: "replace",
        waitForReady: true,
        duration: "P1M"
    });

    timeLayer.addTo(map);
    Object.keys(currentTiles).forEach(layer => {
        if (layer !== userFiles[0]) {
            currentTiles[layer].getContainer().style.display = "none";
        }
    });
}

// define MVT layer for given month table and indicator
//const mvtLayer = (monthYear, indicator) => {
// define MVT layer for given month table
const mvtLayer = (monthYear) => {

    let tiles = "http://localhost:8090/data/" + monthYear + "/mvt/{z}/{x}/{y}?geom_column=geom&columns=" + indicators.join();
    let pbfLayer = L.vectorGrid.protobuf(tiles, {
        pane: "wb-Tiles",
        rendererFactory: L.canvas.tile,
        /*vectorTileLayerStyles: {
            [monthYear]: properties => { // use [var] to use a computed property name for monthYear...
                let currentValue = properties[indicator];
                let scale = chroma.scale("PuBu").padding([0.5, 0]).domain([minIndicators[indicator], maxIndicators[indicator]]).classes(5);
                return {
                    fill: true,
                    fillOpacity: 0.7,
                    stroke: true,
                    weight: 1,
                    opacity: 0.2,
                    fillColor: scale(currentValue).hex(),
                    color: scale(currentValue).hex()
                }
            }
        },*/
        maxZoom: 22,
        tolerance: 5,
        extent: 4096,
        buffer: 64,
        debug: 0,
        indexMaxZoom: 5,
        indexMaxPoints: 100000,
        interactive: true,
        getFeatureId: f => {
            return f.properties.id;
        }
    }).on('load', () => { // check when layer has loaded
        // return promise to check when all layers have
        // loaded and then can remove loader mask
        //resolve("Tiles loaded!");
    });
    currentTiles[monthYear] = 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;
        }
    });

    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
        //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);
        // Don't update on add. Rather check @708 and what happens there
        //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";
        //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();

        // get data for time
        let d = new Date(time),
            year = d.getFullYear().toString(),
            m = d.getUTCMonth(),
            month = monthArray[m].toLowerCase(),
            monthYear = month + "_" + year;

        // Update title
        //let title = $("#title");
        //title.html("<h2>Cobertura de agua en la cuenca del r&iacute;o Grijalva en " + month + " de " + year + "</h2>");

        // Update graphs only on timeload event
        //indicators.forEach( indicator => {
        //    indicatorVars[indicator].chartData = indicatorVars[indicator].chart.data(); // get chart data
        //    indicatorVars[indicator].chart.data(indicatorVars[indicator].chartData); // set chart data
        //});

        //console.time("process");
        console.log("data for", monthYear);
        //console.log(currentTiles)
        Object.keys(currentTiles).forEach(layer => {
            if (layer !== monthYear) {
                currentTiles[layer].getContainer().style.display = "none";
            } else {
                this._baseLayer = currentTiles[layer];
                this._baseLayer.getContainer().style.display = "block";
            }
        });
        //console.timeEnd("process");
    }
});

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


// When selecting indicator from dropdown, style tiles.
$("#indicatorSelect").on("change", function() {
    // style currentTiles
    let option = this.value; // option selected from dropdrown
    styleTiles(option)
    //    .then(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);
    //});
});

const styleTiles = option => {
    // define color scale domain based on min-max values for selected indicator
    let domain = [minIndicators[option], maxIndicators[option]];
    let scale = chroma.scale("PuBu").padding([0.5, 0]).domain(domain).classes(5);
    Object.keys(currentTiles).forEach(layer => { // change style for each tile layer
        currentTiles[layer].options.vectorTileLayerStyles[layer] = (properties, zoom) => {
            let selectedIndicator = properties[option]; // choose column to style with
            return {
                fill: true,
                fillOpacity: 0.5,
                stroke: true,
                weight: 1,
                opacity: 0.5,
                fillColor: scale(selectedIndicator).hex(),
                color: scale(selectedIndicator).hex()
            }
        }
        // redraw() causes additional tile requests... not good!
        currentTiles[layer].redraw();
        currentTiles[layer].addTo(map).setZIndex(4); // add tiles to map
    });
    return Promise.resolve(scale);
}
// TODO: add ordered date selector based on what's available on the DB
// TODO: add layer control
