class RessourceList extends List {
  constructor(idOrElement, subscriberList, type, options={}) {
    if (!["Corpus", "CorpusFile", "Job", "JobInput", "QueryResult", "User"].includes(type)) {
      console.error("Unknown Type!");
      return;
    }
    if (subscriberList) {
      super(idOrElement, {...RessourceList.options['common'],
                          ...RessourceList.options[type],
                          ...options});
      this.type = type;
      subscriberList.push(this);
    } else {
      super(idOrElement, {...RessourceList.options['extended'],
                          ...RessourceList.options[type],
                          ...options});
      this.type = type;
    }
  }


  _init(ressources) {
    this.clear();
    this.addRessources(Object.values(ressources));
    this.sort("creation_date", {order: "desc"});
  }


  _update(patch) {
    let item, pathArray;

    for (let operation of patch) {
      /* "/{ressourceName}/{ressourceId}/..." -> ["{ressourceId}", "..."] */
      pathArray = operation.path.split("/").slice(2);
      switch(operation.op) {
        case "add":
          if (pathArray.includes("results")) {break;}
          this.addRessources([operation.value]);
          break;
        case "remove":
          this.remove("id", pathArray[0]);
          break;
        case "replace":
          item = this.get("id", pathArray[0])[0];
          switch(pathArray[1]) {
            case "status":
              item.values({status: operation.value,
                           "analyse-link": ["analysing", "prepared", "start analysis"].includes(operation.value) ? `/corpora/${pathArray[0]}/analyse` : ""});
              break;
            default:
              break;
          }
        default:
          break;
      }
    }
  }

  addRessources(ressources) {
    this.add(ressources.map(x => RessourceList.dataMapper[this.type](x)));
  }

  // Use this to modify tooltips to show after 750ms. If loaded is set to
  // true (default) tooltips will only be initialized if DOMContentLoaded event
  // triggered. If you do not want to wait for this event set pass false.
  static modifyTooltips(waitForDOMContentLoaded=true) {
    if (waitForDOMContentLoaded) {
      document.addEventListener('DOMContentLoaded', function() {
        var elems = document.querySelectorAll('.tooltipped');
        var instances = M.Tooltip.init(elems, {enterDelay: 750});
      });
    } else {
      var elems = document.querySelectorAll('.tooltipped');
      var instances = M.Tooltip.init(elems, {enterDelay: 750});
    }
  }
}




RessourceList.dataMapper = {
  // A data mapper describes entitys rendered per row. One key value pair holds
  // the data to be rendered in the list.js table. Key has to correspond
  // with the ValueNames defined below in RessourceList.options ValueNames.
  // Links are declared with double ticks(") around them. The key for links
  // have to correspond with the class of an <a> element in the
  // RessourceList.options item blueprint.

  // Mapping for Corpus entities shown in the dashboard table.
  Corpus: corpus => ({creation_date: corpus.creation_date,
                      "delete-onclick": `prepareDeleteCorpusModal(${corpus.id})`,
                      description: corpus.description,
                      id: corpus.id,
                      "analyse-link": ["analysing", "prepared", "start analysis"].includes(corpus.status) ? `/corpora/${corpus.id}/analyse` : "",
                      "edit-link": `/corpora/${corpus.id}`,
                      status: corpus.status,
                      title: corpus.title}),
  // Mapping for corpus file entities shown in the corpus overview
  CorpusFile: corpus_file => ({filename: corpus_file.filename,
                               author: corpus_file.author,
                               title: corpus_file.title,
                               publishing_year: corpus_file.publishing_year,
                               "edit-link": `${corpus_file.corpus_id}/files/${corpus_file.id}/edit`,
                               "download-link": `${corpus_file.corpus_id}/files/${corpus_file.id}/download`,
                               "delete-modal": `delete-corpus-file-${corpus_file.id}-modal`}),
  // Mapping for job entities shown in the dashboard table.
  Job: job => ({creation_date: job.creation_date,
                "delete-onclick": `prepareDeleteJobModal(${job.id})`,
                description: job.description,
                id: job.id,
                link: `/jobs/${job.id}`,
                service: job.service,
                status: job.status,
                title: job.title}),
  // Mapping for job input files shown in table on every job page
  JobInput: job_input => ({filename: job_input.filename,
                           id: job_input.job_id,
                           "download-link": `${job_input.job_id}/inputs/${job_input.id}/download`}),
  // Mapping for imported result entities from corpus analysis.
  // Shown in imported results table
  QueryResult: query_result => ({corpus_name: query_result.query_metadata.corpus_name,
                                 "delete-onclick": `prepareDeleteQueryResultModal(${query_result.id})`,
                                 description: query_result.description,
                                 id: query_result.id,
                                 "inspect-link": `/query_results/${query_result.id}/inspect`,
                                 link: `/query_results/${query_result.id}`,
                                 query: query_result.query_metadata.query,
                                 title: query_result.title}),
  // Mapping for user entities shown in admin table
  User: user => ({username: user.username,
                  email: user.email,
                  role_id: user.role_id,
                  confirmed: user.confirmed,
                  id: user.id,
                  "profile-link": `user/${user.id}`})
};


