

jsPsych.plugins["bag-of-words"] = (function() {

    const info = {
        name: "bag-of-words",
        parameters: {
            /** The image to be displayed */
            stimulus: {
                type: jsPsych.ParameterType.IMAGE,
                pretty_name: "Stimulus",
                default: undefined,
            },
            /** Set the image height in pixels */
            stimulus_height: {
                type: jsPsych.ParameterType.INT,
                pretty_name: "Image height",
                default: null,
            },
            /** Set the image width in pixels */
            stimulus_width: {
                type: jsPsych.ParameterType.INT,
                pretty_name: "Image width",
                default: null,
            },
            /** Maintain the aspect ratio after setting width or height */
            maintain_aspect_ratio: {
                type: jsPsych.ParameterType.BOOL,
                pretty_name: "Maintain aspect ratio",
                default: true,
            },
            /** Array containing the label(s) for the button(s). Need to follow order defined in word_order! */
            choices: {
                type: jsPsych.ParameterType.STRING,
                pretty_name: "Choices",
                default: undefined,
                array: true,
            },
            /** The correct response */
            correct: {
                type: jsPsych.ParameterType.STRING,
                pretty_name: 'Correct string',
                default: null,
            },
            /** Any content here will be displayed over the button(s). */
            prompt: {
                type: jsPsych.ParameterType.HTML_STRING,
                pretty_name: "Prompt",
                default: null,
            },
            /** If true, then feedback will be provided */
            feedback: {
                type: jsPsych.ParameterType.BOOL,
                pretty_name: 'Feedback',
                default: false,
            },
            /** How long to show feedback */
            feedback_duration: {
                type: jsPsych.ParameterType.INT,
                pretty_name: 'Feedback duration',
                default: 750,
            },
            /** If an initial word is already provided */
            given_word: {
                type: jsPsych.ParameterType.INT,
                pretty_name: 'Position of given word',
                default: null,
            },
            /** The correct order of the words/morphemes*/
            word_order: {
                type: jsPsych.ParameterType.STRING,
                pretty_name: 'Correct word order',
                default: null,
            },
            /** The number of required buttons to press*/
            num_req_buttons: {
                type: jsPsych.ParameterType.INT,
                pretty_name: 'Number of buttons required',
                default: 1,
            },
            /** How long to show the stimulus. */
            stimulus_duration: {
                type: jsPsych.ParameterType.INT,
                pretty_name: "Stimulus duration",
                default: null,
            },
            /** How long to show the trial. */
            trial_duration: {
                type: jsPsych.ParameterType.INT,
                pretty_name: "Trial duration",
                default: null,
            },
            /** The vertical margin of the button. */
            margin_vertical: {
                type: jsPsych.ParameterType.STRING,
                pretty_name: "Margin vertical",
                default: "0px",
            },
            /** The horizontal margin of the button. */
            margin_horizontal: {
                type: jsPsych.ParameterType.STRING,
                pretty_name: "Margin horizontal",
                default: "8px",
            },
            /** If true, then trial will end when user responds. */
            response_ends_trial: {
                type: jsPsych.ParameterType.BOOL,
                pretty_name: "Response ends trial",
                default: true,
            },
        },
    };
    /**
     * **bag-of-words**
     *
     * jsPsych plugin for displaying an image stimulus and getting a button response
     *
     * @author Josh de Leeuw, Marc Meisezahl
     */
    class BagOfWordsPlugin {
        constructor(jsPsych) {
            this.jsPsych = jsPsych;
        }
        trial(display_element, trial) {
            var height, width;
            var html;
            let response = Object();
            // remove dots & line breaks, convert to lowercase letters
            let choices_flat = trial.choices.flat()
            for (let i = 0; i < choices_flat.length; i++) {
                choices_flat[i] = choices_flat[i].replace(/\./g, '');
                choices_flat[i] = choices_flat[i].replace(/(\r\n|\n|\r)/gm, "");
                choices_flat[i] = choices_flat[i].toLowerCase();
            };
            // assign grammatical functions to constituents
            const sentence = {
                constituents: [...trial.choices],
                order: trial.word_order.split('-'), // S-V-... format expected (JC: or 'N-Affix', doesn't matter the exact letters used)
            };
            for (let i = 0; i < trial.choices.length; i++) {
                for (let j = 0; j < trial.choices[0].length; j++) {
                    sentence[sentence.constituents[i][j]] = sentence.order[i];
                }
            };
            //console.log(sentence)
            // create DOM structure
            html = '<div class="centered">';
            // display stimulus as an image element
            html +=
                '<img src="' +
                trial.stimulus +
                '" id="jspysch-bag-of-word-stimulus">';
            // add prompt
            if (trial.prompt !== null) {
                html += '<p style="font-size: 15px">' + trial.prompt + '</p>';
            };
            // display sentence field
            html += '<div class="construction-field">';
            let placeholder = 0;
            for (let i = 0; i < trial.choices.length; i++) {
                if (trial.given_word != null && i == (trial.given_word - 1)) {
                    let givenWord = trial.choices[trial.given_word - 1];
                    // capitalise first letter if sentence-initial
                    // if (trial.given_word == 1) {
                    //     givenWord = givenWord.charAt(0).toUpperCase() + givenWord.slice(1);
                    // };
                    html += '<span class="words" id="given">' + givenWord + '</span>';
                } else {
                    //html += '<span class="words input-word" id="input-' + placeholder + '">\u00A0\u00A0\u00A0</span>'
                    html += '<span class="words input-word" id="input-' + placeholder + '"></span>'
                    placeholder++;
                };
            };
            html += '</div>';
            // remove given word from set of constituents
            if (trial.given_word != null) {
                trial.choices = trial.choices.filter(word => word != trial.choices[trial.given_word - 1]);
            };
            // shuffle words
            choices_flat = this.jsPsych.randomization.shuffle(choices_flat);
            // add feedback holder
            html += '<p id="feedback-holder"></p>';
            // bag of words
            html += '<div class="bag-of-words">';
            for (let i = 0; i < choices_flat.length; i++) {
                let str = choices_flat[i];
                html +=
                    '<div class="jspsych-bag-of-words-word-button" style="display: inline-block; margin:' +
                    trial.margin_vertical +
                    ' ' +
                    trial.margin_horizontal +
                    '">' +
                    '<button class="jspsych-btn button-option" id="bag-' +
                    i +
                    '" data-choice="' +
                    str +
                    '">' +
                    str +
                    '</button></div>';
            }
            html += '</div>';
            // response buttons
            html += '<div id="jspsych-bag-of-words-response-btngroup">';
            html += '<div style="display: inline-block; margin:5px 5px">' + '<button class="jspsych-btn" id="reset"> Reset </button> <br> <span style="font-size: 0.65em; visibility:hidden"> xxx </span></div>';
            html += '<div style="display: inline-block; margin:5px 5px">' + '<button class="jspsych-btn" id="submit"> Submit </button> <br> <span style="font-size: 0.65em"><i>(or press enter)</i></span> </div>';
            html += '</div>';
            // update the page content
            html += '</div>'
            display_element.innerHTML = html;

            // set image dimensions after image has loaded (so that we have access to naturalHeight/naturalWidth)
            var img = display_element.querySelector("#jspysch-bag-of-word-stimulus");
            if (trial.stimulus_height !== null) {
                height = trial.stimulus_height;
                if (trial.stimulus_width == null && trial.maintain_aspect_ratio) {
                    width = img.naturalWidth * (trial.stimulus_height / img.naturalHeight);
                }
            } else {
                height = img.naturalHeight;
            }
            if (trial.stimulus_width !== null) {
                width = trial.stimulus_width;
                if (trial.stimulus_height == null && trial.maintain_aspect_ratio) {
                    height = img.naturalHeight * (trial.stimulus_width / img.naturalWidth);
                }
            } else if (!(trial.stimulus_height !== null && trial.maintain_aspect_ratio)) {
                // if stimulus width is null, only use the image's natural width if the width value wasn't set
                // in the if statement above, based on a specified height and maintain_aspect_ratio = true
                width = img.naturalWidth;
            }
            img.style.height = height.toString() + "px";
            img.style.width = width.toString() + "px";

            // start timing
            let startTime = performance.now();
            // click counter
            const clicks = {
                cnt: 0,
                inc: function() { this.cnt++ },
                reset: function() { this.cnt = 0 },
            };
            // word button
            for (let i = 0; i < choices_flat.length; i++) {
                display_element.querySelector('#bag-' + i).addEventListener('click', (e) => {
                    let choice = e.currentTarget.getAttribute('data-choice');
                    // add choice to holder
                    display_element.querySelector('#input-' + clicks.cnt).textContent = choice + '';
                    clicks.inc();
                    // disable button
                    e.currentTarget.setAttribute('disabled', 'disabled');
                });
            };
            // reset button
            display_element.querySelector('#reset').addEventListener('click', (e) => {
                // remove content from sentence field
                display_element.querySelectorAll('.input-word').forEach((field) => {
                    field.textContent = '';
                });
                // enable buttons
                display_element.querySelectorAll('.button-option').forEach((btn) => {
                    btn.disabled = false;
                });
                // hide feedback
                display_element.querySelector('#feedback-holder').style.visibility = 'hidden';
                // reset click counter
                clicks.reset()
            });

            // submit button
            display_element.querySelector('#submit').addEventListener('click', (e) => {
                // retrieve text from sentence field
                let answer = Array()
                let txtAll = display_element.querySelectorAll('.words');
                txtAll.forEach((txt) => {
                    if (txt.textContent != '') {
                        answer.push(txt.textContent.replace(/\s/g, ''));
                    };
                });
                //console.log("answer: "+answer)
                // verify if all words have been used
                let feedbackHolder = display_element.querySelector('#feedback-holder');
                // check that the required number of buttons have been used...
                if (answer.length < trial.num_req_buttons) {
                    feedbackHolder.innerHTML = 'You need to use more buttons!';
                    feedbackHolder.style.visibility = 'visible';
                    feedbackHolder.style.color = '#b80F0A';
                } 
                else {
                    // disable all buttons
                    e.preventDefault();
                    display_element.querySelectorAll('button').forEach((btn) => { btn.setAttribute('disabled', 'disabled') });
                    // measure rt
                    let endTime = performance.now();
                    let rt = Math.round(endTime - startTime);
                    // determine order
                    let input = 0;
                    let incorrect = Array();
                    let order = Array();
                    let type;
                    for (let i = 0; i < answer.length; i++) {
                        // determine constituent type
                        type = sentence[answer[i]];
                        //console.log(sentence[answer[i]])
                        order.push(type);
                        // save id if deviating from correct constituent
                        if (answer[i] !== sentence.constituents[i]) {
                            incorrect.push(input);
                        };
                        // increase counter of input span
                        if (i != (trial.given_word - 1)) {
                            input++;
                        };
                    };
                    // save data
                    response = {
                        rt: rt,
                        production: answer.join(''),
                        order: order.join('-'),
                    };
                    //console.log(response)
                    // feedback
                    let timeout = 0;
                    if (trial.feedback) {
                        // duration of feedback on screen
                        timeout += trial.feedback_duration;
                        if (answer.join('') == trial.correct) {
                            // feedback message
                            feedbackHolder.innerHTML = 'Correct!';
                            feedbackHolder.style.visibility = 'visible';
                            feedbackHolder.style.color = '#2E8B57';
                        } else {
                            // feedback message using the correct label
                            let correctSentence = trial.correct;
                            // correctSentence = correctSentence.charAt(0).toUpperCase() + correctSentence.slice(1);
                            feedbackHolder.innerHTML = 'Incorrect. The correct answer was: <span style="font-weight: bold; color: #2E8B57">' + correctSentence + '</span>';
                            feedbackHolder.style.visibility = 'visible';
                            feedbackHolder.style.color = '#b80F0A';
                            // highlight incorrect words
                            for (const pos of incorrect) {
                                document.querySelector('#input-' + pos).style.color = '#b80F0A';
                            };
                            // add penalty time
                            timeout += 1500;
                        };
                    } else {
                        timeout += 100;
                    };
                    // finish trial
                    this.jsPsych.pluginAPI.setTimeout(() => {
                        end_trial();
                    }, timeout);
                };
            })

            // submit with enter and reset with backspace
            document.addEventListener('keydown', (e) => {
                if (e.key === 'Enter') {
                    document.querySelector('#submit').click();
                } else if (e.key === 'Backspace') {
                    document.querySelector('#reset').click()
                };
            });

            // function to end trial when it is time
            const end_trial = () => {
                // unsubscribe all handlers
                document.removeEventListener('keydown', (e) => {
                    if (e.key === 'Enter') {
                        document.querySelector('#submit').click();
                    } else if (e.key === 'Backspace') {
                        document.querySelector('#reset').click()
                    };
                });
                // kill any remaining setTimeout handlers
                this.jsPsych.pluginAPI.clearAllTimeouts();
                // gather the data to store for the trial
                var trial_data = {
                    response: response.production,
                    response_order: response.order,
                    rt: response.rt,
                    stimulus: trial.stimulus,
                    constituents: sentence.constituents.join(';'),
                    target_order: trial.word_order,
                };
                //console.log(response.order)
                // clear the display
                display_element.innerHTML = "";
                // move on to the next trial
                this.jsPsych.finishTrial(trial_data);
            };
            // hide image if timing is set
            if (trial.stimulus_duration !== null) {
                this.jsPsych.pluginAPI.setTimeout(() => {
                    display_element.querySelector("#jspysch-bag-of-word-stimulus").style.visibility = "hidden";
                }, trial.stimulus_duration);
            }
            // end trial if time limit is set
            if (trial.trial_duration !== null) {
                this.jsPsych.pluginAPI.setTimeout(() => {
                    end_trial();
                }, trial.trial_duration);
            } else if (trial.response_ends_trial === false) {
                console.warn("The experiment may be deadlocked. Try setting a trial duration or set response_ends_trial to true.");
            }
        }
        simulate(trial, simulation_mode, simulation_options, load_callback) {
            if (simulation_mode == "data-only") {
                load_callback();
                this.simulate_data_only(trial, simulation_options);
            }
            if (simulation_mode == "visual") {
                this.simulate_visual(trial, simulation_options, load_callback);
            }
        }
        create_simulation_data(trial, simulation_options) {
            const default_data = {
                stimulus: trial.stimulus,
                rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
                response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),
            };
            const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);
            this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);
            return data;
        }
        simulate_data_only(trial, simulation_options) {
            const data = this.create_simulation_data(trial, simulation_options);
            this.jsPsych.finishTrial(data);
        }
        simulate_visual(trial, simulation_options, load_callback) {
            const data = this.create_simulation_data(trial, simulation_options);
            const display_element = this.jsPsych.getDisplayElement();
            this.trial(display_element, trial);
            load_callback();
            if (data.rt !== null) {
                this.jsPsych.pluginAPI.clickTarget(display_element.querySelector(`div[data-choice="${data.response}"] button`), data.rt);
            }
        }
    }
    BagOfWordsPlugin.info = info;

    console.log(BagOfWordsPlugin)

    return BagOfWordsPlugin;

})();