import { Utility } from '../../core/utility';
import { StorageManager, LOCATION } from '../../lib/storage-manager/storage-manager';
import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager';
import { HttpClient } from '../../services/http-client/http-client';

import moment from 'moment';

import './exam-correct.sass';


const EXAM_CORRECT_HEADERS = {
  'Content-Type': HttpClient.ACCEPT.JSON,
  'Accept':       HttpClient.ACCEPT.JSON,
};


const EXAM_CORRECT_IDENT = 'uw-exam-correct';
const EXAM_CORRECT_PART_INPUT_ATTR = 'uw-exam-correct--part-input';
const EXAM_CORRECT_SEND_BTN_ID = 'exam-correct__send-btn';
const EXAM_CORRECT_USER_INPUT_ID = 'exam-correct__user';
const EXAM_CORRECT_USER_INPUT_STATUS_ID = 'exam-correct__user-status';
const EXAM_CORRECT_USER_INPUT_CANDIDATES_ID = 'exam-correct__user-candidates';
const EXAM_CORRECT_USER_INPUT_CANDIDATES_MORE_ID = 'exam-correct__user-candidates-more';
const EXAM_CORRECT_INPUT_BODY_ID = 'exam-correct__new';
const EXAM_CORRECT_USER_ATTR = 'exam-correct--user-id';
const EXAM_CORRECT_USER_DNAME_ATTR = 'exam-correct--user-dname';
const EXAM_CORRECT_STATUS_CELL_CLASS = 'exam-correct--status-cell';

const STATUS = {
  NONE: null,
  SUCCESS: 'success',
  AMBIGUOUS: 'ambiguous',
  FAILURE: 'failure',
  LOADING: 'loading',
};


@Utility({
  selector: `table[${EXAM_CORRECT_IDENT}]`,
})
export class ExamCorrect {

  _element;
  _app;

  _sendBtn;
  _userInput;
  _userInputStatus;
  _userInputCandidates;
  _userInputCandidatesMore;
  _partInputs;
  _resultSelect;
  _resultGradeSelect;
  _resultPassSelect;
  _partDeleteBoxes;

  _dateFormat;
  _cIndices;
  _lastColumnIndex;

  _storageManager;
  _eventManager;