RessourceList.options = {
  // common list.js options for 4 rows per page etc.
  common: {page: 4, pagination: {innerWindow: 8, outerWindow: 1}},
  // extended list.js options for 10 rows per page etc.
  extended: {page: 10,
             pagination: [{name: "paginationTop",
                           paginationClass: "paginationTop",
                           innerWindow: 8,
                           outerWindow: 1},
                          {paginationClass: "paginationBottom",
                           innerWindow: 8,
                           outerWindow: 1}]},
  /* Type specific List.js options. Usually only "item" and "valueNames" gets
   * defined here but it is possible to define other List.js options.
   * item: https://listjs.com/api/#item
   * valueNames: https://listjs.com/api/#valueNames
   */
  Corpus: {item: `<tr>
                    <td>
                      <a class="btn-floating disabled">
                        <i class="material-icons service">book</i>
                      </a>
                    </td>
                    <td>
                      <b class="title"></b><br>
                      <i class="description"></i>
                    </td>
                    <td>
                      <span class="badge new status" data-badge-caption="">
                      </span>
                    </td>
                    <td class="actions right-align">
                      <a class="btn-floating modal-trigger red tooltipped waves-effect waves-light delete-onclick" data-position="top" data-tooltip="Delete">
                        <i class="material-icons">delete</i>
                      </a>
                      <a class="btn-floating tooltipped waves-effect waves-light edit-link" data-position="top" data-tooltip="Edit">
                        <i class="material-icons">edit</i>
                      </a>
                      <a class="btn-floating tooltipped waves-effect waves-light analyse-link" data-position="top" data-tooltip="Analyse">
                        <i class="material-icons">search</i>
                      </a>
                    </td>
                  </tr>`,
           valueNames: ["creation_date",
                        "description",
                        "title",
                        {data: ["id"]},
                        {name: "analyse-link", attr: "href"},
                        {name: "delete-onclick", attr: "onclick"},
                        {name: "edit-link", attr: "href"},
                        {name: "status", attr: "data-status"}]},
  CorpusFile: {item: `<tr>
                        <td class="filename" style="word-break: break-word;"></td>
                        <td class="author" style="word-break: break-word;"></td>
                        <td class="title" style="word-break: break-word;"></td>
                        <td class="publishing_year" style="word-break: break-word;"></td>
                        <td class="actions right-align">
                          <a class="btn-floating tooltipped waves-effect waves-light edit-link" data-position="top" data-tooltip="Edit">
                            <i class="material-icons">edit</i>
                          </a>
                          <a class="btn-floating tooltipped waves-effect waves-light download-link" data-position="top" data-tooltip="Download">
                            <i class="material-icons">file_download</i>
                          </a>
                          <a class="btn-floating modal-trigger red tooltipped waves-effect waves-light delete-modal" data-position="top" data-tooltip="Delete">
                            <i class="material-icons">delete</i>
                          </a>
                        </td>
                       </tr>`,
               valueNames: ["filename",
                             "author",
                             "title",
                             "publishing_year",
                             {name: "edit-link", attr: "href"},
                             {name: "download-link", attr: "href"},
                             {name: "delete-modal", attr: "data-target"}]},
  Job: {item: `<tr>
                 <td>
                   <a class="btn-floating disabled">
                     <i class="material-icons service"></i>
                   </a>
                 </td>
                 <td>
                   <b class="title"></b><br>
                   <i class="description"></i>
                 </td>
                 <td>
                   <span class="badge new status" data-badge-caption=""></span>
                 </td>
                 <td class="actions right-align">
                   <a class="btn-floating modal-trigger red tooltipped waves-effect waves-light delete-onclick" data-position="top" data-tooltip="Delete">
                     <i class="material-icons">delete</i>
                   </a>
                   <a class="btn-floating tooltipped waves-effect waves-light link" data-position="top" data-tooltip="Go to Job">
                     <i class="material-icons">send</i>
                  </a>
                 </td>
               </tr>`,
        valueNames: ["creation_date",
                     "description",
                     "title",
                     {data: ["id"]},
                     {name: "delete-onclick", attr: "onclick"},
                     {name: "link", attr: "href"},
                     {name: "service", attr: "data-service"},
                     {name: "status", attr: "data-status"}]},
  JobInput: {item : `<tr>
                       <td class="filename"></td>
                       <td class="actions right-align">
                         <a class="btn-floating tooltipped waves-effect waves-light download-link" data-position="top" data-tooltip="Download">
                           <i class="material-icons">file_download</i>
                         </a>
                       </td>
                     </tr>`,
             valueNames: ["filename",
                          "id",
                          {name: "download-link", attr: "href"}]},
  QueryResult: {item: `<tr>
                         <td>
                           <b class="title"></b><br>
                           <i class="description"></i><br>
                         </td>
                         <td>
                           <span class="corpus_name"></span><br>
                           <span class="query"></span>
                         </td>
                         <td class="actions right-align">
                           <a class="btn-floating modal-trigger red tooltipped waves-effect waves-light delete-onclick" data-position="top" data-tooltip="Delete">
                             <i class="material-icons">delete</i>
                           </a>
                           <a class="btn-floating tooltipped link waves-effect waves-light" data-position="top" data-tooltip="Info">
                             <i class="material-icons">info</i>
                           </a>
                           <a class="btn-floating tooltipped waves-effect waves-light inspect-link" data-position="top" data-tooltip="Analyse">
                             <i class="material-icons">search</i>
                           </a>
                         </td>
                       </tr>`,
                valueNames: ["corpus_name",
                             "description",
                             "query",
                             "title",
                             {data: ["id"]},
                             {name: "delete-onclick", attr: "onclick"},
                             {name: "inspect-link", attr: "href"},
                             {name: "link", attr: "href"}]},
  // User entity blueprint setting html strucuture per entity per row
  // Link classes have to correspond with Links defined in the Mapping process
  User: {item: `<tr>
                  <td class="username"></td>
                  <td class="email"></td>
                  <td class="role_id"></td>
                  <td class="confirmed"></td>
                  <td class="id"></td>
                  <td class="actions right-align">
                    <a class="btn-floating tooltipped profile-link waves-effect waves-light" data-position="top" data-tooltip="Edit User">
                      <i class="material-icons">edit</i>
                    </a>
                  </td>
                </tr>`,
         valueNames: ["username",
                      "email",
                      "role_id",
                      "confirmed",
                      "id",
                      {name: "profile-link", attr: "href"}]}
};


