<sampler-visual class="sampler-visual">
    <svg class="icon-array visual-{ sampler.id }" id="{ sampler.svgclass }"></svg>

    <script charset="utf-8">
        import * as d3 from '../d3custom';
        import config from './config_visual.json';

        export default {
            // life cycle
            onBeforeMount(props, state) {
                this.sampler = props.sampler;
            },

            onMounted(props, state) {

                let columns = (config.blocks.horizontal.amount * config.blocks.horizontal.size);
                let rows = config.blocks.vertical.amount * config.blocks.vertical.size;

                let vmrgn = (config.svgHeight - rows * (config.radius * (2 + config.dotspacingRatio))) * 0.65;  // yet another magic number
                let hmrgn = (config.svgWidth - columns * (config.radius * (2 + config.dotspacingRatio))) / 2 - config.axisWidth / 2;
                config.margin = {
                    right: hmrgn,
                    left: hmrgn,
                    top: vmrgn,
                    bottom: vmrgn
                };

                this.width = config.svgWidth - config.margin.left - config.margin.right;
                this.height = config.svgHeight - config.margin.top - config.margin.bottom;

                this.svg = d3.select('#' + this.sampler.svgclass)
                    .attr('width', this.width + config.margin.left + config.margin.right)
                    .attr('height', this.height + config.margin.top + config.margin.bottom);

                this.legendGroup = this.svg.append('g')
                    .attr('class', 'legend-group')
                    .attr('transform', 'translate(' + config.margin.left + ',' + config.margin.top + ')');

                this.mAxisGroup = this.legendGroup.append('g')
                    // .attr('class', 'class-label');
                    .attr('class', 'separator-group');

                this.mAxisGroup.append('text')
                    .attr('class', `class-label-${this.sampler.id}-0`);

                this.mAxisGroup.append('text')
                    .attr('class', `class-label-${this.sampler.id}-1`);

                this.mAxisGroup.append('line')
                    .attr('class', 'icon-separator')
                    .attr('stroke', '#000')
                    .attr('stroke-dasharray', '1');

                this.mAxisGroup.append('line')
                    .attr('class', 'label-separator')
                    .attr('stroke', '#000');

                this.legendGroup.append('line')
                    .attr('class', 'x-axis')
                    .attr('stroke', '#000')
                    .attr('x1', -config.axisOffset)
                    .attr('y1', this.height)
                    .attr('x2', this.width + config.axisOffset)
                    .attr('y2', this.height);


                this.legendGroup.append('g')
                    .attr('class', 'tooltip');

                this.cgroup = this.svg.append('g')
                    .attr('class', 'circles')
                    .attr('transform', 'translate(' + config.margin.left + ',' + config.margin.top + ')');

                if (this.sampler.samples.length >= 0) {
                    this.updateVisualisation();
                }
            },

            onUpdated(props, state) {
                this.updateVisualisation();
            },

            // custom methods

            // Draw axes. Adaptive to bin sizes
            drawAxis() {
                let blockspacing = config.radius * 2 * config.blocks.horizontal.spacingRatio;
                let dotspacing = config.radius * config.dotspacingRatio;
                let xoffset = config.radius;
                let spc = this.sampler.samplesPerClass().map(function(d) { return d.length; });
                let diameter = 2 * config.radius;
                let id = this.sampler.id;

                let binWidth;
                let boffset;

                // labels and separator
                this.labels = [];
                this.separator = undefined;

                for (let cidx in this.sampler.classes) {
                    cidx = parseInt(cidx);
                    let bidx = cidx;

                    let lBinColumns = this.sampler.maxSamples / (config.blocks.vertical.amount * config.blocks.vertical.size) / 2;

                    if (spc[0] > this.sampler.maxSamples / 2) {
                        lBinColumns = Math.ceil(spc[0] / (config.blocks.vertical.size * config.blocks.vertical.amount));
                    } else if (spc[1] > this.sampler.maxSamples / 2) {
                        let rBinColumns = Math.ceil(spc[1] / (config.blocks.vertical.size * config.blocks.vertical.amount));
                        lBinColumns = Math.max(
                            this.sampler.maxSamples / (config.blocks.vertical.size * config.blocks.vertical.amount) - rBinColumns,
                            Math.ceil(spc[0] / (config.blocks.vertical.size * config.blocks.vertical.amount))
                        );
                    }

                    binWidth = lBinColumns * (dotspacing + diameter);

                    boffset = xoffset + (diameter + dotspacing) + config.axisOffset + blockspacing - dotspacing / 2 + binWidth;  // x-offset per icon array

                    // Axis (bins) labels
                    let lines = Array();

                    lines.push(spc[cidx]);  // class size
                    lines.push(this.sampler.classes[bidx].name + ' ' + this.props.investmentOutput + ' Euro');

                    let y = this.height + config.axisOffset + config.labelOffset * 2;
                    let x = cidx === 0 ? boffset - 2.5 * blockspacing : boffset + blockspacing;

                    let label = this.mAxisGroup.selectAll(`.class-label-${id}-${cidx}`);

                    label
                        .attr('class', function() { return `class-label-${id}-${cidx}`; })
                        .style('font-size', '16px')
                        .style('text-anchor', function() { return cidx === 0 ? 'end' : 'start'; })
                        .attr('y', y);

                    let span = label.selectAll('tspan')
                        .data(lines);

                    span
                        .enter()
                        .append('tspan')
                        .merge(span)
                        .transition()
                        .duration(config.duration)
                        .attr('x', x)
                        .attr('class', 'class-size')
                        .attr('dy', function(d, i) { return i === 0 ? '0em' : '1.2em'; })
                        .text(function(d, i) { return d; });

                    this.labels.push(label);

                }
                // vertical lines
                let y2 = this.height + config.axisOffset;
                let y2long = this.height + config.axisOffset * 2;

                this.mAxisGroup.select('.icon-separator')
                    .transition()
                    .duration(config.duration)
                    .attr('opacity', '0.2')
                    .attr('x1', boffset - config.radius - dotspacing / 2)
                    .attr('y1', -config.axisOffset * 2)
                    .attr('x2', boffset - config.radius - dotspacing / 2)
                    .attr('y2', y2);

                this.mAxisGroup.select('.label-separator')
                    .transition()
                    .duration(config.duration)
                    .attr('x1', boffset - config.radius - dotspacing / 2)
                    .attr('y1', this.height)
                    .attr('x2', boffset - config.radius - dotspacing / 2)
                    .attr('y2', y2long);
            },

            // Called whenever 
            updateVisualisation() {

                let self = this;

                let radius = config.radius;
                let diameter = 2 * config.radius;
                let dotspacing = config.radius * config.dotspacingRatio;
                let nrblocks = {
                    h: config.blocks.horizontal.amount,
                    v: config.blocks.vertical.amount
                };
                let blocksize = {
                    h: config.blocks.horizontal.size,
                    v: config.blocks.vertical.size
                };
                let blockspacing = {
                    h: diameter * config.blocks.horizontal.spacingRatio,
                    v: diameter * config.blocks.vertical.spacingRatio
                };

                let xoffset = 3 * (dotspacing + diameter) - dotspacing;
                let yoffset = config.radius;

                let iaheight = this.height;
                let colours = this.props.classes.map(c => c.colour);

                let samplesPerClass = this.sampler.samplesPerClass();
                let samplesConcat = [];

                // initial left bin width. icons are drawn from the centre to the margins
                let lBinColumns = this.sampler.maxSamples / (nrblocks.v * blocksize.v) / 2;

                if (samplesPerClass[0].length > this.sampler.maxSamples / 2) {
                    lBinColumns = Math.ceil(samplesPerClass[0].length / (blocksize.v * nrblocks.v));
                } else if (samplesPerClass[1].length > this.sampler.maxSamples / 2) {
                    let rBinColumns = Math.ceil(samplesPerClass[1].length / (blocksize.v * nrblocks.v));
                    lBinColumns = Math.max(
                        this.sampler.maxSamples / (blocksize.v * nrblocks.v) - rBinColumns,
                        Math.ceil(samplesPerClass[0].length / (blocksize.v * nrblocks.v))
                    );
                }

                //>>> fill samples array
                for (let i = 0; i < lBinColumns * nrblocks.v * blocksize.v - samplesPerClass[0].length; ++i) {
                    samplesConcat.push(undefined);
                }

                // Keep the visual order of the samples. Invert for first class, as the corresponding icon array is flipped.
                for (let s = samplesPerClass[0].length - 1; s >= 0; --s) {
                    samplesConcat.push(samplesPerClass[0][s]);
                }
                for (let s = 0; s < samplesPerClass[1].length; ++s) {
                    samplesConcat.push(samplesPerClass[1][s]);
                }


                let sclength = samplesConcat.length;
                for (let i = 0; i < this.sampler.maxSamples - sclength; ++i) {  // fill up the array with either base class id or undefined (placeholder)
                    samplesConcat.push(undefined);
                }

                let ao = config.axisOffset;
                
                let circles = this.cgroup.selectAll('circle')
                    .data(samplesConcat);

                circles
                    .attr('cx', function(d, i) {  // y positioning with block grouping
                        // blockspacing dependent on number of columns of first bin
                        let bsp = i / (lBinColumns * nrblocks.v * blocksize.v) >= 1 ? blockspacing.h : 0;
                        return xoffset +
                            bsp +
                            (dotspacing + diameter) * Math.floor(i / (nrblocks.v * blocksize.v));
                    })
                    .transition()
                    .duration(config.duration)
                    .attr('r', function(d) {
                        if (d !== undefined && self.props.mouseOver && d.id === self.sampler.currentSample.id) {
                            return radius + dotspacing;
                        }
                        return radius;
                    });

                let circle = circles.enter()
                    .append('circle')
                    .attr('r', radius)
                    .merge(circles)                 // merge first!!!
                    .attr('cx', function(d, i) {  // y positioning with block grouping
                        // blockspacing dependent on number of columns of first bin
                        let bsp = i / (lBinColumns * nrblocks.v * blocksize.v) >= 1 ? blockspacing.h : 0;
                        return xoffset +
                            bsp +
                            (dotspacing + diameter) * Math.floor(i / (nrblocks.v * blocksize.v));
                    })
                    .attr('cy', function(d, i) {  // x positioning with block grouping
                        let fst = i / (lBinColumns * nrblocks.v * blocksize.v) < 1 ? true : false;
                        let cy;
                        if (fst) {  // invert y coordinates for first icon block
                            cy = -0.5 - ao - yoffset - (diameter + dotspacing) + (i % (nrblocks.v * blocksize.v)) * (diameter + dotspacing);
                        } else {
                            cy = iaheight - 2 * yoffset - (dotspacing + diameter) * (i % (nrblocks.v * blocksize.v));
                        }
                        return cy;
                    })
                    .attr('class', function(d) {
                        if (d === undefined) {
                            return 'placeholder';
                        }
                        return 'sample';
                    })
                    .attr('fill', function(d) {
                        let col = 'none';
                        if (d !== undefined) {
                            col = colours[d.cid];
                        }
                        return col;
                    })
                    .attr('stroke', function(d) {
                        let col = 'none';
                        return col;
                    });

                circle
                    .on('mouseover', function(e) {
                        this.setAttribute('r', radius + dotspacing);
                        self.props.onMouseOver(e, e.id);
                    })
                    .on('mouseout', function(e) {
                        this.setAttribute('r', radius);
                        self.props.onMouseOut(e, e.id);
                    });

                circles.exit().remove();
                this.drawAxis();

            }
        }
    </script>
</sampler-visual>
