import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from "jspsych";

import { version } from "../package.json";

const info = <const>{
  name: "same-different-html",
  version: version,
  parameters: {
    /** A pair of stimuli, represented as an array with two entries, one for
     * each stimulus. A stimulus is a string containing valid HTML markup.
     * Stimuli will be shown in the order that they are defined in the array. */
    stimuli: {
      type: ParameterType.HTML_STRING,
      default: undefined,
      array: true,
    },
    /** Correct answer: either "same" or "different". */
    answer: {
      type: ParameterType.SELECT,
      options: ["same", "different"],
      default: undefined,
    },
    /** The key that subjects should press to indicate that the two stimuli are the same. */
    same_key: {
      type: ParameterType.KEY,
      default: "q",
    },
    /** The key that subjects should press to indicate that the two stimuli are different. */
    different_key: {
      type: ParameterType.KEY,
      default: "p",
    },
    /** How long to show the first stimulus for in milliseconds. If the value of this parameter is null then the stimulus will be shown until the participant presses any key. */
    first_stim_duration: {
      type: ParameterType.INT,
      default: 1000,
    },
    /** How long to show a blank screen in between the two stimuli. */
    gap_duration: {
      type: ParameterType.INT,
      default: 500,
    },
    /** How long to show the second stimulus for in milliseconds. If null, then the stimulus will remain on the screen until a valid response is made. */
    second_stim_duration: {
      type: ParameterType.INT,
      pretty_name: "Second stimulus duration",
      default: 1000,
    },
    /** This string can contain HTML markup. Any content here will be displayed below the stimulus. The intention is that it can be used to provide a reminder about the action the participant is supposed to take (e.g., which key to press). */
    prompt: {
      type: ParameterType.HTML_STRING,
      default: null,
    },
  },
  data: {
    /**  An array of length 2 containing the HTML-formatted content that the participant saw for each trial. This will be encoded as a JSON string
     * when data is saved using the `.json()` or `.csv()` functions. */
    stimulus: {
      type: ParameterType.HTML_STRING,
      array: true,
    },
    /** Indicates which key the participant pressed. */
    response: {
      type: ParameterType.STRING,
    },
    /** The response time in milliseconds for the participant to make a response. The time is measured from when the second stimulus first appears on the screen until the participant's response. */
    rt: {
      type: ParameterType.INT,
    },
    /** `true` if the participant's response matched the `answer` for this trial.  */
    correct: {
      type: ParameterType.BOOL,
    },
    /** The correct answer to the trial, either `'same'` or `'different'`. */
    answer: {
      type: ParameterType.STRING,
    },
  },
};

type Info = typeof info;

/**
 * The same-different-html plugin displays two stimuli sequentially. Stimuli are HTML objects.
 * The participant responds using the keyboard, and indicates whether the stimuli were the
 * same or different. Same does not necessarily mean identical; a category judgment could be made, for example.
 *
 * @author Josh de Leeuw
 * @see {@link https://www.jspsych.org/latest/plugins/same-different-html/ same-different-html plugin documentation on jspsych.org}
 */
class SameDifferentHtmlPlugin implements JsPsychPlugin<Info> {
  static info = info;

  constructor(private jsPsych: JsPsych) {}

  trial(display_element: HTMLElement, trial: TrialType<Info>) {
    display_element.innerHTML =
      '<div class="jspsych-same-different-stimulus">' + trial.stimuli[0] + "</div>";

    var first_stim_info: { key: string; rt: number };
    if (trial.first_stim_duration > 0) {
      this.jsPsych.pluginAPI.setTimeout(() => {
        showBlankScreen();
      }, trial.first_stim_duration);
    } else {
      const afterKeyboardResponse = (info: { key: string; rt: number }) => {
        first_stim_info = info;
        showBlankScreen();
      };
      this.jsPsych.pluginAPI.getKeyboardResponse({
        callback_function: afterKeyboardResponse,
        valid_responses: "ALL_KEYS",
        rt_method: "performance",
        persist: false,
        allow_held_key: false,
      });
    }

    const showBlankScreen = () => {
      display_element.innerHTML = "";

      this.jsPsych.pluginAPI.setTimeout(showSecondStim, trial.gap_duration);
    };

    const showSecondStim = () => {
      var html = '<div class="jspsych-same-different-stimulus">' + trial.stimuli[1] + "</div>";
      //show prompt here
      if (trial.prompt !== null) {
        html += trial.prompt;
      }
      display_element.innerHTML = html;

      if (trial.second_stim_duration > 0) {
        this.jsPsych.pluginAPI.setTimeout(() => {
          display_element.querySelector<HTMLElement>(
            ".jspsych-same-different-stimulus"
          ).style.visibility = "hidden";
        }, trial.second_stim_duration);
      }

      const after_response = (info: { key: string; rt: number }) => {
        var correct = false;

        var skey = trial.same_key;
        var dkey = trial.different_key;

        if (this.jsPsych.pluginAPI.compareKeys(info.key, skey) && trial.answer == "same") {
          correct = true;
        }

        if (this.jsPsych.pluginAPI.compareKeys(info.key, dkey) && trial.answer == "different") {
          correct = true;
        }

        var trial_data = {
          rt: info.rt,
          answer: trial.answer,
          correct: correct,
          stimulus: [trial.stimuli[0], trial.stimuli[1]],
          response: info.key,
        };
        if (first_stim_info) {
          trial_data["rt_stim1"] = first_stim_info.rt;
          trial_data["response_stim1"] = first_stim_info.key;
        }

        this.jsPsych.finishTrial(trial_data);
      };

      this.jsPsych.pluginAPI.getKeyboardResponse({
        callback_function: after_response,
        valid_responses: [trial.same_key, trial.different_key],
        rt_method: "performance",
        persist: false,
        allow_held_key: false,
      });
    };
  }

  simulate(
    trial: TrialType<Info>,
    simulation_mode,
    simulation_options: any,
    load_callback: () => void
  ) {
    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);
    }
  }

  private create_simulation_data(trial: TrialType<Info>, simulation_options) {
    const key = this.jsPsych.pluginAPI.getValidKey([trial.same_key, trial.different_key]);

    const default_data = <any>{
      stimuli: trial.stimuli,
      response: key,
      answer: trial.answer,
      correct: trial.answer == "same" ? key == trial.same_key : key == trial.different_key,
      rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
    };

    if (trial.first_stim_duration == null) {
      default_data.rt_stim1 = this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true);
      default_data.response_stim1 = this.jsPsych.pluginAPI.getValidKey([
        trial.same_key,
        trial.different_key,
      ]);
    }

    const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);

    this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);

    return data;
  }

  private simulate_data_only(trial: TrialType<Info>, simulation_options) {
    const data = this.create_simulation_data(trial, simulation_options);

    this.jsPsych.finishTrial(data);
  }

  private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {
    const data = this.create_simulation_data(trial, simulation_options);

    const display_element = this.jsPsych.getDisplayElement();

    this.trial(display_element, trial);
    load_callback();

    if (trial.first_stim_duration == null) {
      this.jsPsych.pluginAPI.pressKey(data.response_stim1, data.rt_stim1);
    }

    this.jsPsych.pluginAPI.pressKey(
      data.response,
      trial.first_stim_duration + trial.gap_duration + data.rt
    );
  }
}

export default SameDifferentHtmlPlugin;