  constructor(element, app) {
    if (!element) {
      throw new Error('Exam Correct utility cannot be setup without an element!');
    }

    if (!app) {
      throw new Error('Exam Correct utility cannot be setup without an app!');
    }

    this._element = element;
    this._app = app;

    this._eventManager = new EventManager();

    // TODO work in progress
    // this._storageManager = new StorageManager('EXAM_CORRECT', '0.0.0', { location: LOCATION.SESSION, encryption: { all: { tag: 'exam-correct', exam: this._element.getAttribute('uw-exam-correct') } } });
    this._storageManager = new StorageManager('EXAM_CORRECT', '0.0.0', { location: LOCATION.WINDOW });

    this._sendBtn = document.getElementById(EXAM_CORRECT_SEND_BTN_ID);
    this._userInput = document.getElementById(EXAM_CORRECT_USER_INPUT_ID);
    this._userInputStatus = document.getElementById(EXAM_CORRECT_USER_INPUT_STATUS_ID);
    this._userInputCandidates = document.getElementById(EXAM_CORRECT_USER_INPUT_CANDIDATES_ID);
    this._userInputCandidatesMore = document.getElementById(EXAM_CORRECT_USER_INPUT_CANDIDATES_MORE_ID);
    this._partInputs = [...this._element.querySelectorAll(`input[${EXAM_CORRECT_PART_INPUT_ATTR}]`)];
    const resultCell = document.getElementById('uw-exam-correct__result');
    this._resultSelect = resultCell && resultCell.querySelector('select');
    const resultDetailCell = document.getElementById('uw-exam-correct__result__pass-grade');
    this._resultGradeSelect = resultDetailCell && resultDetailCell.querySelector('select.uw-exam-correct__grade');
    this._resultPassSelect = resultDetailCell && resultDetailCell.querySelector('select.uw-exam-correct__pass');
    this._partDeleteBoxes = [...this._element.querySelectorAll('input.uw-exam-correct--delete-exam-part')];

    if (this._sendBtn){
      const sendClickEvent = new EventWrapper(EVENT_TYPE.CLICK, this._sendCorrectionHandler.bind(this), this._sendBtn);
      this._eventManager.registerNewListener(sendClickEvent);
    } else {
      console.error('ExamCorrect utility could not detect send button!');
    }

    if (this._userInput) {
      const focusOutEvent = new EventWrapper(EVENT_TYPE.FOCUS_OUT, this._validateUserInput.bind(this), this._userInput);
      this._eventManager.registerNewListener(focusOutEvent);
    } else {
      throw new Error('ExamCorrect utility could not detect user input!');
    }

    for (let deleteBox of this._partDeleteBoxes) {
      const deleteBoxChangeEvent = new EventWrapper(EVENT_TYPE.CHANGE, (() => { this._updatePartDeleteDisabled(deleteBox); }).bind(this), deleteBox);
      this._eventManger.registerNewListener(deleteBoxChangeEvent);
    }

    for (let input of [this._userInput, ...this._partInputs]) {
      const inputKeyDownEvent = new EventWrapper(EVENT_TYPE.KEYDOWN, this._inputKeypress.bind(this), input);
      this._eventManager.registerNewListener(inputKeyDownEvent);
    }

    if (!this._userInputStatus) {
      throw new Error('ExamCorrect utility could not detect user input status element!');
    }

    if (!this._userInputCandidates) {
      throw new Error('ExamCorrect utility could not detect user input candidate list!');
    }

    if (!this._userInputCandidatesMore) {
      throw new Error('ExamCorrect utility could not detect user input candidate more element');
    }

    // TODO get date format by post request
    this._dateFormat = 'DD.MM.YYYY HH:mm:ss';

    this._cIndices = new Map(
      [...this._element.querySelectorAll('[uw-exam-correct-header]')]
        .map((header) => [header.getAttribute('uw-exam-correct-header'), header.cellIndex])
    );

    if (this._resultSelect && this._resultGradeSelect) {
      const resultSelectEvent = new EventWrapper(EVENT_TYPE.CHANGE, (() => {
        if (this._resultSelect.value !== 'grade')
          this._resultGradeSelect.classList.add('grade-hidden');
        else
          this._resultGradeSelect.classList.remove('grade-hidden');
      }).bind(this), this._resultSelect );
      this._eventManager.registerNewListener(resultSelectEvent);

      if (this._resultSelect.value !== 'grade')
        this._resultGradeSelect.classList.add('grade-hidden');
    }
    if (this._resultSelect && this._resultPassSelect) {
      const resultPassSelectEvent = new EventWrapper(EVENT_TYPE.CHANGE, (() => {
        if (this._resultSelect.value !== 'pass')
          this._resultPassSelect.classList.add('pass-hidden');
        else
          this._resultPassSelect.classList.remove('pass-hidden');
      }).bind(this), this._resultSelect);
      this._eventManager.registerNewListener(resultPassSelectEvent);

      if (this._resultSelect.value !== 'pass')
        this._resultPassSelect.classList.add('pass-hidden');
    }

    this._lastColumnIndex = this._element.querySelector('thead > tr').querySelectorAll('th').length - 1;

    // TODO show previously submitted results
    /* const previousEntries = this._storageManager.load('entries');
    if (previousEntries && previousEntries.length > 0) {
      // TODO sort previous results by current sorting order first
      previousEntries.forEach((entry) => this._addRow(entry));
    } */
  }

  destroy() {
    this._eventManager.cleanUp();
  }

  _updatePartDeleteDisabled(deleteBox) {
    const partInput = deleteBox.parentElement.querySelector(`input[${EXAM_CORRECT_PART_INPUT_ATTR}]`);
    if (!partInput)
      return;

    partInput.disabled = deleteBox.checked;
  }

  _inputKeypress() {
    if (event.keyCode !== 13) {
      return false;
    }

    this._sendCorrectionHandler();
  }