class ResultsList extends List {
  constructor(idOrElement, options={}) {
  super(idOrElement, options);
  this.eventTokens = {};  // all span tokens which are holdeing events if expert
  // mode is on. Collected here to delete later on
  this.currentExpertTokenElements = {}; // all token elements which have added
  // classes like chip and hoverable for expert view. Collected
  //here to delete later on
  this.addToSubResultsStatus = {};  // holds True/false for check buttons used to add matches tu sub-results. If checked, it is True. If unchecked, it is false. Buttons for this have the class add. Those little round check buttons.
  this.addToSubResultsIdsToShow = new Set();  // If check button is pressed its corresponding data_index is saved in this set. The set is shown to the user.
  }

  helperCreateCpos(cpos_ranges, cpos_values) {
    let lc;
    let c;
    let rc;
    if (cpos_ranges) {
      // python range like function from MDN
      // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Sequence_generator_(range)
      const range = (start, stop, step) => Array.from({ length: (stop - start) / step + 1}, (_, i) => start + (i * step));
      lc = range(cpos_values.lc[0], cpos_values.lc[1], 1)
      c = range(cpos_values.c[0], cpos_values.c[1], 1)
      rc = range(cpos_values.rc[0], cpos_values.rc[1], 1)
    } else {
      lc = cpos_values.lc;
      c = cpos_values.c;
      rc = cpos_values.rc;
    }
    return {lc: lc, c: c, rc: rc};
  }

  // handels interactionElements during a pagination navigation
  // loops over interactionElements and executes callback functions accordingly
  pageChangeEventInteractionHandler(interactionElements) {
    // get elements to check thier status
    for (let interaction of interactionElements) {
      if (interaction.checkStatus) {
        if (interaction.element.checked) {
          let f_on = interaction.bindThisToCallback("on");
          let args_on = interaction.callbacks.on.args;
          f_on(...args_on);
        } else {
          let f_off = interaction.bindThisToCallback("off");
          let args_off = interaction.callbacks.off.args;
          f_off(...args_off);
        }
      } else {
        let f = interaction.bindThisToCallback("noCheck");
        let args = interaction.callbacks.noCheck.args;
        f(...args);
      }
    }
  }

  // get display options from display options form element
  static getDisplayOptions(displayOptionsFormElement) {
    // gets display options parameters
    let displayOptionsFormData
    let displayOptionsData;
    displayOptionsFormData = new FormData(displayOptionsFormElement);
    displayOptionsData =
      {
        "resultsPerPage": displayOptionsFormData.get("display-options-form-results_per_page"),
        "resultsContex": displayOptionsFormData.get("display-options-form-result_context"),
        "expertMode": displayOptionsFormData.get("display-options-form-expert_mode")
      };
    return displayOptionsData
  }

