'use strict';

var d3 = require('@plotly/d3');

var Drawing = require('../../components/drawing');
var Lib = require('../../lib');
var strScale = Lib.strScale;
var strTranslate = Lib.strTranslate;
var svgTextUtils = require('../../lib/svg_text_utils');

var barPlot = require('../bar/plot');
var toMoveInsideBar = barPlot.toMoveInsideBar;
var uniformText = require('../bar/uniform_text');
var recordMinTextSize = uniformText.recordMinTextSize;
var clearMinTextSize = uniformText.clearMinTextSize;
var pieHelpers = require('../pie/helpers');
var piePlot = require('../pie/plot');

var attachFxHandlers = piePlot.attachFxHandlers;
var determineInsideTextFont = piePlot.determineInsideTextFont;

var layoutAreas = piePlot.layoutAreas;
var prerenderTitles = piePlot.prerenderTitles;
var positionTitleOutside = piePlot.positionTitleOutside;
var formatSliceLabel = piePlot.formatSliceLabel;

module.exports = function plot(gd, cdModule) {
    var isStatic = gd._context.staticPlot;

    var fullLayout = gd._fullLayout;

    clearMinTextSize('funnelarea', fullLayout);

    prerenderTitles(cdModule, gd);
    layoutAreas(cdModule, fullLayout._size);

    Lib.makeTraceGroups(fullLayout._funnelarealayer, cdModule, 'trace').each(function(cd) {
        var plotGroup = d3.select(this);
        var cd0 = cd[0];
        var trace = cd0.trace;

        setCoords(cd);

        plotGroup.each(function() {
            var slices = d3.select(this).selectAll('g.slice').data(cd);

            slices.enter().append('g')
                .classed('slice', true);
            slices.exit().remove();

            slices.each(function(pt, i) {
                if(pt.hidden) {
                    d3.select(this).selectAll('path,g').remove();
                    return;
                }

                // to have consistent event data compared to other traces
                pt.pointNumber = pt.i;
                pt.curveNumber = trace.index;

                var cx = cd0.cx;
                var cy = cd0.cy;
                var sliceTop = d3.select(this);
                var slicePath = sliceTop.selectAll('path.surface').data([pt]);

                slicePath.enter().append('path')
                    .classed('surface', true)
                    .style({'pointer-events': isStatic ? 'none' : 'all'});

                sliceTop.call(attachFxHandlers, gd, cd);

                var shape =
                    'M' + (cx + pt.TR[0]) + ',' + (cy + pt.TR[1]) +
                    line(pt.TR, pt.BR) +
                    line(pt.BR, pt.BL) +
                    line(pt.BL, pt.TL) +
                    'Z';

                slicePath.attr('d', shape);

                // add text
                formatSliceLabel(gd, pt, cd0);
                var textPosition = pieHelpers.castOption(trace.textposition, pt.pts);
                var sliceTextGroup = sliceTop.selectAll('g.slicetext')
                    .data(pt.text && (textPosition !== 'none') ? [0] : []);

                sliceTextGroup.enter().append('g')
                    .classed('slicetext', true);
                sliceTextGroup.exit().remove();

                sliceTextGroup.each(function() {
                    var sliceText = Lib.ensureSingle(d3.select(this), 'text', '', function(s) {
                        // prohibit tex interpretation until we can handle
                        // tex and regular text together
                        s.attr('data-notex', 1);
                    });

                    var font = Lib.ensureUniformFontSize(gd, determineInsideTextFont(trace, pt, fullLayout.font));

                    sliceText.text(pt.text)
                        .attr({
                            class: 'slicetext',
                            transform: '',
                            'text-anchor': 'middle'
                        })
                        .call(Drawing.font, font)
                        .call(svgTextUtils.convertToTspans, gd);

                    // position the text relative to the slice
                    var textBB = Drawing.bBox(sliceText.node());
                    var transform;

                    var x0, x1;
                    var y0 = Math.min(pt.BL[1], pt.BR[1]) + cy;
                    var y1 = Math.max(pt.TL[1], pt.TR[1]) + cy;

                    x0 = Math.max(pt.TL[0], pt.BL[0]) + cx;
                    x1 = Math.min(pt.TR[0], pt.BR[0]) + cx;

                    transform = toMoveInsideBar(x0, x1, y0, y1, textBB, {
                        isHorizontal: true,
                        constrained: true,
                        angle: 0,
                        anchor: 'middle'
                    });

                    transform.fontSize = font.size;
                    recordMinTextSize(trace.type, transform, fullLayout);
                    cd[i].transform = transform;

                    Lib.setTransormAndDisplay(sliceText, transform);
                });
            });

            // add the title
            var titleTextGroup = d3.select(this).selectAll('g.titletext')
                .data(trace.title.text ? [0] : []);

            titleTextGroup.enter().append('g')
                .classed('titletext', true);
            titleTextGroup.exit().remove();

            titleTextGroup.each(function() {
                var titleText = Lib.ensureSingle(d3.select(this), 'text', '', function(s) {
                    // prohibit tex interpretation as above
                    s.attr('data-notex', 1);
                });

                var txt = trace.title.text;
                if(trace._meta) {
                    txt = Lib.templateString(txt, trace._meta);
                }

                titleText.text(txt)
                    .attr({
                        class: 'titletext',
                        transform: '',
                        'text-anchor': 'middle',
                    })
                .call(Drawing.font, trace.title.font)
                .call(svgTextUtils.convertToTspans, gd);

                var transform = positionTitleOutside(cd0, fullLayout._size);

                titleText.attr('transform',
                    strTranslate(transform.x, transform.y) +
                    strScale(Math.min(1, transform.scale)) +
                    strTranslate(transform.tx, transform.ty));
            });
        });
    });
};