  _validateUserInput() {
    (!this._userInput.value) ? this._userInput.classList.add('no-value') : this._userInput.classList.remove('no-value');

    // do nothing in case of empty or invalid input
    if (!this._userInput.value || this._userInput.reportValidity && !this._userInput.reportValidity()) {
      removeAllChildren(this._userInputCandidates);
      this._userInputCandidatesMore.hidden = true;
      setStatus(this._userInputStatus, STATUS.NONE);
      return;
    }

    if (this._userInput.getAttribute(EXAM_CORRECT_USER_ATTR) !== null) {
      if (this._userInput.value === this._userInput.getAttribute(EXAM_CORRECT_USER_DNAME_ATTR)) {
        setStatus(this._userInputStatus, STATUS.SUCCESS);
        return;
      } else {
        this._userInput.removeAttribute(EXAM_CORRECT_USER_ATTR);
        this._userInput.removeAttribute(EXAM_CORRECT_USER_DNAME_ATTR);
      }
    }

    setStatus(this._userInputStatus, STATUS.LOADING);

    const body = this._toRequestBody(this._userInput.value);

    this._app.httpClient.post({
      headers: EXAM_CORRECT_HEADERS,
      body: JSON.stringify(body),
    }).then(
      (response) => response.json()
    ).then(
      (response) => this._processResponse(body, response, body.user)
    ).catch((error) => {
      console.error('Error while validating user input', error);
    });
  }

  _sendCorrectionHandler() {
    // TODO avoid code duplication
    if (this._userInput.reportValidity && !this._userInput.reportValidity()) {
      removeAllChildren(this._userInputCandidates);
      this._userInputCandidatesMore.hidden = true;
      setStatus(this._userInput, STATUS.NONE);
      return;
    }

    // refocus user input element for convenience
    this._userInput.focus();

    const userId = this._userInput.getAttribute(EXAM_CORRECT_USER_ATTR);
    const user = this._userInput.value;

    // abort send if the user input is empty
    if (!user && !userId) {
      return;
    }

    const results = {};
    for (const input of this._partInputs) {
      if (input.disabled) {
        const partKey = input.getAttribute(EXAM_CORRECT_PART_INPUT_ATTR);
        if (!partKey) {
          console.error('Error while parsing results: Could not detect exam part key attribute');
          return;
        }
        results[partKey] = null;
      } else if (input.reportValidity && !input.reportValidity()) {
        input.focus();
        return;
      } else if (input.value) {
        const partKey = input.getAttribute(EXAM_CORRECT_PART_INPUT_ATTR);
        if (!partKey) {
          console.error('Error while parsing results: Could not detect exam part key attribute');
          return;
        }
        results[partKey] = parseFloat(input.value);
      }
    }

    let result;
    if (this._resultSelect && this._resultGradeSelect) {
      switch (this._resultSelect.value) {
        case 'none':
          result = undefined;
          break;
        case 'delete':
          result = null;
          break;
        case 'pass':
          result = { status: 'attended', result: this._resultPassSelect.value };
          break;
        case 'grade':
          result = { status: 'attended', result: this._resultGradeSelect.value };
          break;
        default:
          result = { status: this._resultSelect.value };
      }
    }

    // abort send if there are neither part-results nor an exam-result (after validation)
    if (Object.keys(results).length <= 0 && result === undefined) return;

    const rowInfo = {
      users: [user],
      status: STATUS.LOADING,
    };
    if (results && results !== {}) rowInfo.results = results;
    if (result !== undefined) rowInfo.result = result;
    this._addRow(rowInfo);

    // clear inputs on validation success
    this._clearUserInput();
    this._partInputs.forEach(clearInput);
    this._partDeleteBoxes.forEach(box => { box.checked = false; this._updatePartDeleteDisabled(box); });

    const body = this._toRequestBody(userId || user, results, result);
    
    this._app.httpClient.post({
      headers: EXAM_CORRECT_HEADERS,
      body: JSON.stringify(body),
    }).then(
      (response) => response.json()
    ).then(
      (response) => this._processResponse(body, response, user, undefined, { results: results, result: result })
    ).catch((error) => {
      console.error('Error while processing response', error);
    });
  }