  // ###### Functions to add one match to a sub-results ######
  // activate the add buttons
  activateAddToSubResults() {
    subResultsIdListElement.classList.remove("hide");
    if (subResultsExportElement.classList.contains("hide")) {
      subResultsCreateElement.classList.remove("hide");
    }
    let addToSubResultsBtnElements = document.getElementsByClassName("add");
    for (let addToSubResultsBtn of addToSubResultsBtnElements) {
      addToSubResultsBtn.classList.remove("hide");
    }
  }
  // deactivate the add buttons
  deactivateAddToSubResults() {
    subResultsIdListElement.classList.add("hide");
    subResultsCreateElement.classList.add("hide");
    let addToSubResultsBtnElements = document.getElementsByClassName("add");
    for (let addToSubResultsBtn of addToSubResultsBtnElements) {
      addToSubResultsBtn.classList.add("hide");
    }
  }

  // Used in addToSubResults and inspect to toggle the design of the check
  // buttons according to its checked unchecked status.
  helperActivateBtn(btn) {
    btn.classList.remove("grey");
    btn.classList.add("green");
    btn.innerText = "check";
  }

  // Used in addToSubResults and inspect to toggle the design of the check
  // buttons according to its checked unchecked status.
  helperDeactivateBtn(btn) {
    btn.classList.remove("green");
    btn.classList.add("grey");
    btn.innerText = "add";
  }

  // Either adds or removes a match to the sub-results. For this it checks
  // onclick if the current button has been checked or not. For this the
  // function checks if its status in addToSubResultsStatus is either flase or
  // true. Adds match to sub-results if status is false if status is true it
  // removes it.
  addToSubResults(dataIndex, tableCall=true) {
    let textarea = subResultsIdListElement.getElementsByTagName("textarea")[0];
    if (!this.addToSubResultsStatus[dataIndex]
        || this.addToSubResultsStatus === undefined) {
      // add button is activated because status is either false or undefined
      this.helperActivateBtn(event.target);
      this.addToSubResultsStatus[dataIndex] = true;  // sets status to true
      this.addToSubResultsIdsToShow.add(dataIndex + 1);  // + 1 because user does not see zero indexd data indexes
      textarea.innerText = [...this.addToSubResultsIdsToShow].sort(function(a, b){return a-b}).join(", ");  // automaticalle sorts ids into the textarea in ascending order
      M.textareaAutoResize(textarea);  // after an insert textarea has to be resized manually
      nrMarkedMatches.innerText = [...this.addToSubResultsIdsToShow].length;
    } else if (this.addToSubResultsStatus[dataIndex]) {
      // add button is deactivated because status is true
      this.helperDeactivateBtn(event.target);
      this.addToSubResultsStatus[dataIndex] = false;  // sets status to false
      this.addToSubResultsIdsToShow.delete(dataIndex + 1);  // + 1 because user does not see zero indexd data indexes
      textarea.innerText = [...this.addToSubResultsIdsToShow].sort(function(a, b){return a-b}).join(", ");  // automaticalle sorts ids into the textarea in ascending order
      nrMarkedMatches.innerText = [...this.addToSubResultsIdsToShow].length;
      M.textareaAutoResize(textarea); // after an insert textarea has to be resized manually
    }
    // Toggles the create button accoring to the number of ids in addToSubResultsIdsToShow
    if ([...this.addToSubResultsIdsToShow].length > 0) {
      subResultsCreateElement.classList.remove("disabled");
    } else if ([...this.addToSubResultsIdsToShow].length === 0) {
      subResultsCreateElement.classList.add("disabled");
    }
    // After a match as been added or removed the export button will be
    // hidden because the sub-results have been alterd and have to be built //// again. Thus subResultsCreateElement has to be shown again.
    subResultsExportElement.classList.add("hide");
    subResultsCreateElement.classList.remove("hide");
    // Also activate/deactivate buttons in the table/jsList results accordingly
    //if button in inspect was activated/deactivated.
    // This part only runs if tableCall is false.
    if (!tableCall) {
      let tableAddBtn = document.getElementById("query-results").querySelectorAll(`[data-index="${dataIndex}"]`)[0].getElementsByClassName('add')[0].firstElementChild; // gets the add button from the list view
      if (this.addToSubResultsStatus[dataIndex]) {
        this.helperActivateBtn(tableAddBtn);
      } else {
        this.helperDeactivateBtn(tableAddBtn);
      }
    }
  }

  // Triggers emit to get full match context from server for a number of
  // matches identified by their data_index.
  getMatchWithContext(dataIndexes, type) {
    let tmp_first_cpos = [];
    let tmp_last_cpos = [];
    for (let dataIndex of dataIndexes) {
      tmp_first_cpos.push(results.data.matches[dataIndex].c[0]);
      tmp_last_cpos.push(results.data.matches[dataIndex].c[1]);
    }
    nopaque.socket.emit("corpus_analysis_inspect_match",
                      {
                        type: type,
                        data_indexes: dataIndexes,
                        first_cpos: tmp_first_cpos,
                        last_cpos: tmp_last_cpos,
                      }
          );
  }

