import { customElement, bindable, inject } from 'aurelia-framework';
import { EventAggregator } from 'aurelia-event-aggregator';

import * as d3 from './d3custom';

import { VisualConfig } from './config_visual';
import { UpdateVisualisation } from './messages';


@customElement('sampler-visual')
@inject(VisualConfig, EventAggregator)
export class SamplerVisual {

    @bindable colours;
    @bindable lang;
    @bindable maxSamples;
    @bindable sampler;
    @bindable soffsets;
    @bindable sortMethodId;

    constructor(configuration, ea) {  // injected VisualConfig, EventAggregator

        this.ea = ea;
        this.svgWidth = 480;
        this.svgHeight = 340;
        this.axisWidth = 45;
        this.axisOffset = 10;
        this.tickLength = 10;
        this.labelOffset = 5;
        this.axisLabel = {de: 'Gewinne', en: 'Profits'};

        for (let key in configuration) {
            this[key] = configuration[key];
        }

        this.ea.subscribe(UpdateVisualisation, msg => {
            this.updateVisualisation();
        });
    }

    attached() {  // (aurelia life cycle function) set up d3 view when dom ready

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

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

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

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

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

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

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

    drawAxis() {
        let blockspacing = this.radius * 2 * this.blocks.horizontal.spacingRatio;
        let dotspacing = this.radius * this.dotspacingRatio;
        let xoffset = this.radius;

        let cgroup = this.lgroup.append('g')
            .attr('class', 'class-label');

        cgroup.append('text')
            .text(this.axisLabel[this.lang])
            .style('font-size', '12px')
            .style('font-weight', 'bold')
            .attr('x', xoffset + this.blocks.horizontal.size * (2 * this.radius + dotspacing) + blockspacing / 2 + blockspacing / 2)
            .attr('y', this.height + this.axisOffset + this.labelOffset);

        // horizontal line
        cgroup.append('line')
            .attr('stroke', '#000')
            .attr('x1', -this.axisOffset)
            .attr('y1', this.height)
            .attr('x2', this.width + this.axisOffset)
            .attr('y2', this.height);

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

            // all labels are offset by one block + half block spacing
            // for every following block the full block spacing is added
            let boffset = xoffset + (cidx + 1) * (this.blocks.horizontal.size * (2 * this.radius + dotspacing) + blockspacing / 2) + cidx * blockspacing / 2;

            let lines = this.sampler.classes[bidx].name[this.lang].split('\n');
            let y = this.height + this.axisOffset + this.labelOffset * 4;
            if (cidx === 0) {
                y = this.height + this.axisOffset + this.labelOffset;
            }
            let label = cgroup.append('text')
                .style('font-size', '12px')
                .style('font-weight', function() { return cidx === 0 ? 'bold' : 'normal'; })
                .attr('y', y);

            for (let lidx = 0; lidx < lines.length; ++lidx) {
                label.append('tspan')
                    .attr('x', boffset - (this.blocks.horizontal.size * (2 * this.radius + dotspacing) + blockspacing / 2))
                    .attr('dy', function() { return lidx > 0 ? '1.2em' : '0em'; })
                    .text(lines[lidx]);
            }

            if (cidx < this.sampler.classes.length - 1) {
                let y2 = this.height + this.axisOffset;
                let y2long = this.height + this.axisOffset * 4.5;
                if (cidx === 0) {
                    y2 = y2long;
                }

                cgroup.append('line')
                    .attr('stroke', '#000')
                    .attr('stroke-dasharray', '1')
                    .attr('opacity', '0.2')
                    .attr('x1', boffset - this.radius - dotspacing / 2)
                    .attr('y1', -this.axisOffset)
                    .attr('x2', boffset - this.radius - dotspacing / 2)
                    .attr('y2', y2long);

                cgroup.append('line')
                    .attr('stroke', '#000')
                    .attr('x1', boffset - this.radius - dotspacing / 2)
                    .attr('y1', this.height)
                    .attr('x2', boffset - this.radius - dotspacing / 2)
                    .attr('y2', y2);
            }
        }
    }

    updateVisualisation() {

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

        let iaheight = this.height;
        let colours = this.colours;

        // Invert every structure: samples, offsets, colours
        // FIXME: Is there still a reason to do this instead of inverting the order in the configuration?
        let spc = this.sampler.samplesPerClass();
        let samplesPerClass = Array(...spc);
        samplesPerClass.reverse();
        let synClassOffsets = Array(...this.soffsets);
        synClassOffsets.reverse();
        let samplesConcat = [];

        // for (let i = 0; i < samplesPerClass.length; ++i) {
        for (let i = samplesPerClass.length - 1; i >= 0; --i) {

            for (let j = 0; j < samplesPerClass[i].length; ++j) {  // fill array with all samples of current class
                samplesConcat.push(i);
            }

            for (let k = 0; k < synClassOffsets[i] * this.blocks[this.layout].amount * this.blocks.horizontal.size * this.blocks.vertical.size - samplesPerClass[i].length; ++k) {  // fill up block with complementary class
                samplesConcat.push(undefined);
            }
        }

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

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

        circles.enter()
            .append('circle')
            .attr('r', radius)
            .merge(circles)                 // merge first!!!
            .attr('cx', function(d, i) {  // y positioning with block grouping
                return xoffset +
                    (dotspacing + diameter) * Math.floor(i / (nrblocks.v * blocksize.v)) +
                    blockspacing.h * (Math.floor(i / blocksize.h / blocksize.v / nrblocks.h));
            })
            .attr('cy', function(d, i) {  // x positioning with block grouping
                return iaheight - 2 * yoffset - (dotspacing + diameter) * (i % (nrblocks.v * blocksize.v));
            })
            .attr('class', function(d) {
                if (d === undefined) {
                    return 'placeholder';
                }
                return 'sample';
            })
            .attr('fill', function(d) {
                let col = 'none';
                // let col = '#fff';
                if (d !== undefined) {
                    col = colours[colours.length - 1 - d];  // don't forget to invert the colour indices
                }
                return col;
            })
            .attr('stroke', function(d) {
                let col = 'none';
                return col;
            })
            .on('mouseover', function(e) {
                this.setAttribute('r', radius + dotspacing);
            })
            .on('mouseout', function(e) {
                this.setAttribute('r', radius);
            });


        circles.exit().remove();
    }
}