  _processResponse(request, response, user, targetRow, ...results) { // eslint-disable-line no-unused-vars
    if (response) {
      if (response.status === 'no-op') {
        if (response.users) {
          // delete candidate list entries from previous requests
          removeAllChildren(this._userInputCandidates);
          this._userInputCandidatesMore.hidden = true;

          // show error if there are no matches for this input
          if (response.users.length === 0) {
            setStatus(this._userInputStatus, STATUS.FAILURE);
            return;
          }

          // directly accept response if there is exactly one candidate (input not ambiguous)
          if (response.users.length === 1) {
            const candidate = response.users[0];
            this._userInput.value = candidate['display-name'];
            this._userInput.setAttribute(EXAM_CORRECT_USER_ATTR, candidate.id);
            this._userInput.setAttribute(EXAM_CORRECT_USER_DNAME_ATTR, candidate['display-name']);
            setStatus(this._userInputStatus, STATUS.SUCCESS);
            return;
          }

          setStatus(this._userInputStatus, STATUS.AMBIGUOUS);
          // TODO how to destroy candidate handlers?
          response.users.forEach((userCandidate) => {
            const candidateItem = document.createElement('LI');
            candidateItem.innerHTML = userToHTML(userCandidate);
            candidateItem.setAttribute(EXAM_CORRECT_USER_ATTR, userCandidate.id);
            
            const acceptCandidateHandler = () => {
              this._userInput.value = userCandidate['display-name'] || userCandidate.surname || userCandidate['mat-nr'];
              this._userInput.setAttribute(EXAM_CORRECT_USER_ATTR, userCandidate.id);
              
              // remove all candidates on accept
              removeAllChildren(this._userInputCandidates);
              this._userInputCandidatesMore.hidden = true;

              setStatus(this._userInputStatus, STATUS.SUCCESS);

              // TODO focus first part result input element for convenience
            };
            candidateItem.addEventListener('click', acceptCandidateHandler);

            this._userInputCandidates.appendChild(candidateItem);
          });
          this._userInputCandidatesMore.hidden = response['has-more'] !== true;
        } else {
          // TODO what to do in this case?
          setStatus(this._userInputStatus, STATUS.FAILURE);
        }

        return;
      }

      const savedEntries = this._storageManager.load('entries') || [];
      let newEntry = {
        users: null,
        results: {},
        result: undefined,
        status: STATUS.FAILURE,
        message: null,
        date: null,
      };

      const candidateRows = (targetRow && [targetRow]) || [...this._element.rows];
      for (let row of candidateRows) {
        let userElem = row.cells.item(this._cIndices.get('user'));
        const userIdent = userElem && userElem.getAttribute(EXAM_CORRECT_USER_ATTR);  // TODO use other attribute identifier
        if (userIdent === user) {
          let status = STATUS.FAILURE;
          switch (response.status) {
            case 'success':
              status = STATUS.SUCCESS;
              if (response.user) {
                const timeElem = row.cells.item(this._cIndices.get('date'));
                timeElem.innerHTML = moment(response.time).format(this._dateFormat);
                timeElem.classList.remove('exam-correct--local-time');
                newEntry.users = [response.user];
                newEntry.results = response.results;
                if (response.grade !== undefined) newEntry.result = response.grade;
              } else {
                console.error('Invalid response');
                return;
              }
              // TODO set edit button visibility
              break;
            case 'ambiguous':
              // TODO set edit button visibility
              status = STATUS.AMBIGUOUS;
              newEntry.users = response.users;
              newEntry.hasMore = response['has-more'] === true;
              newEntry.message = response.message || null;
              break;
            case 'failure':
              status = STATUS.FAILURE;
              newEntry.users = (response.user && [response.user]) || null;
              newEntry.message = response.message || null;
              break;
            default:
              // TODO show tooltip with 'invalid response'
              // TODO set edit button visibility
              console.error('Invalid response');
              return;
          }
          row.querySelectorAll('.fa-spin').forEach((elem) => {
            setStatus(elem, status);
          });
          newEntry.status = status || STATUS.FAILURE;
          newEntry.date = response.time || moment().utc().format();

          const statusCell = row.querySelector(`.${EXAM_CORRECT_STATUS_CELL_CLASS}`);
          const messageElem = statusCell.querySelector('.uw-exam-correct--message');
          if (messageElem) {
            statusCell.removeChild(messageElem);
          }
          
          if (newEntry.message) {
            const messageElem = document.createElement('SPAN');
            messageElem.classList.add('uw-exam-correct--message');
            const messageText = document.createTextNode(newEntry.message);
            messageElem.appendChild(messageText);
            statusCell.appendChild(messageElem);
          }

          if (userElem && newEntry.users && newEntry.users.length === 1) {
            const user = newEntry.users[0];
            userElem.innerHTML = userToHTML(user);
            userElem.setAttribute(EXAM_CORRECT_USER_ATTR, user.id || user);
          } else if (userElem && newEntry.users) {
            row.replaceChild(userElem, this._showUserList(row, newEntry.users, { partResults: request.results, result: request.grade }, newEntry.hasMore === true));
          }

          for (let [k, v] of Object.entries(newEntry.results)) {
            const resultCell = row.cells.item(this._cIndices.get(k));
            if (v === null) {
              resultCell.innerHTML = '<i class="fas fa-fw fa-trash"></i>';
              resultCell.classList.remove('exam-correct--result-unconfirmed');
            } else if (v && v.result !== undefined && v.result !== null) {
              resultCell.innerHTML = v.result;
              resultCell.classList.remove('exam-correct--result-unconfirmed');
            }
          }

          if (newEntry.result !== undefined) {
            const examResultCell = row.cells.item(this._cIndices.get('result'));
            if (examResultCell) {
              examResultCell.innerHTML = this._examResultToHTML(newEntry.result);
              examResultCell.classList.remove('exam-correct--result-unconfirmed');
            }
          }

          savedEntries.push(newEntry);
          this._storageManager.save('entries', savedEntries);
          return;
        }
      }

      // insert a new row if no previous entry was found
      // this._addRow(newEntry);
      // savedEntries.unshift(newEntry);
      // this._storageManager.save('entries', savedEntries);
    }
  }

