import {
    area,
    line,
} from 'd3-shape';
import { interpolateArray } from 'd3-interpolate';
import { timer } from 'd3-timer';
import { mouse } from 'd3-selection';
import { easeCubic } from 'd3-ease';
import {
    uuid,
    linearStops
} from 'vizart-core';

import AbstractCanvasChart from '../../canvas/AbstractCanvasChart';
import createCartesianOpt from '../../options/createCartesianOpt';
import interpolateCurve from '../../util/curve';
import applyQuadtree from '../../canvas/quadtree/apply';
import applyVoronoi from '../../canvas/voronoi/apply';
import linearGradient from '../../canvas/gradient-stroke';
import genColorByIndex from '../../canvas/generate-color';
import tooltipMarkup from'../../canvas/tooltip';

const AreaOpt = {
    chart: {
        type: 'area_horizontal'
    },
    plots: {
        areaOpacity: 0.3,
        curve: 'basis',
        strokeWidth: 2,
        nodeRadius: 4,
        drawArea: true,
        showDots: true
    }
};


const nodeColor = opt=> {
    const stops = linearStops(opt.color.scheme);
    return stops[stops.length - 1].color;
}

const drawPoints = (context, particles, opt, hidden)=> {
    for (const [i, p] of particles.entries()) {
        context.beginPath();
        context.fillStyle = hidden === true? genColorByIndex(i) : nodeColor(opt);
        context.globalAlpha = p.alpha;
        context.arc(p.x, p.y, p.r, 0, 2 * Math.PI, false);
        context.fill();
    }
}

const drawLine = (context, particles, opt)=> {
    const curve = line()
            .x(d=>d.x)
            .y(d=>d.y)
            .context(context);
    interpolateCurve(opt.plots.curve, [curve]);

    context.beginPath();
    curve(particles);
    context.lineWidth = opt.plots.strokeWidth;
    const gradientStyle = linearGradient(context, opt.color.scheme, 1);
    context.strokeStyle = gradientStyle;

    context.stroke();
    context.closePath();
}

const drawArea = (context, particles, opt)=> {
    const curve = area()
        .x(d=>d.x)
        .y0(context.canvas.height)
        .y1(d=>d.y)
        .context(context);

    interpolateCurve(opt.plots.curve, [curve]);
    context.beginPath();
    curve(particles);
    context.lineWidth = opt.plots.strokeWidth;
    const gradientStyle = linearGradient(context, opt.color.scheme, opt.plots.areaOpacity);
    context.fillStyle = gradientStyle;
    context.strokeStyle = nodeColor(opt);
    context.stroke();

    context.fill();
    context.closePath();
}

/**
 * a particle contains x, y, r, c, alpha
 *
 * @param context
 * @param particles, particle colors may be defined in rgb string and thus cannot be recognized by
 * canvas. This is caused by d3's interpolation.
 */
const draw = (context, particles, opt, hidden = false)=> {
    context.clearRect(0, 0, context.canvas.width, context.canvas.height);

    if (hidden === true) {
        drawPoints(context, particles, opt, true);
    } else {
        if (opt.plots.showDots === true) {
            drawPoints(context, particles, opt, false);
        }

        if (opt.plots.drawArea === true) {
            drawArea(context, particles, opt);
        } else {
            drawLine(context, particles, opt);
        }
    }
}

class Area extends AbstractCanvasChart {
    constructor(canvasId, _userOptions) {
        super(canvasId, _userOptions);
    }

    _animate() {
        const Duration = this._options.animation.duration.update;
        const stops = linearStops(this._options.color.scheme);
        const nodeColor = stops[stops.length - 1].color;

        const initialState = this.previousState
            ? this.previousState
            : this._data.map(d=>{
                return {
                    x: this._x(d),
                    y: this._frontCanvas.node().height,
                    r: this._options.plots.nodeRadius,
                    c: nodeColor,
                    alpha: 0,
                    data: d,
                    _: {
                        x: this._getDimensionVal(d),
                        y: this._getMetricVal(d),
                        metric: this._getMetric().name,
                        style: `border-color: ${nodeColor};`
                    }
                }
        });

        const finalState = this._data.map(d=>{
            return {
                x: this._x(d),
                y: this._y(d),
                r: this._options.plots.nodeRadius,
                c: nodeColor,
                alpha: 1,
                data: d,
                _: {
                    x: this._getDimensionVal(d),
                    y: this._getMetricVal(d),
                    metric: this._getMetric().name,
                    style: `border-color: ${nodeColor};`
                }
            }
        });

        // cache finalState as the initial state of next animation call
        this.previousState = finalState;

        const interpolateParticles = interpolateArray(initialState, finalState);

        let that = this;
        const batchRendering = timer( (elapsed)=> {
            const t = Math.min(1, easeCubic(elapsed / Duration));

            draw(that._frontContext,
                interpolateParticles(t),
                that._options);

            if (t === 1) {
                batchRendering.stop();

                that._voronoi = applyVoronoi(that._frontContext,
                    that._options, finalState);

                that._quadtree = applyQuadtree(that._frontContext,
                    that._options, finalState);

                /**
                 * callback for when the mouse moves across the overlay
                 */
                function mouseMoveHandler() {
                    // get the current mouse position
                    const [mx, my] = mouse(this);
                    const QuadtreeRadius = 100;
                    // use the new diagram.find() function to find the Voronoi site
                    // closest to the mouse, limited by max distance voronoiRadius
                    const closest = that._voronoi.find(mx, my, QuadtreeRadius);

                    if (closest) {
                        that._tooltip.style("left", closest[0] + "px")
                            .style("top", closest[1] + "px")
                            .html( tooltipMarkup(closest.data._));

                        that._tooltip.style("opacity", 1)
                    } else {
                        that._tooltip.style("opacity", 0)
                    }
                }

                function mouseOutHandler() {
                    that._tooltip.style("opacity", 0)
                }

                that._frontCanvas.on('mousemove', mouseMoveHandler);
                that._frontCanvas.on('mouseout', mouseOutHandler);

                // draw hidden in parallel;
                draw(that._hiddenContext,
                    interpolateParticles(t),
                    that._options, true);

                that._listeners.call('rendered');
            }
        });
    }

    createOptions(_userOpt) {
        return createCartesianOpt(AreaOpt, _userOpt);
    };
}


export default Area;
@@ -3884,276 +3884,8 @@
 a: d
-,%0A                    _: %7B%0A                        x: this._getDimensionVal(d),%0A                        y: this._getMetricVal(d),%0A                        metric: this._getMetric().name,%0A                        style: %60border-color: $%7BnodeColor%7D;%60%0A                    %7D
 %0A   
@@ -4176,252 +4176,8 @@
 a: d
-,%0A                _: %7B%0A                    x: this._getDimensionVal(d),%0A                    y: this._getMetricVal(d),%0A                    metric: this._getMetric().name,%0A                    style: %60border-color: $%7BnodeColor%7D;%60%0A                %7D
 %0A   
@@ -4714,24 +4714,367 @@
 ng.stop();%0A%0A
+                for (let d of finalState) %7B%0A                    d._ = %7B%0A                        x: that._getDimensionVal(d.data),%0A                        y: that._getMetricVal(d.data),%0A                        metric: that._getMetric().name,%0A                        style: %60border-color: $%7BnodeColor%7D;%60%0A                    %7D%0A                %7D%0A%0A
             

reduce interpolate burdern