/**
 * @author tlwr [toby@toby.codes]
 * @author Matt C [me@mitt.dev]
 * @copyright Crown Copyright 2019
 * @license Apache-2.0
 */

import * as d3temp from "d3";
import * as nodomtemp from "nodom";
import { getSeriesValues, RECORD_DELIMITER_OPTIONS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts";

import Operation from "../Operation";
import Utils from "../Utils";

const d3 = d3temp.default ? d3temp.default : d3temp;
const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp;

/**
 * Series chart operation
 */
class SeriesChart extends Operation {

    /**
     * SeriesChart constructor
     */
    constructor() {
        super();

        this.name = "Series chart";
        this.module = "Charts";
        this.description = "A time series graph is a line graph of repeated measurements taken over regular time intervals.";
        this.inputType = "string";
        this.outputType = "html";
        this.args = [
            {
                name: "Record delimiter",
                type: "option",
                value: RECORD_DELIMITER_OPTIONS,
            },
            {
                name: "Field delimiter",
                type: "option",
                value: FIELD_DELIMITER_OPTIONS,
            },
            {
                name: "X label",
                type: "string",
                value: "",
            },
            {
                name: "Point radius",
                type: "number",
                value: 1,
            },
            {
                name: "Series colours",
                type: "string",
                value: "mediumseagreen, dodgerblue, tomato",
            },
        ];
    }

    /**
     * Series chart operation.
     *
     * @param {string} input
     * @param {Object[]} args
     * @returns {html}
     */
    run(input, args) {
        const recordDelimiter = Utils.charRep(args[0]),
            fieldDelimiter = Utils.charRep(args[1]),
            xLabel = args[2],
            pipRadius = args[3],
            seriesColours = args[4].split(","),
            svgWidth = 500,
            interSeriesPadding = 20,
            xAxisHeight = 50,
            seriesLabelWidth = 50,
            seriesHeight = 100,
            seriesWidth = svgWidth - seriesLabelWidth - interSeriesPadding;

        const { xValues, series } = getSeriesValues(input, recordDelimiter, fieldDelimiter),
            allSeriesHeight = Object.keys(series).length * (interSeriesPadding + seriesHeight),
            svgHeight = allSeriesHeight + xAxisHeight + interSeriesPadding;

        const document = new nodom.Document();
        let svg = document.createElement("svg");
        svg = d3.select(svg)
            .attr("width", "100%")
            .attr("height", "100%")
            .attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`);

        const xAxis = d3.scalePoint()
            .domain(xValues)
            .range([0, seriesWidth]);

        svg.append("g")
            .attr("class", "axis axis--x")
            .attr("transform", `translate(${seriesLabelWidth}, ${xAxisHeight})`)
            .call(
                d3.axisTop(xAxis).tickValues(xValues.filter((x, i) => {
                    return [0, Math.round(xValues.length / 2), xValues.length -1].indexOf(i) >= 0;
                }))
            );

        svg.append("text")
            .attr("x", svgWidth / 2)
            .attr("y", xAxisHeight / 2)
            .style("text-anchor", "middle")
            .text(xLabel);

        const tooltipText = {},
            tooltipAreaWidth = seriesWidth / xValues.length;

        xValues.forEach(x => {
            const tooltip = [];

            series.forEach(serie => {
                const y = serie.data[x];
                if (typeof y === "undefined") return;

                tooltip.push(`${serie.name}: ${y}`);
            });

            tooltipText[x] = tooltip.join("\n");
        });

        const chartArea = svg.append("g")
            .attr("transform", `translate(${seriesLabelWidth}, ${xAxisHeight})`);

        chartArea
            .append("g")
            .selectAll("rect")
            .data(xValues)
            .enter()
            .append("rect")
            .attr("x", x => {
                return xAxis(x) - (tooltipAreaWidth / 2);
            })
            .attr("y", 0)
            .attr("width", tooltipAreaWidth)
            .attr("height", allSeriesHeight)
            .attr("stroke", "none")
            .attr("fill", "transparent")
            .append("title")
            .text(x => {
                return `${x}\n
                    --\n
                    ${tooltipText[x]}\n
                `.replace(/\s{2,}/g, "\n");
            });

        const yAxesArea = svg.append("g")
            .attr("transform", `translate(0, ${xAxisHeight})`);

        series.forEach((serie, seriesIndex) => {
            const yExtent = d3.extent(Object.values(serie.data)),
                yAxis = d3.scaleLinear()
                    .domain(yExtent)
                    .range([seriesHeight, 0]);

            const seriesGroup = chartArea
                .append("g")
                .attr("transform", `translate(0, ${seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`);

            let path = "";
            xValues.forEach((x, xIndex) => {
                let nextX = xValues[xIndex + 1],
                    y = serie.data[x],
                    nextY= serie.data[nextX];

                if (typeof y === "undefined" || typeof nextY === "undefined") return;

                x = xAxis(x); nextX = xAxis(nextX);
                y = yAxis(y); nextY = yAxis(nextY);

                path += `M ${x} ${y} L ${nextX} ${nextY} z `;
            });

            seriesGroup
                .append("path")
                .attr("d", path)
                .attr("fill", "none")
                .attr("stroke", seriesColours[seriesIndex % seriesColours.length])
                .attr("stroke-width", "1");

            xValues.forEach(x => {
                const y = serie.data[x];
                if (typeof y === "undefined") return;

                seriesGroup
                    .append("circle")
                    .attr("cx", xAxis(x))
                    .attr("cy", yAxis(y))
                    .attr("r", pipRadius)
                    .attr("fill", seriesColours[seriesIndex % seriesColours.length])
                    .append("title")
                    .text(d => {
                        return `${x}\n
                            --\n
                            ${tooltipText[x]}\n
                        `.replace(/\s{2,}/g, "\n");
                    });
            });

            yAxesArea
                .append("g")
                .attr("transform", `translate(${seriesLabelWidth - interSeriesPadding}, ${seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`)
                .attr("class", "axis axis--y")
                .call(d3.axisLeft(yAxis).ticks(5));

            yAxesArea
                .append("g")
                .attr("transform", `translate(0, ${seriesHeight / 2 + seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`)
                .append("text")
                .style("text-anchor", "middle")
                .attr("transform", "rotate(-90)")
                .text(serie.name);
        });

        return svg._groups[0][0].outerHTML;
    }

}

export default SeriesChart;
