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

/* globals map, userFiles, userDates, indicators, indicatorVars, indicatorsNames */
/* exported makeIndicatorGraph, getData, updateData, getDataInSelection */

let minDate, maxDate, xLine, yLine;

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

//TODO: scale data to appropriate units? m2-> ha? m -> km?
const getData = async () => {
    let queryColumns = ["sum(area) as area", "sum(perimeter) as perimeter", "avg(costa) as costa", "avg(df) as df"],
        timeParse = d3.timeParse("%B_%Y"),
        data = {};
    
    indicators.map( indicator => {
        data[indicator] = [];
        data[indicator].push({name: `${indicator}0`, values: []});
    });

    const baseUrl = new URL(`/data`, window.location.href).href;
    const userFilePromises = userFiles.map( async monthYear => {
        let queryDB = `${baseUrl}/query/${monthYear}?columns=${queryColumns.join(",")}`;
        const dbData = await d3.json(queryDB);
        return {date: timeParse(monthYear), value: dbData[0]};
    });

    const chartData = await Promise.all(userFilePromises);
    chartData.map( monthData => {
        indicators.map( indicator => {
            data[indicator][0].values.push({
                "date": monthData.date,
                "value": +monthData.value[indicator]
            });
            // order data[i].values.date
            data[indicator].forEach(d => d.values.sort(sortByDateAscending));
        });
    });
    return data;
}

const getDataInSelection = async () => {
    let geojson = drawnItems.toGeoJSON(),
        queryColumns = ["sum(area) as area", "sum(perimeter) as perimeter", "avg(costa) as costa", "avg(df) as df"],
        timeParse = d3.timeParse("%B_%Y"),
        data = {};

    indicators.map( indicator => {
        data[indicator] = [];
    });

    const baseUrl = new URL(`/data`, window.location.href).href;

    // Convert drawn items to GeoJSON and check what's inside each one of them
    const drawnItemPromises = drawnItems.toGeoJSON().features.map( async (item, i) => {
        // set SRID fro drawn features
        geojson.features[i].geometry.crs = {"type":"name","properties":{"name":"EPSG:4326"}};
        indicators.map( async indicator => {
            data[indicator].push({name: indicator + (i + 1), values: []});
        });
        // End up with data = {area: [{name: "area1", values: []}], perimeter: [{name: "perimeter1", values: []}]...}

        let filter = `ST_Intersects(geom, (SELECT ST_Multi(ST_GeomFromGeoJSON('${JSON.stringify(geojson.features[i].geometry)}') ) ) )`
        const userFilePromises = userFiles.map(async monthYear => {
            let queryData = `${baseUrl}/query/${monthYear}?columns=${queryColumns.join(",")}&filter=${filter}&group=1%3D1`;
            const selectionQuery = await d3.json(queryData);
            return {date: timeParse(monthYear), value: selectionQuery[0]};
        });
        const selectionData = await Promise.all(userFilePromises);
        selectionData.map( itemData => {
            indicators.map( indicator => {
                let itemDataValue = itemData.value != undefined ? itemData.value[indicator] : 0;
                data[indicator][i].values.push({date: itemData.date, value: itemDataValue });
            });
        });
    });
    const drawnItemsData = await Promise.all(drawnItemPromises);
    return data;
}

const summarizeData = (allData, indicator) => {
    let dataFull = allData;
    let data = dataFull[indicator];
    let data2 = [];
    data.map( a => data2.push(a.values)); // flatten array of data[i].values
    data2 = [].concat.apply([], data2);
    // insert sum of each date to first element of data array with name "column0"
    data.unshift({
        "name": `${indicator}0`,
        "values": d3.nest()
            .key( d => d.date)
            // return d3.sum or d3.average depending on indicator
            .rollup( v => { return indicator == "costa" || indicator == "df" ? d3.mean(v, v => v.value) : d3.sum(v, v => v.value) })
            .entries(data2)
            // map "key" and "value" from d3.nest().rollup() to date and value
            .map( group => { 
                return {
                    date: new Date(group.key),
                    value: group.value
                }
            })
    });
    // order data[i].values.date
    data.forEach(d => d.values.sort(sortByDateAscending));
    return data;
}

