import 'whatwg-fetch';

export class PDFExport {

    constructor(controller, config) {

        this.controller = controller;

        for (const key in config) {
            this[key] = config[key];
        }

        this.fontsloaded = 0;

        this.context_url = 'data/sampling-info.json';

        this.fontData = {
            'roman': { 'name': 'SourceSansPro Regular', 'url': 'fonts/SourceSansPro-Regular.otf', 'data': undefined },
            'bold': { 'name': 'SourceSansPro  Bold', 'url': 'fonts/SourceSansPro-Bold.otf', 'data': undefined },
            'light': { 'name': 'SourceSansPro Light', 'url': 'fonts/SourceSansPro-Light.otf', 'data': undefined }
        };

        this.lineSpacing = 1.5;
        this.baseFontSize = 10;

        this.axisWidth = 45;
        this.axisOffset = 10;
        this.tickLength = 10;
        this.labelOffset = 5;
        this.height = 180;
        this.width = 215;

        this.offset = {
            x: 4 * this.baseFontSize,
            y: 4 * this.baseFontSize
        };

        // *Magic Numbers* for conversion from svg web layout to pdf print
        this.magicSpacingRatio = 0.425;
        this.magicMagicRatio = 0.49;

        this.diameter = parseFloat(this.iconArray.radius) * 1.18;
        this.dotspacing = this.diameter * this.iconArray.dotspacingRatio * this.magicSpacingRatio;

        // Load context json and fonts, then draw pdf
        window.fetch(this.context_url)
            .then(response => response.json())
            .then(data => {
                this.context = data;

                for (let md in this.fontData) {
                    let cfd = this.fontData[md];

                    window.fetch(cfd.url)
                        .then(response => response.arrayBuffer())
                        .then(data => {
                            cfd.data = data;
                            this.fontsloaded += 1;
                            if (this.fontsloaded === Object.keys(this.fontData).length) {
                                this.drawPDF();
                            }
                        });
                }
            });
    }

    listUpdated(list) {
        this.drawPDF(list);
    }

    /**
     *   Creates an array of samples for each class
     *   @idx:      Index of Sampler
     *   @return:   Array of array of samples per class
     */
    samplesPerClass(idx) {
        let samplesIsolated = [];
        let samples = [];

        for (let s of this.samplers[idx].samples) {  // deep copy of samples
            samples.push(s);
        }

        for (let cls of this.classes) {  // create arrays per class
            let arr = samples.filter(function(c) {
                return c.cid === cls.id;
            }, cls);

            samplesIsolated.push(arr);
        }
        return samplesIsolated;
    }

    drawAxis(doc, x_offset, samplesPerClass) {
        let blockspacing = this.diameter * this.iconArray.blocks.horizontal.spacingRatio;
        // width of the left bin
        let binWidth = (Math.ceil(samplesPerClass[0].length / (this.iconArray.blocks.vertical.size * this.iconArray.blocks.vertical.amount)) * (this.diameter + this.dotspacing));

        let y_baseline = this.iconArray.y_offset + this.height + this.axisOffset - this.labelOffset / 2;

        doc.fillColor('black');

        for (let cidx in this.samplers[0].classes) {
            cidx = parseInt(cidx);
            let bidx = cidx;

            let lines = new Array();
            lines.push(samplesPerClass[cidx].length);
            lines.push(this.samplers[0].classes[bidx].name + ' ' + this.investment.output.value + ' Euro');
            let y = this.iconArray.y_offset + this.height + this.axisOffset;

            let x = x_offset + (cidx + 1) * binWidth + blockspacing / 2;
            let options = { width: binWidth };

            if (cidx === 0) {
                options.align = 'right';
            } else {
                options.align = 'left';
            }

            for (let lidx = 0; lidx < lines.length; ++lidx) {
                doc.text(
                    lines[lidx],
                    x - binWidth + cidx * this.labelOffset - (1 - cidx) * this.labelOffset,  // x position for labels:bin seperator x position +- labelOffset
                    y + lidx * this.labelOffset * 2.5,
                    options
                );
            }

            // vertical line
            if (cidx < this.samplers[0].classes.length - 1) {
                let y2 = y_baseline + this.axisOffset * 1.5;
                let y2long = y_baseline + this.axisOffset * 1.25;
                if (cidx === 0) {
                    y2 = y2long;
                }

                doc.moveTo(x, y_baseline - (this.iconArray.blocks.vertical.size * (this.diameter + this.dotspacing) * this.iconArray.blocks.vertical.amount + this.labelOffset))
                    .lineTo(x, y2long)
                    .dash(1, {space: 2})
                    .strokeColor('#aaa')
                    .stroke();

                doc.moveTo(x, y_baseline)
                    .lineTo(x, y2)
                    .dash(100)
                    .strokeColor('#444')
                    .stroke();
            }
        }
        // horizontal line
        doc.moveTo(x_offset - this.axisOffset, y_baseline)
            .lineTo(x_offset + this.width, y_baseline)
            .dash(500)
            .strokeColor('#444')
            .stroke();
    }

