'use strict';

var d3 = require('@plotly/d3');
var Lib = require('../../lib');
var numberFormat = Lib.numberFormat;
var render = require('./render');
var Fx = require('../../components/fx');
var Color = require('../../components/color');
var cn = require('./constants').cn;

var _ = Lib._;

function renderableValuePresent(d) {return d !== '';}

function ownTrace(selection, d) {
    return selection.filter(function(s) {return s.key === d.traceId;});
}

function makeTranslucent(element, alpha) {
    d3.select(element)
        .select('path')
        .style('fill-opacity', alpha);
    d3.select(element)
        .select('rect')
        .style('fill-opacity', alpha);
}

function makeTextContrasty(element) {
    d3.select(element)
        .select('text.name')
        .style('fill', 'black');
}

function relatedLinks(d) {
    return function(l) {
        return d.node.sourceLinks.indexOf(l.link) !== -1 || d.node.targetLinks.indexOf(l.link) !== -1;
    };
}

function relatedNodes(l) {
    return function(d) {
        return d.node.sourceLinks.indexOf(l.link) !== -1 || d.node.targetLinks.indexOf(l.link) !== -1;
    };
}

function nodeHoveredStyle(sankeyNode, d, sankey) {
    if(d && sankey) {
        ownTrace(sankey, d)
            .selectAll('.' + cn.sankeyLink)
            .filter(relatedLinks(d))
            .call(linkHoveredStyle.bind(0, d, sankey, false));
    }
}

function nodeNonHoveredStyle(sankeyNode, d, sankey) {
    if(d && sankey) {
        ownTrace(sankey, d)
            .selectAll('.' + cn.sankeyLink)
            .filter(relatedLinks(d))
            .call(linkNonHoveredStyle.bind(0, d, sankey, false));
    }
}

function linkHoveredStyle(d, sankey, visitNodes, sankeyLink) {
    sankeyLink.style('fill', function(l) {
        if(!l.link.concentrationscale) {
            return l.tinyColorHoverHue;
        }
    }).style('fill-opacity', function(l) {
        if(!l.link.concentrationscale) {
            return l.tinyColorHoverAlpha;
        }
    });

    sankeyLink.each(function(curLink) {
        var label = curLink.link.label;
        if(label !== '') {
            ownTrace(sankey, d)
                .selectAll('.' + cn.sankeyLink)
                .filter(function(l) {return l.link.label === label;})
                .style('fill', function(l) {
                    if(!l.link.concentrationscale) {
                        return l.tinyColorHoverHue;
                    }
                }).style('fill-opacity', function(l) {
                    if(!l.link.concentrationscale) {
                        return l.tinyColorHoverAlpha;
                    }
                });
        }
    });

    if(visitNodes) {
        ownTrace(sankey, d)
            .selectAll('.' + cn.sankeyNode)
            .filter(relatedNodes(d))
            .call(nodeHoveredStyle);
    }
}

function linkNonHoveredStyle(d, sankey, visitNodes, sankeyLink) {
    sankeyLink.style('fill', function(l) {
        return l.tinyColorHue;
    }).style('fill-opacity', function(l) {
        return l.tinyColorAlpha;
    });

    sankeyLink.each(function(curLink) {
        var label = curLink.link.label;
        if(label !== '') {
            ownTrace(sankey, d)
                .selectAll('.' + cn.sankeyLink)
                .filter(function(l) {return l.link.label === label;})
                .style('fill', function(l) {return l.tinyColorHue;})
                .style('fill-opacity', function(l) {return l.tinyColorAlpha;});
        }
    });

    if(visitNodes) {
        ownTrace(sankey, d)
            .selectAll(cn.sankeyNode)
            .filter(relatedNodes(d))
            .call(nodeNonHoveredStyle);
    }
}

// does not support array values for now
function castHoverOption(trace, attr) {
    var labelOpts = trace.hoverlabel || {};
    var val = Lib.nestedProperty(labelOpts, attr).get();
    return Array.isArray(val) ? false : val;
}