  // ###### Functions to inspect one match, to show more details ######
  // activate inspect buttons if progress is 100
  activateInspect() {
    if (progress === 100) {
      let inspectBtnElements;
      inspectBtnElements = document.getElementsByClassName("inspect");
      for (let inspectBtn of inspectBtnElements) {
        inspectBtn.classList.remove("disabled");
      }
    } else {
      return
    }
  }

  // ### functions to inspect imported Matches
  // This function creates an object that is similar to the object that is
  // being recieved as an answere to the getMatchWithContext Method, which is
  // triggering an socket.io event.
  // It is used as an input for show match context in the context of imported
  // results to be able to inspect matches.
  createFakeResponse() {
    contextModal.open();
    let cpos_lookup;
    let fake_response = {};
    let contextResultsElement;
    // function to create one match object from entire imported results
    // that is passed into the results.jsList.showMatchContext() function
    fake_response["payload"] = {};
    let dataIndex = event.target.closest("tr").dataset.index;
    fake_response.payload["matches"] = [results.data.matches[dataIndex]];
    contextResultsElement = document.getElementById("context-results");
    contextResultsElement.innerHTML = "";
    let {lc, c, rc} = this.helperCreateCpos(results.data.cpos_ranges,
                                            fake_response.payload.matches[0]);
    cpos_lookup = {};
    for (let cpos of lc) {
      cpos_lookup[cpos] = results.data.cpos_lookup[cpos];
    }
    for (let cpos of c) {
      cpos_lookup[cpos] = results.data.cpos_lookup[cpos];
    }
    for (let cpos of rc) {
      cpos_lookup[cpos] = results.data.cpos_lookup[cpos];
    }
    fake_response.payload["cpos_lookup"] = cpos_lookup
    fake_response.payload["cpos_ranges"] = results.data.cpos_ranges;
    fake_response.payload["query"] = results.data.query;
    fake_response.payload["context_id"] = dataIndex + 1;
    fake_response.payload["match_count"] = fake_response.payload.matches.length
    fake_response.payload["corpus_type"] = "inspect-result"
    return fake_response
  }

  // gets result cpos infos for one dataIndex (list of length 1) to send back to
  // the server
  inspect(dataIndex, type) {
    let contextMatchNrElement;
    let contextResultsElement;
    // get result infos from server and show them in context modal
    this.contextId = dataIndex[0];
     // match nr for user to display derived from data_index
    contextMatchNrElement = document.getElementById("context-match-nr");
    contextMatchNrElement.innerText = this.contextId + 1;
    contextResultsElement = document.getElementById("context-results");
    contextResultsElement.innerHTML = "";  // clear it from old inspects
    this.getMatchWithContext(dataIndex, type);
    contextModal.open();
    // add a button to add this match to sub results with onclick event
    let classes = `btn-floating btn waves-effect` +
                  `waves-light grey right`
    let addToSubResultsIdsBtn = document.createElement("a");
    addToSubResultsIdsBtn.setAttribute("class", classes + ` add`);
    addToSubResultsIdsBtn.innerHTML = '<i class="material-icons">add</i>';
    addToSubResultsIdsBtn.onclick= () => {this.addToSubResults(dataIndex[0], false)};
    // checks if a button has already been added to the inspect modal and removes it
    if (addToSubResultsFromInspectElement.children.length > 0) {
      addToSubResultsFromInspectElement.firstElementChild.remove();
    }
    // Changes the design of the add button according to its checked status
    // upon opening the inspect modal.
    if (this.addToSubResultsStatus[dataIndex[0]]) {
      this.helperActivateBtn(addToSubResultsIdsBtn.firstElementChild);
    } else if (!this.addToSubResultsStatus[dataIndex[0]]) {
      this.helperDeactivateBtn(addToSubResultsIdsBtn.firstElementChild);
    }
    addToSubResultsFromInspectElement.appendChild(addToSubResultsIdsBtn);
  }

  // create Element from HTML String helper function
  HTMLTStrToElement(htmlStr) {
    // https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518
    let template = document.createElement("template");
    htmlStr = htmlStr.trim();
    template.innerHTML = htmlStr;
    return template.content.firstChild;
    }

