nopaque.corpus_analysis.query_builder.TokenAttributeBuilderFunctions = class TokenAttributeBuilderFunctions {
  constructor(app) {
    this.app = app;
    this.elements = app.elements;

    this.elements.positionalAttrSelection.addEventListener('change', () => {
      this.preparePositionalAttrModal();
    });

    // Options for positional attribute selection
    document.querySelectorAll('.positional-attr-options-action-button[data-options-action]').forEach(button => {
      button.addEventListener('click', () => {this.actionButtonInOptionSectionHandler(button.dataset.optionsAction);});
    });

    this.elements.tokenSubmitButton.addEventListener('click', () => {this.addTokenToQuery();});

    this.elements.positionalAttrModal = M.Modal.init(
      document.querySelector('#corpus-analysis-concordance-positional-attr-modal'), 
      {
        onOpenStart: () => {
          this.preparePositionalAttrModal();
        },
        onCloseStart: () => {
          this.resetPositionalAttrModal();
        }
      }
    );
  }

  resetPositionalAttrModal() {
    let originalSelectionList = 
      `
        <option value="word" selected>word</option>
        <option value="lemma" >lemma</option>
        <option value="english-pos">english pos</option>
        <option value="german-pos">german pos</option>
        <option value="simple_pos">simple_pos</option>
        <option value="empty-token">empty token</option>
      `;
    this.elements.positionalAttrSelection.innerHTML = originalSelectionList;
    this.elements.tokenQuery.innerHTML = '';
    this.elements.tokenBuilderContent.innerHTML = '';
    this.app.toggleClass(['input-field-options'], 'hide', 'remove');
    this.app.toggleClass(['incidence-modifiers', 'or', 'and'], 'disabled', 'add');
    this.app.resetMaterializeSelection([this.elements.positionalAttrSelection], "word");
    this.elements.ignoreCaseCheckbox.checked = false;
    this.elements.editingModusOn = false;
    this.elements.editedQueryChipElementIndex = undefined;
  }

  actionButtonInOptionSectionHandler(elem) {
    let input = this.tokenInputCheck(this.elements.tokenBuilderContent);
    switch (elem) {
      case 'option-group':
        input.value  += '(option1|option2)';
        let firstIndex = input.value.indexOf('option1');
        let lastIndex = firstIndex + 'option1'.length;
        input.focus();
        input.setSelectionRange(firstIndex, lastIndex);
        break;
      case 'wildcard-char':
        input.value += '.';
        break;
      case 'and':
        this.conditionHandler('and');
        break;
      case 'or':
        this.conditionHandler('or');
        break;
      default:
        break;
    }
    this.optionToggleHandler();
  }

  characterIncidenceModifierHandler(elem) {
    let input = this.tokenInputCheck(this.elements.tokenBuilderContent);
    input.value += elem.dataset.token;
  }

  characterNMSubmitHandler(modalId) {
    let modal = document.querySelector(`#${modalId}`);
    let input_n = modal.querySelector('.n-m-input[data-value-type="n"]').value;
    let input_m = modal.querySelector('.n-m-input[data-value-type="m"]') || undefined;
    input_m = input_m !== undefined ? ',' + input_m.value : '';
    let input = `${input_n}${input_m}`;

    let instance = M.Modal.getInstance(modal);
    instance.close();
    let tokenInput = this.tokenInputCheck(this.elements.tokenBuilderContent);
    tokenInput.value += '{' + input + '}';
  }

  conditionHandler(conditionText) {
    let tokenQueryTemplateClone = this.elements.tokenQueryTemplate.content.cloneNode(true);
    tokenQueryTemplateClone.querySelector('.token-query-template-content').appendChild(this.elements.tokenBuilderContent.firstElementChild);
    let notSelectedButton = tokenQueryTemplateClone.querySelector(`[data-condition-pretty-text]:not([data-condition-pretty-text="${conditionText}"])`);
    let deleteButton = tokenQueryTemplateClone.querySelector(`[data-token-query-content-action="delete"]`);
    deleteButton.addEventListener('click', (event) => {
      this.deleteTokenQueryRow(event.target);
    });
    notSelectedButton.parentNode.removeChild(notSelectedButton);
    this.elements.tokenQuery.appendChild(tokenQueryTemplateClone);

    // Deleting the options which do not make sense in the context of the condition like "word" AND "word". Also sets selection default.
    let selectionDefault = "word";
    let optionDeleteList = ['empty-token'];
    if (conditionText === 'and') {
      switch (this.elements.positionalAttrSelection.value) {
        case 'english-pos' || 'german-pos':
          optionDeleteList.push('english-pos', 'german-pos');
          break;
        default:
          optionDeleteList.push(this.elements.positionalAttrSelection.value);
          break;
      }
    } else {
      let originalSelectionList = 
      `
        <option value="word" selected>word</option>
        <option value="lemma" >lemma</option>
        <option value="english-pos">english pos</option>
        <option value="german-pos">german pos</option>
        <option value="simple_pos">simple_pos</option>
      `;
      this.elements.positionalAttrSelection.innerHTML = originalSelectionList;
      M.FormSelect.init(this.elements.positionalAttrSelection);
    }
    let lastTokenQueryRow = this.elements.tokenQuery.lastElementChild;
    if(lastTokenQueryRow.querySelector('[data-kind-of-token="word"]') || lastTokenQueryRow.querySelector('[data-kind-of-token="lemma"]')) {
      this.appendIgnoreCaseCheckbox(lastTokenQueryRow.querySelector('.token-query-template-content'), this.elements.ignoreCaseCheckbox.checked);
    }
    this.elements.ignoreCaseCheckbox.checked = false;
    this.setTokenSelection(selectionDefault, optionDeleteList);
  }

  deleteTokenQueryRow(deleteButton) {
    let deletedRow = deleteButton.closest('.row');
    let condition = deletedRow.querySelector('[data-condition-pretty-text]').dataset.conditionPrettyText;
    if (condition === 'and') {
      let kindOfToken = deletedRow.querySelector('[data-kind-of-token]').dataset.kindOfToken;
      switch (kindOfToken) {
        case 'english-pos' || 'german-pos':
          this.createOptionElementForPosAttrSelection('english-pos');
          this.createOptionElementForPosAttrSelection('german-pos');
          break;
        default:
          this.createOptionElementForPosAttrSelection(kindOfToken);
          break;
      }
      M.FormSelect.init(this.elements.positionalAttrSelection);
    }
    deletedRow.remove();
  }

  createOptionElementForPosAttrSelection(kindOfToken) {
    let option = document.createElement('option');
    option.value = kindOfToken;
    option.text = kindOfToken;
    this.elements.positionalAttrSelection.appendChild(option);
  }

  appendIgnoreCaseCheckbox(parentElement, checked = false) {
    let ignoreCaseCheckboxClone = document.querySelector('#ignore-case-checkbox-template').content.cloneNode(true);
    parentElement.appendChild(ignoreCaseCheckboxClone);
    M.Tooltip.init(parentElement.querySelectorAll('.tooltipped'));
    if (checked) {
      parentElement.querySelector('input[type="checkbox"]').checked = true;
    }
  }

  setTokenSelection(selection, optionDeleteList) {
    optionDeleteList.forEach(option => {
      if (this.elements.positionalAttrSelection.querySelector(`option[value=${option}]`) !== null) {
        this.elements.positionalAttrSelection.querySelector(`option[value=${option}]`).remove();
      }
    });

    this.app.resetMaterializeSelection([this.elements.positionalAttrSelection], selection);
    this.preparePositionalAttrModal();
  }

  preparePositionalAttrModal() {
    let selection = this.elements.positionalAttrSelection.value;
    if (selection !== 'empty-token') {
      let selectionTemplate = document.querySelector(`.token-builder-section[data-token-builder-section="${selection}"]`);
      let selectionTemplateClone = selectionTemplate.content.cloneNode(true);
    
      this.elements.tokenBuilderContent.innerHTML = '';
      this.elements.tokenBuilderContent.appendChild(selectionTemplateClone);
      if (this.elements.tokenBuilderContent.querySelector('select') !== null) {
        let selectElement = this.elements.tokenBuilderContent.querySelector('select');
        M.FormSelect.init(selectElement);
        selectElement.addEventListener('change', () => {this.optionToggleHandler();});
      } else {
        this.elements.tokenBuilderContent.querySelector('input').addEventListener('input', () => {this.optionToggleHandler();});
      }
    }
    this.optionToggleHandler();

    if (selection === 'word' || selection === 'lemma') {
      this.app.toggleClass(['input-field-options'], 'hide', 'remove');
    } else if (selection === 'empty-token'){
      this.addTokenToQuery();
    } else {
      this.app.toggleClass(['input-field-options'], 'hide', 'add');
    }
  }

  tokenInputCheck(elem) {
    return elem.querySelector('select') !== null ? elem.querySelector('select') : elem.querySelector('input');
  }

  optionToggleHandler() {
    let input = this.tokenInputCheck(this.elements.tokenBuilderContent);
    if (input.value === '' && this.elements.editingModusOn === false) {
      this.app.toggleClass(['incidence-modifiers', 'or', 'and'], 'disabled', 'add');
    } else if (this.elements.positionalAttrSelection.querySelectorAll('option').length === 1) {
      this.app.toggleClass(['and'], 'disabled', 'add');
      this.app.toggleClass(['or'], 'disabled', 'remove');
    } else {
      this.app.toggleClass(['incidence-modifiers', 'or', 'and'], 'disabled', 'remove');
    }
  }

  addTokenToQuery() {
    let tokenQueryPrettyText = '';
    let tokenQueryCQLText = '';
    let input;
    let kindOfToken = this.kindOfTokenCheck(this.elements.positionalAttrSelection.value);
    
    // Takes all rows of the token query (if there is a query concatenation).
    // Adds their contents to tokenQueryPrettyText and tokenQueryCQLText, which will later be expanded with the current input field.
    let tokenQueryRows = this.elements.tokenQuery.querySelectorAll('.row');
    tokenQueryRows.forEach(row => {
      let ignoreCaseCheckbox = row.querySelector('input[type="checkbox"]');
      let c = ignoreCaseCheckbox !== null && ignoreCaseCheckbox.checked ? ' %c' : '';
      let tokenQueryRowInput = this.tokenInputCheck(row.querySelector('.token-query-template-content'));
      let tokenQueryKindOfToken = this.kindOfTokenCheck(tokenQueryRowInput.closest('.input-field').dataset.kindOfToken);
      let tokenConditionPrettyText = row.querySelector('[data-condition-pretty-text]').dataset.conditionPrettyText;
      let tokenConditionCQLText = row.querySelector('[data-condition-cql-text]').dataset.conditionCqlText;
      tokenQueryPrettyText += `${tokenQueryKindOfToken}=${tokenQueryRowInput.value}${c} ${tokenConditionPrettyText} `;
      tokenQueryCQLText += `${tokenQueryKindOfToken}="${tokenQueryRowInput.value}"${c} ${tokenConditionCQLText}`;
    });
    if (kindOfToken === 'empty-token') {
      tokenQueryPrettyText += 'empty token';
    } else {
      let c = this.elements.ignoreCaseCheckbox.checked ? ' %c' : '';
      input = this.tokenInputCheck(this.elements.tokenBuilderContent);
      tokenQueryPrettyText += `${kindOfToken}=${input.value}${c}`;
      tokenQueryCQLText += `${kindOfToken}="${input.value}"${c}`;
    }
    // isTokenQueryInvalid looks if a valid value is passed. If the input fields/dropdowns are empty (isTokenQueryInvalid === true), no token is added.
    if (this.elements.positionalAttrSelection.value !== 'empty-token' && input.value === '') {
      this.disableTokenSubmit();
    } else {
      tokenQueryCQLText = `[${tokenQueryCQLText}]`;
      this.app.submitQueryChipElement('token', tokenQueryPrettyText, tokenQueryCQLText, null, false, kindOfToken === 'empty-token' ? false : true);
      this.elements.positionalAttrModal.close();
    }
  }
  
  kindOfTokenCheck(kindOfToken) {
    return kindOfToken === 'english-pos' || kindOfToken === 'german-pos' ? 'pos' : kindOfToken;
  }

  disableTokenSubmit() {
    this.elements.tokenSubmitButton.classList.add('red');
    this.elements.noValueMessage.classList.remove('hide');
    setTimeout(() => {
      this.elements.tokenSubmitButton.classList.remove('red');
    }, 500);
    setTimeout(() => {
      this.elements.noValueMessage.classList.add('hide');
    }, 3000);
  }

  editTokenChipElement(queryElementsContent) {
    this.elements.positionalAttrModal.open();
    queryElementsContent.forEach((queryElement) => {
      this.app.resetMaterializeSelection([this.elements.positionalAttrSelection], queryElement.tokenAttr);
      this.preparePositionalAttrModal();
      switch (queryElement.tokenAttr) {
        case 'word':
        case 'lemma':
          this.elements.tokenBuilderContent.querySelector('input').value = queryElement.tokenValue;
          break;
        case 'english-pos':
          // English-pos is selected by default. Then it is checked whether the passed token value occurs in the english-pos selection. If not, the selection is reseted and changed to german-pos.
          let selection = this.elements.tokenBuilderContent.querySelector('select');
          queryElement.tokenAttr = selection.querySelector(`option[value=${queryElement.tokenValue}]`) ? 'english-pos' : 'german-pos';
          this.app.resetMaterializeSelection([this.elements.positionalAttrSelection], queryElement.tokenAttr);
          this.preparePositionalAttrModal();
          this.app.resetMaterializeSelection([this.elements.tokenBuilderContent.querySelector('select')], queryElement.tokenValue);
          break;
        case 'simple_pos':
          this.app.resetMaterializeSelection([this.elements.tokenBuilderContent.querySelector('select')], queryElement.tokenValue);
        default:
          break;
      }
      if (queryElement.ignoreCase) {
        this.elements.ignoreCaseCheckbox.checked = true;
      }
      if (queryElement.condition !== undefined) {
        this.conditionHandler(queryElement.condition, true);
      }

    });
  }

  prepareTokenQueryElementsContent(queryChipElement) {
    //this regex searches for word or lemma or pos or simple_pos="any string within single or double quotes" followed by one or no ignore case markers, followed by one or no condition characters.
    let regex = new RegExp('(word|lemma|pos|simple_pos)=(("[^"]+")|(\\\\u0027[^\\\\u0027]+\\\\u0027)) ?(%c)? ?(\\&|\\|)?', 'gm');
    let m;
    let queryElementsContent = [];
    while ((m = regex.exec(queryChipElement.dataset.query)) !== null) {
      // this is necessary to avoid infinite loops with zero-width matches
      if (m.index === regex.lastIndex) {
          regex.lastIndex++;
      }
      let tokenAttr = m[1];
      // Passes english-pos by default so that the template is added. In editTokenChipElement it is then checked whether it is english-pos or german-pos.
      if (tokenAttr === 'pos') {
        tokenAttr = 'english-pos';
      }
      let tokenValue = m[2].replace(/"|'/g, '');
      let ignoreCase = false;
      let condition = undefined;
      m.forEach((match) => {
        if (match === "%c") {
          ignoreCase = true;
        } else if (match === "&") {
          condition = "and";
        } else if (match === "|") {
          condition = "or";
        }
      });
      queryElementsContent.push({tokenAttr: tokenAttr, tokenValue: tokenValue, ignoreCase: ignoreCase, condition: condition});
    }
    return queryElementsContent;
  }

}