function line(a, b) {
    var dx = b[0] - a[0];
    var dy = b[1] - a[1];

    return 'l' + dx + ',' + dy;
}

function getBetween(a, b) {
    return [
        0.5 * (a[0] + b[0]),
        0.5 * (a[1] + b[1])
    ];
}

function setCoords(cd) {
    if(!cd.length) return;

    var cd0 = cd[0];
    var trace = cd0.trace;

    var aspectratio = trace.aspectratio;

    var h = trace.baseratio;
    if(h > 0.999) h = 0.999; // TODO: may handle this case separately
    var h2 = Math.pow(h, 2);

    var v1 = cd0.vTotal;
    var v0 = v1 * h2 / (1 - h2);

    var totalValues = v1;
    var sumSteps = v0 / v1;

    function calcPos() {
        var q = Math.sqrt(sumSteps);
        return {
            x: q,
            y: -q
        };
    }

    function getPoint() {
        var pos = calcPos();
        return [pos.x, pos.y];
    }

    var p;
    var allPoints = [];
    allPoints.push(getPoint());

    var i, cdi;
    for(i = cd.length - 1; i > -1; i--) {
        cdi = cd[i];
        if(cdi.hidden) continue;

        var step = cdi.v / totalValues;
        sumSteps += step;

        allPoints.push(getPoint());
    }

    var minY = Infinity;
    var maxY = -Infinity;
    for(i = 0; i < allPoints.length; i++) {
        p = allPoints[i];
        minY = Math.min(minY, p[1]);
        maxY = Math.max(maxY, p[1]);
    }

    // center the shape
    for(i = 0; i < allPoints.length; i++) {
        allPoints[i][1] -= (maxY + minY) / 2;
    }

    var lastX = allPoints[allPoints.length - 1][0];

    // get pie r
    var r = cd0.r;

    var rY = (maxY - minY) / 2;
    var scaleX = r / lastX;
    var scaleY = r / rY * aspectratio;

    // set funnelarea r
    cd0.r = scaleY * rY;

    // scale the shape
    for(i = 0; i < allPoints.length; i++) {
        allPoints[i][0] *= scaleX;
        allPoints[i][1] *= scaleY;
    }

    // record first position
    p = allPoints[0];
    var prevLeft = [-p[0], p[1]];
    var prevRight = [p[0], p[1]];

    var n = 0; // note we skip the very first point.
    for(i = cd.length - 1; i > -1; i--) {
        cdi = cd[i];
        if(cdi.hidden) continue;

        n += 1;
        var x = allPoints[n][0];
        var y = allPoints[n][1];

        cdi.TL = [-x, y];
        cdi.TR = [x, y];

        cdi.BL = prevLeft;
        cdi.BR = prevRight;

        cdi.pxmid = getBetween(cdi.TR, cdi.BR);

        prevLeft = cdi.TL;
        prevRight = cdi.TR;
    }
}