module.exports = function plot(gd, calcData) {
    var fullLayout = gd._fullLayout;
    var svg = fullLayout._paper;
    var size = fullLayout._size;

    // stash initial view
    for(var i = 0; i < gd._fullData.length; i++) {
        if(!gd._fullData[i].visible) continue;
        if(gd._fullData[i].type !== cn.sankey) continue;
        if(!gd._fullData[i]._viewInitial) {
            var node = gd._fullData[i].node;
            gd._fullData[i]._viewInitial = {
                node: {
                    groups: node.groups.slice(),
                    x: node.x.slice(),
                    y: node.y.slice()
                }
            };
        }
    }

    var linkSelect = function(element, d) {
        var evt = d.link;
        evt.originalEvent = d3.event;
        gd._hoverdata = [evt];
        Fx.click(gd, { target: true });
    };

    var linkHover = function(element, d, sankey) {
        if(gd._fullLayout.hovermode === false) return;
        d3.select(element).call(linkHoveredStyle.bind(0, d, sankey, true));
        if(d.link.trace.link.hoverinfo !== 'skip') {
            d.link.fullData = d.link.trace;
            gd.emit('plotly_hover', {
                event: d3.event,
                points: [d.link]
            });
        }
    };

    var sourceLabel = _(gd, 'source:') + ' ';
    var targetLabel = _(gd, 'target:') + ' ';
    var concentrationLabel = _(gd, 'concentration:') + ' ';
    var incomingLabel = _(gd, 'incoming flow count:') + ' ';
    var outgoingLabel = _(gd, 'outgoing flow count:') + ' ';

    var linkHoverFollow = function(element, d) {
        if(gd._fullLayout.hovermode === false) return;
        var obj = d.link.trace.link;
        if(obj.hoverinfo === 'none' || obj.hoverinfo === 'skip') return;

        var hoverItems = [];

        function hoverCenterPosition(link) {
            var hoverCenterX, hoverCenterY;
            if(link.circular) {
                hoverCenterX = (link.circularPathData.leftInnerExtent + link.circularPathData.rightInnerExtent) / 2;
                hoverCenterY = link.circularPathData.verticalFullExtent;
            } else {
                hoverCenterX = (link.source.x1 + link.target.x0) / 2;
                hoverCenterY = (link.y0 + link.y1) / 2;
            }
            var center = [hoverCenterX, hoverCenterY];
            if(link.trace.orientation === 'v') center.reverse();
            center[0] += d.parent.translateX;
            center[1] += d.parent.translateY;
            return center;
        }

        // For each related links, create a hoverItem
        var anchorIndex = 0;
        for(var i = 0; i < d.flow.links.length; i++) {
            var link = d.flow.links[i];
            if(gd._fullLayout.hovermode === 'closest' && d.link.pointNumber !== link.pointNumber) continue;
            if(d.link.pointNumber === link.pointNumber) anchorIndex = i;
            link.fullData = link.trace;
            obj = d.link.trace.link;
            var hoverCenter = hoverCenterPosition(link);
            var hovertemplateLabels = {valueLabel: numberFormat(d.valueFormat)(link.value) + d.valueSuffix};

            hoverItems.push({
                x: hoverCenter[0],
                y: hoverCenter[1],
                name: hovertemplateLabels.valueLabel,
                text: [
                    link.label || '',
                    sourceLabel + link.source.label,
                    targetLabel + link.target.label,
                    link.concentrationscale ? concentrationLabel + numberFormat('%0.2f')(link.flow.labelConcentration) : ''
                ].filter(renderableValuePresent).join('<br>'),
                color: castHoverOption(obj, 'bgcolor') || Color.addOpacity(link.color, 1),
                borderColor: castHoverOption(obj, 'bordercolor'),
                fontFamily: castHoverOption(obj, 'font.family'),
                fontSize: castHoverOption(obj, 'font.size'),
                fontColor: castHoverOption(obj, 'font.color'),
                fontWeight: castHoverOption(obj, 'font.weight'),
                fontStyle: castHoverOption(obj, 'font.style'),
                fontVariant: castHoverOption(obj, 'font.variant'),
                fontTextcase: castHoverOption(obj, 'font.textcase'),
                fontLineposition: castHoverOption(obj, 'font.lineposition'),
                fontShadow: castHoverOption(obj, 'font.shadow'),
                nameLength: castHoverOption(obj, 'namelength'),
                textAlign: castHoverOption(obj, 'align'),
                idealAlign: d3.event.x < hoverCenter[0] ? 'right' : 'left',

                hovertemplate: obj.hovertemplate,
                hovertemplateLabels: hovertemplateLabels,
                eventData: [link]
            });
        }

        var tooltips = Fx.loneHover(hoverItems, {
            container: fullLayout._hoverlayer.node(),
            outerContainer: fullLayout._paper.node(),
            gd: gd,
            anchorIndex: anchorIndex
        });

        tooltips.each(function() {
            var tooltip = this;
            if(!d.link.concentrationscale) {
                makeTranslucent(tooltip, 0.65);
            }
            makeTextContrasty(tooltip);
        });
    };

    var linkUnhover = function(element, d, sankey) {
        if(gd._fullLayout.hovermode === false) return;
        d3.select(element).call(linkNonHoveredStyle.bind(0, d, sankey, true));
        if(d.link.trace.link.hoverinfo !== 'skip') {
            d.link.fullData = d.link.trace;
            gd.emit('plotly_unhover', {
                event: d3.event,
                points: [d.link]
            });
        }

        Fx.loneUnhover(fullLayout._hoverlayer.node());
    };

    var nodeSelect = function(element, d, sankey) {
        var evt = d.node;
        evt.originalEvent = d3.event;
        gd._hoverdata = [evt];
        d3.select(element).call(nodeNonHoveredStyle, d, sankey);
        Fx.click(gd, { target: true });
    };

    var nodeHover = function(element, d, sankey) {
        if(gd._fullLayout.hovermode === false) return;
        d3.select(element).call(nodeHoveredStyle, d, sankey);
        if(d.node.trace.node.hoverinfo !== 'skip') {
            d.node.fullData = d.node.trace;
            gd.emit('plotly_hover', {
                event: d3.event,
                points: [d.node]
            });
        }
    };

    var nodeHoverFollow = function(element, d) {
        if(gd._fullLayout.hovermode === false) return;

        var obj = d.node.trace.node;
        if(obj.hoverinfo === 'none' || obj.hoverinfo === 'skip') return;
        var nodeRect = d3.select(element).select('.' + cn.nodeRect);
        var rootBBox = gd._fullLayout._paperdiv.node().getBoundingClientRect();
        var boundingBox = nodeRect.node().getBoundingClientRect();
        var hoverCenterX0 = boundingBox.left - 2 - rootBBox.left;
        var hoverCenterX1 = boundingBox.right + 2 - rootBBox.left;
        var hoverCenterY = boundingBox.top + boundingBox.height / 4 - rootBBox.top;

        var hovertemplateLabels = {valueLabel: numberFormat(d.valueFormat)(d.node.value) + d.valueSuffix};
        d.node.fullData = d.node.trace;

        gd._fullLayout._calcInverseTransform(gd);
        var scaleX = gd._fullLayout._invScaleX;
        var scaleY = gd._fullLayout._invScaleY;

        var tooltip = Fx.loneHover({
            x0: scaleX * hoverCenterX0,
            x1: scaleX * hoverCenterX1,
            y: scaleY * hoverCenterY,
            name: numberFormat(d.valueFormat)(d.node.value) + d.valueSuffix,
            text: [
                d.node.label,
                incomingLabel + d.node.targetLinks.length,
                outgoingLabel + d.node.sourceLinks.length
            ].filter(renderableValuePresent).join('<br>'),
            color: castHoverOption(obj, 'bgcolor') || d.tinyColorHue,
            borderColor: castHoverOption(obj, 'bordercolor'),
            fontFamily: castHoverOption(obj, 'font.family'),
            fontSize: castHoverOption(obj, 'font.size'),
            fontColor: castHoverOption(obj, 'font.color'),
            fontWeight: castHoverOption(obj, 'font.weight'),
            fontStyle: castHoverOption(obj, 'font.style'),
            fontVariant: castHoverOption(obj, 'font.variant'),
            fontTextcase: castHoverOption(obj, 'font.textcase'),
            fontLineposition: castHoverOption(obj, 'font.lineposition'),
            fontShadow: castHoverOption(obj, 'font.shadow'),
            nameLength: castHoverOption(obj, 'namelength'),
            textAlign: castHoverOption(obj, 'align'),
            idealAlign: 'left',

            hovertemplate: obj.hovertemplate,
            hovertemplateLabels: hovertemplateLabels,
            eventData: [d.node]
        }, {
            container: fullLayout._hoverlayer.node(),
            outerContainer: fullLayout._paper.node(),
            gd: gd
        });

        makeTranslucent(tooltip, 0.85);
        makeTextContrasty(tooltip);
    };

    var nodeUnhover = function(element, d, sankey) {
        if(gd._fullLayout.hovermode === false) return;
        d3.select(element).call(nodeNonHoveredStyle, d, sankey);
        if(d.node.trace.node.hoverinfo !== 'skip') {
            d.node.fullData = d.node.trace;
            gd.emit('plotly_unhover', {
                event: d3.event,
                points: [d.node]
            });
        }

        Fx.loneUnhover(fullLayout._hoverlayer.node());
    };

    render(
        gd,
        svg,
        calcData,
        {
            width: size.w,
            height: size.h,
            margin: {
                t: size.t,
                r: size.r,
                b: size.b,
                l: size.l
            }
        },
        {
            linkEvents: {
                hover: linkHover,
                follow: linkHoverFollow,
                unhover: linkUnhover,
                select: linkSelect
            },
            nodeEvents: {
                hover: nodeHover,
                follow: nodeHoverFollow,
                unhover: nodeUnhover,
                select: nodeSelect
            }
        }
    );
};
