<sampling-app>
    <header app-title="{ props.header.title }"
            description="{ props.header.description }"
            investment="{ props.investment }"
            periods="{ props.periods }"
            period="{ state.sPeriod }"
            on-period-change="{ onPeriodChange }"
            on-investment-input-change="{ onInvestmentInputChange }"
            on-investment-output-change="{ onInvestmentOutputChange }"
            labels="{ props.labels }"
            is="sampling-header">
    </header>
    <section class="samplers">
        <div each="{ sampler in this.samplers }"
             class="sampler-wrapper sampler-wrapper-{ sampler.id }">
            <div is="sampler-controls"
                 class="reference-group"
                 investment="{ props.investment }"
                 referenceClasses="{ props.referenceClasses }"
                 on-option-change="{ onOptionChange }"
                 on-ratio-change="{ onRatioChange }"
                 sampler="{ sampler }">
            </div>
            <sampler-visual class="sampler-visual"
                            on-mouse-over="{ onMouseOver }"
                            on-mouse-out="{ onMouseOut }"
                            mouse-over="{ state.mouseOver }"
                            classes="{ props.classes }"
                            colours="{ state.colours }"
                            investment-output="{ state.investment.output.value }"
                            sampler="{ sampler }">
            </sampler-visual>
        </div>
    </section>
    <section class="textual controls">
        <div class="buttons size-selectors">
            <div each="{ gsize in props.groupSizes }"
                 class="size-input">
                <input id="size-{ gsize.id }"
                       type="radio"
                       class="input-radio"
                       value="{ gsize.id }"
                       defaultChecked="{ gsize.id === state.sGroupSize.id }"
                       name="groupSize"
                       onchange="{ groupSizeChanged }" />
                <label for="size-{ gsize.id }" class="form-item">{ gsize.name }</label>
            </div>
        </div>
        <sampler-textual each="{ sampler in this.samplers }"
                         id="sampler-textual-{ sampler.id }"
                         class="sampler-textual sampler-textual-{ sampler.id }"
                         sampler="{ sampler }"
                         group-size-id="{ state.sGroupSize.id }"
                         textual="{ props.textual }"
                         colours="{ state.colours }"
                         investment-output="{ state.investment.output.value }"
                         mouse-over="{ state.mouseOver }">
        </sampler-textual>

        <div class="sample-button" onclick="{ sampleButtonClicked }">
            <button class="sample"
                    disabled="{ maximumSamplesDrawn }">
                <a href="#">{ props.labels.sample } { state.investment.output.value } { props.labels.currency }?</a>
            </button>
        </div>

        <div class="buttons export-reset">
            <button id="export"
                    class=" export form-item"
                    disabled="{ state.isLoading }"
                    onclick="{ generatePDF }">
                <a href="{ state.pdfDownloadURL }" id="exportPDF" class="export" download="{ state.pdfFilename }">
                    <i class="demo-icon icon-download">&#xe801;</i>Faktenbox
                </a>
            </button>
            <button class="reset form-item"
                    onclick="{ onReset }"
                    disabled="{ resetEnabled }">
                <a href="#"><i class="demo-icon icon-ccw">&#xe802;</i>{ props.labels.reset }</a>
            </button>
        </div>

    </section>

    <script charset="utf-8">

        import '@babel/polyfill';
        import * as riot from 'riot';
        import * as d3 from '../d3custom';

        import { Sampler } from '../sampler';
        import { PDFExport } from '../pdfExport';
        import visual_config from './config_visual.json';

        import info from './sampling-info.riot';  // built from /data/sampling-info.md by bin/preprocess.js
        import header from './sampling-header.riot';
        import textual from './sampler-textual.riot';
        import controls from './sampler-controls.riot';
        import visual from './sampler-visual.riot';
        riot.register('sampling-header', header);
        riot.register('sampling-info', info);
        riot.register('sampler-controls', controls);
        riot.register('sampler-textual', textual);
        riot.register('sampler-visual', visual);

        export default {

             // life cycle methods
            onBeforeMount(props, state) {

                state.investment = props.investment;
                state.period = props.period;
                this.state.isLoading = false;

                // load defaults: selected values / elements
                state.mouseOver = false;
                state.sPeriod = props.periods[props.defaults.periodId];
                state.sGroupSize = props.groupSizes[props.defaults.groupSizeId];

                state.colours = props.classes.map(c => c.colour);

                this.fetchData(props.dataFilePath);
                document.title = props.header.title;
             },

             // fetch all data once
             fetchData(filepath) {  //-> initialise, create samplers, samplers call controller.filterData(s.o1, s.o2)
                 d3.tsv(filepath).then(this.initialise);
             },

             // filter data for sampler with primaryKeys, make sure there is also data for opposing sampler (secondaryKeys)
             filterData(primaryKeys, secondaryKeys) {

                function parseVal(val) {
                    return parseFloat(val.replace(',', '.'));
                }

                let filteredData = this.data
                    .filter(
                        d => d[primaryKeys[0]] && d[primaryKeys[1]] &&
                            d[secondaryKeys[0]] && d[secondaryKeys[1]])  // only return data if row contains values for both options for both samplers
                    .map(d => {
                        return {
                            year: d.date_of_investment,
                            values: [parseVal(d[primaryKeys[0]]), parseVal(d[primaryKeys[1]])]
                        }
                    });

                return filteredData;
             },

            // Create new data set for sampler identified by samplerId
            queryData(samplerId, leftOptions, rightOptions) {
                return this.filterData(
                    [
                        this.createDataKey(leftOptions[samplerId].name),
                        this.createDataKey(rightOptions[samplerId].name)
                    ], [
                        this.createDataKey(leftOptions[(samplerId + 1) % 2].name),
                        this.createDataKey(rightOptions[(samplerId + 1) % 2].name)
                    ]
                );
            },

            createDataKey(name) {
                return this.state.sPeriod.name + '_' + name;
            },

            getLeftOptions() {
                if (this.samplers === undefined) {
                    return [
                        this.props.investment.options[this.props.defaults.selectedOptionsIds[0][0]],
                        this.props.investment.options[this.props.defaults.selectedOptionsIds[1][0]]
                    ];
                }
                return [
                    this.samplers[0].investment.options[0],
                    this.samplers[1].investment.options[0]
                ];
            },

            getRightOptions() {
                if (this.samplers === undefined) {
                    return [
                        this.props.investment.options[this.props.defaults.selectedOptionsIds[0][1]],
                        this.props.investment.options[this.props.defaults.selectedOptionsIds[1][1]]
                    ];
                }

                return [
                    this.samplers[0].investment.options[1],
                    this.samplers[1].investment.options[1]
                ];
            },

            getRatio(i) {
                if(this.samplers === undefined) {
                    return this.props.defaults.ratios[i];
                }
                return this.samplers[i].investment.ratio;
            },

            createSamplers() {
                let samplers = [];

                let leftOptions = this.getLeftOptions();
                let rightOptions = this.getRightOptions();

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

                    samplers.push(new Sampler(
                        i,
                        this.queryData(i, leftOptions, rightOptions),
                        this.props.classes,
                        {
                            ratio: this.getRatio(i),
                            inverseRatio: 1 - this.getRatio(i),  // inverted <input type="range"> bar!!
                            input: this.props.investment.input,
                            output: this.props.investment.output,
                            options: [leftOptions[i], rightOptions[i]]
                        },
                        this.props.maxSamples
                    ));
                }
                return samplers;
            },

            initialise(data) {
                this.data = data;
                this.samplers = this.createSamplers();

                // define getters for template updating only after 'this.props' is defined
                Object.defineProperty(this, 'maximumSamplesDrawn', { get: this.maximumSamplesDrawn });
                Object.defineProperty(this, 'resetEnabled', { get: this.resetEnabled });
                this.resetSamplers();
            },

            drawSamples(amount, samplers) {

                if (samplers[0].data.length !== samplers[1].data.length) {
                    throw new Error('Data arrays of samplers are not of equal size: ' + samplers[0].data.length + ', ' + samplers[1].data.length);
                }
                for (let i = 0; i < amount; ++i) {

                    // Importance sampling implementation
                    let cumulatedProb = 0.0;
                    let random = Math.random();
                    let prob = 1.0 / samplers[0].data.length;

                    let index = 0;
                    for (; index < samplers[0].data.length; ++index) {
                        cumulatedProb += prob;

                        if (random < cumulatedProb) {
                            break;
                        }
                    }

                    for (let sampler of samplers) {
                        sampler.drawSample(index);
                    }
                }
            },

            clearSamples() {
                for (let sampler of this.samplers) {
                    sampler.reset();
                }
            },

            // Generates pdf on button click
            generatePDF(e) {

                if (e.hasBeenProcessed === true) {  // Discard event and do nothing if the event already has been processed
                    e.stopImmediatePropagation();
                    this.$('#exportPDF').classList.remove('loading');
                    this.update({
                        isLoading: false
                    });
                    return;
                }

                this.$('#exportPDF').classList.add('loading');
                this.update({
                    isLoading: true
                });
                e.stopPropagation();  // Stop event propagation

                let pdfSamplers = this.createSamplers();

                this.drawSamples(this.props.maxSamples, pdfSamplers);

                // Create new PDFExport and pass all necessary data and the click event
                this.pdfx = new PDFExport(
                    this,
                    {
                        title: this.props.header.title,
                        clickEvent: e,
                        description: this.props.header.description,
                        referenceClasses: this.props.referenceClasses,
                        author: this.props.author,
                        subject: this.props.subject,
                        classes: this.props.classes,
                        colours: this.state.colours,
                        context: this.props.context,
                        fontRatios: this.props.fontRatios,
                        iconArray: {
                            blocks: visual_config.blocks,
                            dotspacingRatio: visual_config.dotspacingRatio,
                            radius: visual_config.radius
                        },
                        investment: this.props.investment,
                        lang: this.lang,
                        labels: this.props.labels,
                        maxSamples: this.props.maxSamples,
                        samplers: pdfSamplers,
                        selectedPeriod: this.state.sPeriod
                    }
                );
            },

            /**
             * Called from pdf generation code
             * The event that has been triggered to start the pdf generation process
             * is dispatched so that the pdf download happens after the variables have been assigned
             *
             * @param url:  Blob url
             * @param e:    Event object
             */
            updatePDFLink(url, e) {
                this.update({
                    pdfDownloadURL: url,
                    pdfFilename: `${this.props.header.title}.pdf`
                });
                let anchor = this.$('#exportPDF');
                anchor.classList.remove('loading');
                e.hasBeenProcessed = true;  // indicate that the event has been processed and should be discarded (otherwise the pdf would be downloaded indefinitely)
                anchor.dispatchEvent(e);
                this.update({
                    isLoading: false
                });
            },

            resetSamplers() {
                this.clearSamples();
                this.update();
            },

            maximumSamplesDrawn() {
                let sampler = this.samplers[0];  // Both samplers always have the same amount of samples and maxSamples
                return sampler.samples.length === sampler.maxSamples;
            },

            resetEnabled() {
                let result = false;
                if (this.samplers !== undefined) {
                    result = this.samplers[0].samples.length === 0;
                }
                return result;
            },

            sampleButtonClicked(e) {
                e.preventDefault();

                let groupSize = this.state.sGroupSize.value;
                let remaining = this.samplers[0].maxSamples - this.samplers[0].samples.length;
                let amount = Math.min(remaining, groupSize);
 
                this.drawSamples(amount, this.samplers);
                this.update();
            },
 
            groupSizeChanged(e) {
                this.state.sGroupSize = this.props.groupSizes[parseInt(e.target.value)];
                this.update();
            },

            // events
            onPeriodChange (e) {
                this.state.sPeriod = this.props.periods[e.target.selectedIndex];
                for (let sampler of this.samplers) {
                    sampler.data = this.queryData(sampler.id, this.getLeftOptions(), this.getRightOptions());
                }
                this.resetSamplers();
            },

            onMouseOver (e, idx) {
                this.state.mouseOver = true;
                for (let sampler of this.samplers) {
                    sampler.setCurrentSample(idx);
                }
                this.update();
            },

            onMouseOut (e, idx) {
                this.state.mouseOver = false;
                for (let sampler of this.samplers) {
                    sampler.setCurrentSample(sampler.samples.length - 1);
                }
                this.update();
            },

            onInvestmentInputChange (e) {
                let newVal = Math.max(Math.min(e.target.valueAsNumber, this.state.investment.input.max), this.state.investment.input.min);
                this.state.investment.input.value = newVal;
                this.resetSamplers();
            },

            onInvestmentOutputChange (e) {
                let newVal = Math.max(Math.min(e.target.valueAsNumber, this.state.investment.output.max), this.state.investment.output.min);
                this.state.investment.output.value = newVal
                this.resetSamplers();
            },

            onOptionChange(e, sampler) {
                let leftOptions = this.getLeftOptions();
                let rightOptions = this.getRightOptions();

                // Set new data for both samplers, as data arrays must be of equal lengths
                for (let s of this.samplers) {
                    s.data = this.queryData(s.id, leftOptions, rightOptions);
                }
                this.resetSamplers();
            },

            onRatioChange(e) {
                this.resetSamplers();
            },

            onReset(e) {
                e.preventDefault();
                this.resetSamplers();
            }
        }
    </script>
</sampling-app>