  _examResultToHTML(examResult) {
    let html = '';

    if (examResult) {
      if (examResult.status === 'attended') 
        html = examResult.result;
      else
        html = examResult.status;
    } else if (examResult === null) {
      html = '<i class="fas fa-fw fa-trash"></i>';
    }

    return html;
  }

  // TODO better name
  _showUserList(row, users, results, hasMore) {
    let userElem = row.cells.item(this._cIndices.get('user'));
    if (!userElem) {
      userElem = document.createElement('TD');
    }
    if (users) {
      removeAllChildren(userElem);
      const list = document.createElement('UL');
      for (const user of users) {
        const listItem = document.createElement('LI');
        listItem.innerHTML = userToHTML(user);
        listItem.setAttribute(EXAM_CORRECT_USER_ATTR, user.id);
        listItem.setAttribute(EXAM_CORRECT_USER_DNAME_ATTR, user['display-name']);
        // TODO destroy event handler
        const acceptCandidateHandler = () => {
          userElem.innerHTML = userToHTML(user);
          this._rowToRequest(row, listItem, results);
        };
        listItem.addEventListener('click', acceptCandidateHandler);
        list.appendChild(listItem);
      }
      userElem.appendChild(list);
      if (hasMore === true) {
        const moreElem = this._userInputCandidatesMore.cloneNode(true);
        moreElem.removeAttribute('id');
        moreElem.hidden = false;
        userElem.appendChild(moreElem);
      }
    } else {
      console.error('Unable to show users from invalid response');
    }

    return userElem;
  }

  _rowToRequest(row, listItem, results) {
    const now = moment();
    const timeElem = row.cells.item(0);
    timeElem.innerHTML = now.format(this._dateFormat);
    timeElem.classList.add('exam-correct--local-time');
    const userElem = row.cells.item(this._cIndices.get('user'));
    const statusElem = row.querySelector('.exam-correct--ambiguous');

    setStatus(statusElem, STATUS.LOADING);

    const body = this._toRequestBody(listItem.getAttribute(EXAM_CORRECT_USER_ATTR), results.partResults, results.result);

    this._app.httpClient.post({
      headers: EXAM_CORRECT_HEADERS,
      body: JSON.stringify(body),
    }).then(
      (response) => response.json()
    ).then((response) => this._processResponse(body, response, userElem.getAttribute(EXAM_CORRECT_USER_ATTR), row, { results: results.partResults, result: results.result })
    ).catch(console.error);
  }