  // Used as a callback to handle incoming match context results when inspect
  // has been used.
  showMatchContext(response) {
    this.contextData;
    let contextModalLoading;
    let contextModalReady;
    let contextResultsElement;
    let highlightSentencesSwitchElement;
    let htmlTokenStr;
    let modalExpertModeSwitchElement;
    let modalTokenElements;
    let nrOfContextSentences;
    let partElement;
    let token;
    let tokenHTMLArray;
    let tokenHTMlElement;
    let uniqueContextS;
    let uniqueS;

    this.contextData = response.payload;
    console.log(this.contextData);
    this.contextData["cpos_ranges"] = response.payload.cpos_ranges;
    this.contextData["query"] = results.data.query;
    this.contextData["context_id"] = this.contextId;
    this.contextData["match_count"] = this.contextData.matches.length
    this.contextData["corpus_type"] = "inspect-result"
    Object.assign(this.contextData, results.metaData);
    contextResultsElement = document.getElementById("context-results");
    modalExpertModeSwitchElement = document.getElementById("inspect-display-options-form-expert_mode_inspect");
    highlightSentencesSwitchElement = document.getElementById("inspect-display-options-form-highlight_sentences");
    nrOfContextSentences = document.getElementById("context-sentences");
    uniqueS = new Set();
    uniqueContextS = new Set();
    let {lc, c, rc} = this.helperCreateCpos(this.contextData.cpos_ranges,
                                            this.contextData.matches[0])
    // create sentence strings as tokens
    tokenHTMLArray = [];
    for (let cpos of lc) {
      token = this.contextData.cpos_lookup[cpos];
      uniqueS.add(token.s)
      htmlTokenStr = `<span class="token"` +
                           `data-sid="${token.s}"` +
                           `data-cpos="${cpos}">` +
                       `${token.word}` +
                     `</span>`;
      tokenHTMlElement = this.HTMLTStrToElement(htmlTokenStr)
      tokenHTMLArray.push(tokenHTMlElement);
    }
    for (let cpos of c) {
      token = this.contextData.cpos_lookup[cpos];
      uniqueContextS.add(token.s);
      uniqueS.add(token.s);
      htmlTokenStr = `<span class="token bold light-green"` +
                           `data-sid="${token.s}"` +
                           `data-cpos="${cpos}"` +
                           `style="text-decoration-line: underline;">` +
                       `${token.word}` +
                     `</span>`;
      tokenHTMlElement = this.HTMLTStrToElement(htmlTokenStr)
      tokenHTMLArray.push(tokenHTMlElement);
    }
    this.contextData["context_s_ids"] = Array.from(uniqueContextS);
    for (let cpos of rc) {
      token = this.contextData.cpos_lookup[cpos];
      uniqueS.add(token.s)
      htmlTokenStr = `<span class="token"` +
                           `data-sid="${token.s}"` +
                           `data-cpos="${cpos}">` +
                       `${token.word}` +
                     `</span>`;
      tokenHTMlElement = this.HTMLTStrToElement(htmlTokenStr)
      tokenHTMLArray.push(tokenHTMlElement);
    }
    for (let sId of uniqueS) {
      let htmlSentence = `<span class="sentence" data-sid="${sId}"></span>`;
      let sentenceElement = this.HTMLTStrToElement(htmlSentence);
      for (let tokenElement of tokenHTMLArray) {
        if (tokenElement.dataset.sid == sId) {
          sentenceElement.appendChild(tokenElement);
          sentenceElement.insertAdjacentHTML("beforeend", ` `);
        } else {
          continue;
        }
      }
      contextResultsElement.appendChild(sentenceElement);
    }


    // add inspect display options events
    modalExpertModeSwitchElement.onchange = (event) => {
      if (event.target.checked) {
        this.expertModeOn("context-results");
      } else {
        this.expertModeOff("context-results")
      }
    };

    highlightSentencesSwitchElement.onchange = (event) => {
      if (event.target.checked) {
        this.higlightContextSentences();
      } else {
      this.unhighlightContextSentences();
      }
    };

    nrOfContextSentences.onchange = (event) => {
      // console.log(event.target.value);
      this.changeSentenceContext(event.target.value);
    }

    // checks on new modal opening if switches are checked
    // if switches are checked functions are executed
    if (modalExpertModeSwitchElement.checked) {
      this.expertModeOn("context-results");
    }

    if (highlightSentencesSwitchElement.checked) {
      this.higlightContextSentences();
    }

    // checks the value of the number of sentences to show on modal opening
    // sets context sentences accordingly
    this.changeSentenceContext(nrOfContextSentences.value)
  }

  // splits context text into sentences based on spacy sentence split
  higlightContextSentences() {
    let sentences;
    sentences = document.getElementById("context-results").getElementsByClassName("sentence");
      for (let s of sentences) {
        s.insertAdjacentHTML("beforeend", `<span><br><br></span>`)
      }
  }

  unhighlightContextSentences() {
    let sentences;
    let br;
    sentences = document.getElementById("context-results").getElementsByClassName("sentence");
      for (let s of sentences) {
        br = s.lastChild;
        br.remove();
      }
  }

  // changes how many context sentences in inspect view are shown
  changeSentenceContext(sValue, maxSValue=10) {
    let array;
    let sentences;
    let toHideArray;
    let toShowArray;
    sValue = maxSValue - sValue;
    // console.log(sValue);
    sentences = document.getElementById("context-results").getElementsByClassName("sentence");
    array = Array.from(sentences);
    if (sValue != 0) {
      toHideArray = array.slice(0, sValue).concat(array.slice(-(sValue)));
      toShowArray = array.slice(sValue, 9).concat(array.slice(9, -(sValue)))
    } else {
      toHideArray = [];
      toShowArray = array;
    }
    // console.log(array);
    // console.log("#######");
    // console.log(toHideArray);
    for (let s of toHideArray) {
      s.classList.add("hide");
    }
    for (let s of toShowArray) {
      s.classList.remove("hide");
    }
  }