const makeIndicatorGraph = () => {

    let data = [],
        width = 450,
        height = 400,
        padding = 30,
        margin = {top: 5, right: 10, bottom: 100, left: 35},
        id, // variable in data to use as identifier
        lineVariables = null, // list of variables to display as lines
        displayName, // variable in data to use as x axis labels
        transitionTime = 500,
        //lineColor = "#17678E",
        //pointColor = "#17678E",
        defaultLineColor = "CadetBlue",
        defaultPointColor = "CadetBlue",
        lineColors = d3.scaleOrdinal(d3.schemeCategory10),
        pointColors = d3.scaleOrdinal(d3.schemeCategory10),
        lineAxisLabel = "",
        xAxisFormat = d3.timeFormat("%b '%y"), // mm 'yy
        yAxisFormat = d3.format(".2s"), // SI format with 2 significant numbers
        //rightAxisFormat = ".0%", // rounded percentage
        legend = false,
        title = "",
        tooltipUnits = "",
        tooltipFormat = "",
        //legend = {title: "title", translateX: 100, translateY: 0, items:["item1","item2"]}
        legendContainer = "legendZone",
        updateData;

    const chart = selection => {
        // get data ranges using values from displayName
        minDate = d3.min(data[0].values, d => {
            return d[displayName];
        });
        minDate = d3.timeDay.offset(minDate, -15) // get previous month to get correct x-axis alignment
        maxDate = d3.max(data[0], d => {
            return d[displayName];
        });
        maxDate = d3.timeDay.offset(maxDate, 15) // get next month to get correct x-axis alignment

        selection.each( () => {

            // add graph svg to selected container on webpage
            let areaSVG = selection
                .append("svg")
                // don't set height/width but use viewBox so charts get resized
                // if they are within a container that can be resized (also scales text)
                .attr("viewBox", `0 0 ${width} ${height}`)
                //.attr("preserveAspectRatio", "xMinYMin meet"),
                .attr("preserveAspectRatio", "xMidYMid meet"),

                g = areaSVG.append("g")
                .attr("class", "lineChart")
                .attr("transform", `translate(${margin.left}, ${margin.top})`);

            // add x axis
            g.append("g")
                .attr("class", "x axis")
                .attr("transform", `translate(30, ${height - margin.left})`);
            // add x axis for ticks
            g.append("g")
                .attr("class", "x axis-ticks")
                .attr("transform", `translate(30, ${height - margin.left})`);
            // add x axis for month labels
            g.append("g")
                .attr("class", "x axis-month-labels")
                .attr("transform", `translate(30, ${height - margin.left})`);
            // add x axis for year labels
            g.append("g")
                .attr("class", "x axis-year-labels")
                .attr("transform", `translate(30, ${height - margin.left + 12})`);
            //add left y axis
            g.append("g")
                .attr("class", "y axis")
                .attr("transform", "translate(30,0)");

            //label y axes
            areaSVG.append("text")
                .attr("class", "label")
                .attr("text-anchor", "middle") // this makes it easy to centre the text as the transform is applied to the anchor
                .attr("transform", `translate(${padding / 3}, ${height / 2}) rotate(-90)`) // text is drawn off the screen top left, move down and out and rotate
                .attr("fill", "#b2b2b2");
            //.text("Superficie inundada");

            // append line container
            g.append("g")
                .attr("class", "lines")
                .attr("transform", "translate(30, 0)");
            // append circle container
            g.append("g")
                .attr("class", "circles")
                .attr("transform", "translate(30, 0)");

            // add chart title container
            areaSVG.append("text")
                .attr("class", "title label")
                .attr("text-anchor", "middle") // this makes it easy to centre the text as the transform is applied to the anchor
                .attr("transform", `translate(${width / 2}, ${margin.top * 4})`); // text is drawn off the screen top left, move down and out and rotate

            // add the tooltip area to the webpage
            let tooltip;
            if (d3.select("div.tooltip").empty()) { // check if no tooltip div exists and add if necessary
                tooltip = d3.select("body").append("div")
                    .attr("class", "tooltip")
                    .style("opacity", 0);
            }

            // define axes with ranges
            xLine = d3.scaleTime().range([0, width - margin.left - margin.right - 30]).clamp(true);
            yLine = d3.scaleLinear().range([height - margin.left - margin.right, 0 + 35])

            // scale data range
            xLine.domain([minDate, maxDate]).nice();
            //yLine.domain(d3.extent(data, function(d) { return d[lineVariables[0]]; })).nice();
            //yLine.domain([0, d3.max(data, function(d) { return d[lineVariables[0]]; })]).nice();

            // Calculate min and max values of data to define chart y-axis domain
            //let minDomain = d3.min(data.map(d => d.values).flat().map(d => d.value));

            //console.log("data: ", data.map(d => d.values));

            let maxDomain = d3.max(data.map(d => d.values).flat().map(d => d.value));
            yLine.domain([0, maxDomain]).nice();
            //yLine.domain([0, d3.max(data[0].values, d => d.value)]).nice();

            // add axes
            let xaxis = g.select(".x.axis");
            xaxis.call(
                    d3.axisBottom(xLine)
                    //    .ticks(d3.timeMonth.every(1))
                    //    .tickFormat(d3.timeFormat("%b '%y"))
                )
                .selectAll("text")
                //.attr("y", 0)
                //.attr("x", 9)
                .attr("y", 15)
                .attr("x", 0)
                .attr("dy", ".35em")
                .attr("font-size", "12px")
                //.attr("transform", "rotate(90)")
                //.style("text-anchor", "start");
                .style("text-anchor", "middle")
                .style("fill", "#b2b2b2");

            // format x-axis lines for minor and major ticks
            //TODO: check this line to separate years starting in Jan. Maybe it is not needed
            xaxis.selectAll("g.tick line")
                .attr("y2", d => { // d is the tick value
                    if (d3.timeFormat("%m")(d) == "01") //if it is Jan
                        return -height - 6;
                    else
                        return 6;
                })
                .attr("transform", d => { // d is the tick value
                    if (d3.timeFormat("%m")(d) == "01") //if it is Jan
                        return "translate(0,6)";
                })
                .attr("class", d => { // d is the tick value
                    if (d3.timeFormat("%m")(d) == "01") //if it is Jan
                        return "yearTick";
                });

            let yaxis = g.select(".y.axis");
            yaxis.transition()
                .duration(500)
                .call(d3.axisLeft(yLine)
                    /*.tickFormat(d3.format(".2s"))*/
                );

            // color axis labels
            g.selectAll(".y.axis g.tick text")
                .style("fill", "#b2b2b2"); // function (d,i) {return (i%2)?"red":"blue";})

            let title = selection.select(".title.label");
            title.text(chart.title())
                .style("fill", "#b2b2b2");

            selection.select(".title.label")
                .append("tspan")
                .attr("class", "far")
                .html(" \uf059")
                //.style("color", "#b2b2b2")
                .style("cursor", "pointer")
                .on("click", () => { // when clicking ? display modal with information about indicator
                    $("#explainIndicatorModal").on("show.bs.modal", () => {
                        let indicatorName = indicatorsNames[indicators.indexOf(id)]
                        $("#explainIndicatorModal").find("#explainIndicatorModalTitle").text(indicatorName);
                        $("#explainIndicatorModal").find(".modal-body").html(indicatorVars[id].explanation);
                    });
                    $("#explainIndicatorModal").modal();
                });

            // update chart
            updateData = () => {
                let time = map.timeDimension.getCurrentTime();

                // update data ranges using values from displayName and lineVariables
                minDate = d3.min(data[0].values, d => {
                    return d[displayName];
                });

                minDate = d3.timeDay.offset(minDate, -15) // get previous month to get correct x-axis alignment
                maxDate = d3.max(data[0].values, d => {
                    return d[displayName];
                });
                maxDate = d3.timeDay.offset(maxDate, 15) // get next month to get correct x-axis alignment

                // update axes' domain
                xLine.domain([minDate, maxDate])
                    //.nice()
                    //.nice(d3.timeMonth)
                    .range([0, width - margin.left - margin.right - 30])
                    .clamp(true);
                //yLine.domain(d3.extent(data, function(d) { return d[lineVariables[0]]; }))
                //yLine.domain([0, d3.max(data, function(d) { return d[lineVariables[0]]; })]).nice()

                // Calculate min and max values of data to define chart y-axis domain
                //let minDomain = d3.min(data.map(d => d.values).flat().map(d => d.value));
                let maxDomain = d3.max(data.map(d => d.values).flat().map(d => d.value));
                yLine.domain([0, maxDomain])
                    //yLine.domain([0, d3.max(data[0].values, d => d.value)]).nice()
                    .range([height - margin.left - margin.right, 0 + 35])
                    .nice();

                // filter data to only use the ones needed for current time
                let filteredData = [];
                data.forEach((d, i) => {
                    filteredData.push({
                        name: d.name,
                        values: []
                    });
                    d.values.filter(v => {
                        if (v.date <= new Date(time)) {
                            filteredData[i].values.push(v);
                        }
                    });
                });

                // update axes
                let g = selection.select(".lineChart");

                // update axis line with no ticks
                let xaxis = g.select(".x.axis");
                xaxis.transition()
                    .duration(500)
                    .call(
                        d3.axisBottom(xLine)
                        .ticks(0)
                        .tickSize(0)
                    );

                // update axis ticks - one for each month
                let xaxisTicks = g.select(".x.axis-ticks");
                let monthsInGraph = [];
                userDates.forEach(d => {
                    monthsInGraph.push(new Date(d));
                });

                xaxisTicks.transition()
                    .duration(500)
                    .call(
                        d3.axisBottom(xLine)
                        .tickValues(monthsInGraph)
                        .tickFormat("")
                    );

                // update axis month labels depending on how many months there are
                let xaxisLabels = g.select(".x.axis-month-labels");
                // FIXME: check how this displays ticks when there are many months/years
                // this fix gives up to 10 ticks
                // maybe need to set up more conditions
                let numberOfTicks = monthsInGraph.length < 6 ? d3.timeMonth.every(1) : 6;
                xaxisLabels.transition().duration(500)
                    .call(
                        d3.axisBottom(xLine)
                        .ticks(numberOfTicks)
                        .tickFormat(d3.timeFormat("%b"))
                    )
                    .selectAll("text")
                    .style("fill", "#b2b2b2")
                    .attr("font-size", "12px");

                // update axis year labels depending on how many months there are
                let xaxisYearLabels = g.select(".x.axis-year-labels");
                xaxisYearLabels.transition().duration(500)
                    .call(
                        d3.axisBottom(xLine)
                        .ticks(numberOfTicks)
                        .tickFormat(d3.timeFormat("'%y"))
                    )
                    .selectAll("text")
                    .style("fill", "#b2b2b2")
                    .attr("font-size", "12px");

                // format x-axis lines for minor and major ticks
                //TODO: check this line to separate years starting in Jan. Maybe it is not needed
                xaxis.selectAll("g.tick line")
                    .attr("y2", d => { // d is the tick value
                        if (d3.timeFormat("%m")(d) == "01") //if it is Jan
                            return -height - 6;
                        else
                            return 6;
                    })
                    .attr("transform", d => { // d is the tick value
                        if (d3.timeFormat("%m")(d) == "01") //if it is Jan
                            return "translate(0,6)";
                    })
                    .attr("class", d => { // d is the tick value
                        if (d3.timeFormat("%m")(d) == "01") //if it is Jan
                            return "yearTick";
                    });

                let yaxis = g.select(".y.axis");
                yaxis.transition()
                    .duration(500)
                    .call(d3.axisLeft(yLine)
                        .tickFormat(yAxisFormat));

                // color axis labels
                g.selectAll(".y.axis g.tick text")
                    .style("fill", "#b2b2b2");

                // define line
                let line = d3.line()
                    .defined(d => d)
                    .curve(d3.curveMonotoneX)
                    .x(d => xLine(d.date))
                    .y(d => yLine(d.value));

                // get lines
                let lines = g.select(".lines");
                lines.selectAll("path.line")
                    .data(filteredData)
                    .join("path") // handle enter, update, and exit automagically
                    .attr("stroke", (d, i) => {
                        return i == 0 ? defaultLineColor : lineColors(i - 1);
                    })
                    .attr("class", "line")
                    .attr("fill", "none")
                    .attr("stroke-linejoin", "round")
                    .attr("stroke-linecap", "round")
                    .attr("stroke-width", 1.5)
                    .attr("d", d => line(d.values))

                // get circles
                let circles = g.select(".circles"),
                    tooltip = d3.select(".tooltip");
                //tooltipFormat = d3.timeFormat("%b "%y"),
                //tooltipFormat = d3.format(",.2f")
                //tooltipFormat = d3.format(",.3s");

                circles.selectAll("g") // using nested data, so need to make a g to contain stuff
                    .data(filteredData)
                    .join( // handle enter, update, and exit separately for overall g circle containers
                        enter => enter
                        .append("g")
                        .attr("class", d => d.name)
                        .attr("stroke", (d, i) => {
                            return i == 0 ? defaultPointColor : pointColors(i - 1);
                        })
                        .attr("fill", (d, i) => {
                            return i == 0 ? defaultPointColor : pointColors(i - 1);
                        }),
                        update => update
                        .attr("class", d => d.name)
                        .attr("stroke", (d, i) => {
                            return i == 0 ? defaultPointColor : pointColors(i - 1);
                        })
                        .attr("fill", (d, i) => {
                            return i == 0 ? defaultPointColor : pointColors(i - 1);
                        }),
                        exit => exit.remove()
                    )
                    // nested join
                    .selectAll("circle").data(d => d.values) // access internal values array for circles here
                    .join( // handle enter, update, and exit separately for circles
                        enter => enter
                        .append("circle")
                        .attr("class", "circle")
                        .attr("r", 2.5)
                        .attr("cx", d => xLine(d.date))
                        .attr("cy", d => yLine(d.value))
                        .on("mouseover", d => {
                            tooltip.html(`${tooltipFormat(d.value)} ${tooltipUnits}`);
                            let tpWidth = tooltip.node().offsetWidth; // to center tooltip
                            tooltip.style("left", `${d3.event.pageX - tpWidth / 2}px`)
                                .style("top", `${d3.event.pageY - 23}px`)
                                .style("font-family", "Consolas, courier")
                                .style("font-size", "10pt")
                                .transition()
                                .duration(200)
                                .style("opacity", .9);
                        })
                        .on("mouseout", () => {
                            tooltip.transition()
                                .duration(500)
                                .style("opacity", 0);
                        }),
                        update => update
                        .attr("cx", d => xLine(d.date))
                        .attr("cy", d => yLine(d.value)),
                        exit => exit.remove()
                    );
            }
        });
    }

    chart.width = function(value) {
        if (!arguments.length) return width;
        width = value;
        return chart;
    };

    chart.height = function(value) {
        if (!arguments.length) return height;
        height = value;
        return chart;
    };

    chart.margin = function(value) {
        if (!arguments.length) return margin;
        margin = value;
        return chart;
    };

    chart.id = function(value) {
        if (!arguments.length) return id;
        id = value;
        return chart;
    };

    chart.lineVariables = function(value) {
        if (!arguments.length) return lineVariables;
        lineVariables = value;
        return chart;
    };

    chart.displayName = function(value) {
        if (!arguments.length) return displayName;
        displayName = value;
        return chart;
    };

    chart.transitionTime = function(value) {
        if (!arguments.length) return transitionTime;
        transitionTime = value;
        return chart;
    };

    chart.legend = function(value) {
        if (!arguments.length) return legend;
        legend = value;
        return chart;
    };

    chart.legendContainer = function(value) {
        if (!arguments.length) return legendContainer;
        legendContainer = value;
        return chart;
    };

    chart.data = function(value) {
        if (!arguments.length) return data;
        data = value;
        if (typeof updateData === "function") updateData();
        return chart;
    };

    chart.lineColors = function(value) {
        if (!arguments.length) return lineColors;
        lineColors = value;
        return chart;
    };

    chart.pointColors = function(value) {
        if (!arguments.length) return pointColors;
        pointColors = value;
        return chart;
    };

    chart.xAxisFormat = function(value) {
        if (!arguments.length) return xAxisFormat;
        xAxisFormat = value;
        return chart;
    };

    chart.yAxisFormat = function(value) {
        if (!arguments.length) return yAxisFormat;
        yAxisFormat = value;
        return chart;
    };

    chart.transitionTime = function(value) {
        if (!arguments.length) return transitionTime;
        transitionTime = value;
        return chart;
    };

    chart.lineAxisLabel = function(value) {
        if (!arguments.length) return lineAxisLabel;
        lineAxisLabel = value;
        return chart;
    };

    chart.title = function(value) {
        if (!arguments.length) return title;
        title = value;
        return chart;
    };

    chart.tooltipUnits = function(value) {
        if (!arguments.length) return tooltipUnits;
        tooltipUnits = value;
        return chart;
    };

    chart.tooltipFormat = function(value) {
        if (!arguments.length) return tooltipFormat;
        tooltipFormat = value;
        return chart;
    };

    return chart;
}