    createIconArray(doc) {

        // hand-adjusted positioning
        this.iconArray.x_offset = this.baseFontSize * 25;
        this.iconArray.y_offset = this.baseFontSize * 5;

        let blockspacing = {
            h: this.diameter * this.iconArray.blocks.horizontal.spacingRatio * 1.5,
            v: this.diameter * this.iconArray.blocks.vertical.spacingRatio * 1.5
        };

        let blocksize = {
            h: this.iconArray.blocks.horizontal.size,
            v: this.iconArray.blocks.vertical.size
        };

        let nrblocks = {
            h: this.iconArray.blocks.horizontal.amount,
            v: this.iconArray.blocks.vertical.amount
        };

        let iaxfirst = this.iconArray.size === 100 ? 50 : 0;

        // draw both icon arrays
        for (let j = 0; j < 2; ++j) {

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

            // width of left bin
            let binWidth = (Math.ceil(samplesPerClass[0].length / (blocksize.v * nrblocks.v)) * (this.diameter + this.dotspacing));

            for (let i = 0; i < samplesPerClass.length; ++i) {

                // fill array with all samples of current class
                for (let l = 0; l < samplesPerClass[i].length; ++l) {
                    samplesConcat.push(i);
                }
                // fill up column with complementary class
                for (let k = 0; k < nrblocks.v * blocksize.v - samplesPerClass[i].length % (nrblocks.v * blocksize.v); ++k) {
                    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 samples = samplesConcat;

            // x-offset per icon array. mighty magic
            let x_offset = j * this.iconArray.x_offset + iaxfirst + this.baseFontSize * 4 + this.diameter * this.magicMagicRatio + this.axisOffset;

            for (let i = 0; i < samples.length; ++i) {

                let cx = blockspacing.h / 2;

                // for left side: subtract actual position from [offset of icon array plus width of left class block minus spacing between icon array blocks]
                if (i < Math.ceil(samplesPerClass[0].length / (nrblocks.v * blocksize.v)) * nrblocks.v * blocksize.v) {  // first class of icon array
                    cx += x_offset +
                        binWidth -
                        blockspacing.h - this.dotspacing * 2 -  //
                        (this.dotspacing + this.diameter) * Math.floor(i / (nrblocks.v * blocksize.v));
                } else {  // second class of iconarray
                    cx += x_offset + blockspacing.h +
                        (this.dotspacing + this.diameter) * Math.floor(i / (nrblocks.v * blocksize.v));
                }

                let cy =
                    this.iconArray.y_offset +  // y-offset
                    this.height - (this.dotspacing + this.diameter) * (i % (nrblocks.v * blocksize.v));  // blockspacing y
                doc.ellipse(
                    cx,
                    cy,
                    this.diameter * this.magicMagicRatio  // radius !!??
                );

                let col = '';
                if (samples[i] === undefined) {
                    col = '#fff';
                } else {
                    col = this.colours[samples[i]];
                }
                doc.fill(col);
            }
            this.drawAxis(doc, x_offset, samplesPerClass);
        }
    }

    drawPDF() {

        let doc = new PDFDocument({  // eslint-disable-line
            size: 'A4',
            // margin: 0
            margins: {
                top: this.baseFontSize * this.fontRatios.huge,
                bottom: this.baseFontSize * this.fontRatios.huge,
                left: this.offset.x,
                right: this.offset.x
            }
        });

        for (let fd in this.fontData) {
            let cfd = this.fontData[fd];
            doc.registerFont(cfd.name, cfd.data);
        }

        this.stream = doc.pipe(blobStream());  // eslint-disable-line

        // meta data
        doc.info.Title = this.title;
        doc.info.Author = this.author;
        doc.info.Subject = this.description;

        // title
        doc.font(this.fontData.light.name)
            // .fontSize(this.baseFontSize * this.fontRatios.huge)
            .fontSize(this.baseFontSize * this.fontRatios.large)
            .text(
                this.title,
                // this.offset.x,
                // this.baseFontSize * this.fontRatios.huge,
                { width: 500 }
            );

        // description
        let desc = this.description[0] + ' ' +
            this.investment.input.value + ' ' +
            this.description[1] + ' ' +
            this.selectedPeriod.value + ' ' +
            this.labels.period + ' ' +
            this.description[2] + ' ' +
            this.investment.output.value + ' ' +
            this.description[3];

        doc.font(this.fontData.bold.name)
            .fontSize(this.baseFontSize * this.fontRatios.medium);

        doc.text(desc);

        doc.fontSize(this.baseFontSize * this.fontRatios.small)
            .fillColor('#333333')
            .font(this.fontData.roman.name);

        this.offset.table = 7;

        doc.moveTo(this.offset.x, this.offset.y);

        // reference classes
        for (let i = 0; i < 2; ++i) {

            // let so1 = this.investment.options[this.leftOptions[i].id].desc;
            // let so2 = this.investment.options[this.rightOptions[i].id].desc;
            let so1 = this.investment.options[this.samplers[i].investment.options[0].id].desc;
            let so2 = this.investment.options[this.samplers[i].investment.options[1].id].desc;
            let rc = this.referenceClasses.prefix + ' ' + this.samplers[i].samples.length + ' ' +
                this.referenceClasses.infix + ' ' + this.investment.input.value + ' ' +
                this.referenceClasses.suffix + '\n' +
                this.samplers[i].getLeftOptionRatioRepr() + ' ' + so1 + ' – ' +
                this.samplers[i].getRightOptionRatioRepr() + ' ' + so2;

            rc = rc.replace(/\u2009/g, ' ');  // quick hack: Font does not support thin space character!?

            doc.text(
                rc,
                this.offset.x + i * this.baseFontSize * 25,
                this.offset.table * this.baseFontSize,
                { width: this.baseFontSize * 25, align: 'left' }
            );
        }

        if (this.iconArray.size === 1000) {
            this.iconArray.y_offset = this.baseFontSize * 24;
        } else {
            this.iconArray.y_offset = this.baseFontSize * 24;
        }

        // draw icon arrays and 'axes'
        this.createIconArray(doc);

        // draw context
        doc.font(this.fontData.roman.name)
            .fontSize(this.baseFontSize * this.fontRatios.small);

        // reset the text cursor??! for continuous text positioning. !@&^%
        doc.text(
            '',
            this.offset.x,
            this.height + this.iconArray.y_offset * 2.5
        );

        function parseText(text) {
            let regexp = /^(.*)<a href="(.*?)".*>(.*)<\/a>(.*)$/;
            let match = regexp.exec(text);

            if (match !== null) {
                return match[1] + ' ' + match[3] + ' ' + match[2] + match[4];  // pdfkit only allows for pragmatic lead desert
            }
            return text;
        }

        // Context: print structured text from sampling-info.json
        for (let i = 0; i < this.context.length; ++i) {  // classes array
            doc.font(this.fontData.roman.name);
            doc.fontSize(this.baseFontSize * this.fontRatios.small);

            let obj = this.context[i];

            if (obj.type === 'list') {

                let lst = obj.items.map(function(e) {
                    return parseText(e);
                });

                doc.list(
                    lst,
                    { width: this.baseFontSize * 45, align: 'left' }  // in contrast to documentation no list style option seems to have any effect
                );
            } else {
                if (obj.type === 'pageBreak' && i != this.context.length - 1) {  // don't add another page break at the end of the PDF file
                    doc.addPage();
                } else if (obj.type === 'heading') {
                    if (obj.depth === 3) {
                        doc.fontSize(this.baseFontSize * this.fontRatios.medium);
                    }
                    doc.font(this.fontData.bold.name);
                }

                doc.text(
                    obj.text,
                    { width: this.baseFontSize * 45, align: 'left' }
                );
            }
            doc.moveDown();
        }

        doc.end();

        this.stream.on('finish', () => {
            let url = this.stream.toBlobURL('application/pdf');
            this.controller.updatePDFLink(url, this.clickEvent);
        });
    }
}