  // ###### Display options changing live how the matches are being displayed ######

  // Event function that changes the shown hits per page.
  // Just alters the resultsList.page property
  changeHitsPerPage(event) {
    try {
      // console.log(this);
      this.page = event.target.value;
      this.update();
      this.activateInspect();
      this.pageChangeEventInteractionHandler(interactionElements);
      if (expertModeSwitchElement.checked) {
        this.expertModeOn("query-display");  // page holds new result rows, so add new tooltips
      }
      nopaque.flash("Updated matches per page.", "corpus")
    } catch (e) {
      // console.log(e);
      // console.log("resultsList has no results right now.");
    }
  }

  // Event function triggered on context select change
  // also if pagination is clicked
  changeContext(event) {
    let array;
    let lc;
    let newContextValue;
    let rc;
    try {
        if (event.type === "change") {
            nopaque.flash("Updated context per match!", "corpus");
        }
    } catch (e) {
    } finally {
        newContextValue = document.getElementById("display-options-form-result_context").value;
        lc = document.getElementsByClassName("left-context");
        rc = document.getElementsByClassName("right-context");
        for (let element of lc) {
          array = Array.from(element.childNodes);
          for (let element of array.reverse().slice(newContextValue)) {
            element.classList.add("hide");
          }
          for (let element of array.slice(0, newContextValue)) {
            element.classList.remove("hide");
          }
        }
        for (let element of rc) {
          array = Array.from(element.childNodes);
          for (let element of array.slice(newContextValue)) {
            element.classList.add("hide");
          }
          for (let element of array.slice(0, newContextValue)) {
            element.classList.remove("hide");
          }
        }
    }
  }

  // ###### Expert view event functions ######
  // function to create a tooltip for the current hovered token
  tooltipEventCreate(event) {
    // console.log("Create Tooltip on mouseover.");
    let token;
    token = results.data.cpos_lookup[event.target.dataset.cpos];
    if (!token) {
      token = this.contextData.cpos_lookup[event.target.dataset.cpos];
    }
    this.addToolTipToTokenElement(event.target, token);
  }

  // Function to destroy the current Tooltip for the current hovered tooltip
  // on mouse leave
  tooltipEventDestroy(event) {
    // console.log("Tooltip destroy on leave.");
    this.currentTooltipElement.destroy();
  }

  expertModeOn(htmlId) {
    // turn the expert mode on for all tokens in the DOM element identified by its htmlID
    if (!Array.isArray(this.currentExpertTokenElements[htmlId])) {
      this.currentExpertTokenElements[htmlId] = [];
    }
    let container = document.getElementById(htmlId);
    let tokens = container.querySelectorAll("span.token");
    this.currentExpertTokenElements[htmlId].push(...tokens);
    this.tooltipEventCreateBind = this.tooltipEventCreate.bind(this);
    this.tooltipEventDestroyBind = this.tooltipEventDestroy.bind(this);
    this.eventTokens[htmlId] = [];
    for (let tokenElement of this.currentExpertTokenElements[htmlId]) {
      tokenElement.classList.add("chip", "hoverable", "expert-view");
      tokenElement.onmouseover = this.tooltipEventCreateBind;
      tokenElement.onmouseout = this.tooltipEventDestroyBind;
      this.eventTokens[htmlId].push(tokenElement);
    }
  }

  // fuction that creates Tooltip for one token and extracts the corresponding
  // infos from the result JSON
  addToolTipToTokenElement(tokenElement, token) {
    this.currentTooltipElement;
    this.currentTooltipElement = M.Tooltip.init(tokenElement,
     {"html": `<table>
                 <tr>
                   <th>Token information</th>
                   <th>Source information</th>
                 </tr>
                 <tr>
                   <td class="left-align">
                     Word: ${token.word}<br>
                     Lemma: ${token.lemma}<br>
                     POS: ${token.pos}<br>
                     Simple POS: ${token.simple_pos}<br>
                     NER: ${token.ner}
                   </td>
                   <td class="left-align">
                     Title: ${results.data.text_lookup[token.text].title}
                     <br>
                     Author: ${results.data.text_lookup[token.text].author}
                     <br>
                     Publishing year: ${results.data.text_lookup[token.text].publishing_year}
                   </td>
                 </tr>
               </table>`}
      );
  }

  // function to remove extra informations and animations from tokens
  expertModeOff(htmlId) {
    // console.log("Expert mode is off.");
    if (!Array.isArray(this.currentExpertTokenElements[htmlId])) {
      this.currentExpertTokenElements[htmlId] = [];
    }
    if (!Array.isArray(this.eventTokens[htmlId])) {
      this.eventTokens[htmlId] = [];
    }
    for (let tokenElement of this.currentExpertTokenElements[htmlId]) {
      tokenElement.classList.remove("chip", "hoverable", "expert-view");
    }
    this.currentExpertTokenElements[htmlId] = [];

    for (let eventToken of this.eventTokens[htmlId]) {
      eventToken.onmouseover = "";
      eventToken.onmouseout = "";
    }
  this.eventTokens[htmlId] = [];
  }