  _addRow(rowInfo) {
    // TODO create and use template for this
    const newRow = document.createElement('TR');
    newRow.classList.add('table__row');

    const cells = new Map();

    const dateCell = document.createElement('TD');
    dateCell.classList.add('uw-exam-correct--date-cell');
    const date = moment(rowInfo.date);
    dateCell.appendChild(document.createTextNode(date.format(this._dateFormat)));
    dateCell.setAttribute('date', date.utc().format());
    if (rowInfo.status !== STATUS.SUCCESS) dateCell.classList.add('exam-correct--local-time');
    cells.set(this._cIndices.get('date'), dateCell);

    let userCell = document.createElement('TD');
    userCell.classList.add('uw-exam-correct--user-cell');
    if (!rowInfo.users || rowInfo.users.length === 0) {
      console.error('Found rowInfo without users info!');
    } else if (rowInfo.users.length === 1) {
      const user = rowInfo.users[0];
      userCell.innerHTML = userToHTML(user);
      userCell.setAttribute(EXAM_CORRECT_USER_ATTR, user);
    } else {
      userCell = this._showUserList(newRow, rowInfo.users, { partResults: rowInfo.results, result: rowInfo.result });
    }
    cells.set(this._cIndices.get('user'), userCell);

    for (const [partKey, partResult] of Object.entries(rowInfo.results)) {
      const cellIndex = this._cIndices.get(partKey);
      if (cellIndex === undefined) {
        console.error('Could not determine cell index from part key!');
      } else {
        const partCell = document.createElement('TD');

        if (partResult === null) {
          partCell.innerHTML = '<i class="fas fa-fw fa-trash"></i>';
        } else {
          partCell.innerHTML = partResult;
        }
        partCell.classList.add('uw-exam-correct--part-cell', 'exam-correct--result-unconfirmed');
        cells.set(cellIndex, partCell);
      }
    }

    const resultCell = document.createElement('TD');
    resultCell.colSpan = 2;
    resultCell.innerHTML = this._examResultToHTML(rowInfo.result);
    resultCell.classList.add('exam-correct--result-unconfirmed');
    cells.set(this._cIndices.get('result'), resultCell);

    const statusCell = document.createElement('TD');
    statusCell.classList.add(EXAM_CORRECT_STATUS_CELL_CLASS);
    const statusSymbol = document.createElement('I');
    setStatus(statusSymbol, rowInfo.status);
    statusCell.appendChild(statusSymbol);
    cells.set(this._cIndices.get('status'), statusCell);

    for (let i = 0; i <= this._lastColumnIndex; i++) {
      const cell = cells.get(i) || document.createElement('TD');
      cell.classList.add('table__td');
      newRow.appendChild(cell);
    }

    const tableBody = this._element.querySelector('tbody');
    const inputRow = document.getElementById(EXAM_CORRECT_INPUT_BODY_ID);
    tableBody && tableBody.insertBefore(newRow, inputRow ? inputRow.nextSibling : null);
  }

  _clearUserInput() {
    removeAllChildren(this._userInputCandidates);
    this._userInputCandidatesMore.hidden = true;
    clearInput(this._userInput);
    this._userInput.removeAttribute(EXAM_CORRECT_USER_ATTR);
    this._userInput.removeAttribute(EXAM_CORRECT_USER_DNAME_ATTR);
    setStatus(this._userInputStatus, STATUS.NONE);
  }

  _toRequestBody(user, partResults, examResult) {
    const body = {
      user: user,
    };

    if (partResults && Object.entries(partResults).length !== 0)
      body.results = partResults;
    
    if (examResult !== undefined)
      body.grade = examResult;

    return body;
  }

}

// TODO move to general util section?
function clearInput(inputElement) {
  inputElement.value = null;
}
/* function insertAsFirstChild(elementToInsert, parentElement) {
  parentElement.insertBefore(elementToInsert, parentElement.firstChild);
} */
function removeAllChildren(element) {
  while (element.firstChild) {
    element.removeChild(element.firstChild);
  }
}

function userToHTML(user) {
  if (user) {
    if (user['display-name'] && user['surname']) {
      return user['display-name'].replace(new RegExp(user['surname']), `<strong>${user['surname']}</strong>`) + (user['mat-nr'] ? ` (${user['mat-nr']})` : '');
    } else {
      return user;
    }
  } else {
    console.error('Unable to format invalid user response');
    return '';
  }
}

function setStatus(elem, status) {
  const successClasses = ['fas', 'fa-fw', 'fa-check', 'exam-correct--success'];
  const ambiguousClasses = ['fas', 'fa-fw', 'fa-question', 'exam-correct--ambiguous'];
  const errorClasses = ['fas', 'fa-fw', 'fa-times', 'exam-correct--error'];
  const loadingClasses = ['fas', 'fa-fw', 'fa-spinner-third', 'fa-spin'];

  elem.classList.remove(...successClasses, ...ambiguousClasses, ...errorClasses, ...loadingClasses);

  switch (status) {
    case STATUS.NONE:
      break;
    case STATUS.SUCCESS:
      elem.classList.add(...successClasses);
      break;
    case STATUS.AMBIGUOUS:
      elem.classList.add(...ambiguousClasses);
      break;
    case STATUS.FAILURE:
      elem.classList.add(...errorClasses);
      break;
    case STATUS.LOADING:
      elem.classList.add(...loadingClasses);
      break;
    default:
      console.error('Invalid user input status!');
  }
}