  createResultRowElement(item, chunk, imported=false) {
    let aCellElement;
    let addToSubResultsBtn;
    let cCellElement;
    let cpos;
    let fakeResponse;   // used if imported results are being created;
    let inspectBtn
    let lcCellElement;
    let matchNrElement;
    let matchRowElement;
    let rcCellElement;
    let textTitles;
    let textTitlesCellElement;
    let token;
    let values;
    // gather values from item
    values = item.values();
    let {lc, c, rc} = this.helperCreateCpos(chunk.cpos_ranges,
                                            values)
    // if (chunk.cpos_ranges == true) {
    //   // python range like function from MDN
    //   // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Sequence_generator_(range)
    //   const range = (start, stop, step) => Array.from({ length: (stop - start) / step + 1}, (_, i) => start + (i * step));
    //   lc = range(values.lc[0], values.lc[1], 1)
    //   c = range(values.c[0], values.c[1], 1)
    //   rc = range(values.rc[0], values.rc[1], 1)
    // } else {
    //   lc = values.lc;
    //   c = values.c;
    //   rc = values.rc;
    // }
    // get infos for full match row
    matchRowElement = document.createElement("tr");
    matchRowElement.setAttribute("data-index", values.index)
    lcCellElement = document.createElement("td");
    lcCellElement.classList.add("left-context");
    matchRowElement.appendChild(lcCellElement);
    for (cpos of lc) {
      token = chunk.cpos_lookup[cpos];
      lcCellElement.insertAdjacentHTML("beforeend",
        `<span class="token" data-cpos="${cpos}">${token.word} </span>`);
    }

    // get infos for hit of match and set actions
    textTitles = new Set();
    aCellElement = document.createElement("td");
    aCellElement.classList.add("actions");
    cCellElement = document.createElement("td");
    cCellElement.classList.add("match-hit");
    textTitlesCellElement = document.createElement("td");
    textTitlesCellElement.classList.add("titles");
    matchNrElement = document.createElement("td");
    matchNrElement.classList.add("match-nr");
    matchRowElement.appendChild(cCellElement);
    matchRowElement.appendChild(aCellElement);
    for (cpos of c) {
      token = chunk.cpos_lookup[cpos];
      cCellElement.insertAdjacentHTML("beforeend",
        `<span class="token" data-cpos="${cpos}">${token.word} </span>`);
      // get text titles of every hit cpos token
      textTitles.add(chunk.text_lookup[token.text].title);
    }
    // add some interaction buttons
    // # some btn css rules and classes
    let css = `margin-right: 5px; margin-bottom: 5px;`
    let classes = `btn-floating btn waves-effect` +
                  `waves-light grey`
    // # add button to trigger more context to every match td
    inspectBtn = document.createElement("a");
    inspectBtn.setAttribute("style", css);
    inspectBtn.setAttribute("class", classes + ` disabled inspect`
                            );
    inspectBtn.innerHTML = '<i class="material-icons">search</i>';
    if (imported) {
      inspectBtn.onclick = () => {
        fakeResponse = this.createFakeResponse();
        this.showMatchContext(fakeResponse);
      };
    } else {
      inspectBtn.onclick = () => {this.inspect([values.index], "inspect")};
    }
    // # add btn to add matches to sub-results. hidden per default
    addToSubResultsBtn = document.createElement("a");
    addToSubResultsBtn.setAttribute("style", css);
    addToSubResultsBtn.setAttribute("class", classes + ` hide add`
                                );
    addToSubResultsBtn.innerHTML = '<i class="material-icons">add</i>';
    addToSubResultsBtn.onclick = (event) => {this.addToSubResults(values.index)}
    aCellElement.appendChild(inspectBtn);
    aCellElement.appendChild(addToSubResultsBtn);
    // add text titles at front as first td of one row
    textTitlesCellElement.innerText = [...textTitles].join(", ");
    matchRowElement.insertAdjacentHTML("afterbegin", textTitlesCellElement.outerHTML);
    matchNrElement.innerText = values.index + 1;
    matchRowElement.insertAdjacentHTML("afterbegin", matchNrElement.outerHTML);

    // get infos for right context of match
    rcCellElement = document.createElement("td");
    rcCellElement.classList.add("right-context");
    matchRowElement.appendChild(rcCellElement);
    for (cpos of rc) {
      token = chunk.cpos_lookup[cpos];
      rcCellElement.insertAdjacentHTML("beforeend",
        `<span class="token" data-cpos="${cpos}">${token.word} </span>`);
    }
    return matchRowElement
  }
}