/** * @class HtmlTerminal * * This class is a very basic implementation of a "terminal" in the browser. * It provides simple functions like "write" and an "input" Callback. * * @license AGPL-2.0 * @author Alexaner Wunschik */ class HtmlTerminal { /** * Input callback. * If the prompt is activated by calling the input function * a callback is defined. If this member is not set this means * the prompt is not active. * * @private * @type {function} */ #inputCallback = undefined; /** * A html element to show a "prompt". * * @private * @type {HTMLElement} */ #$prompt = undefined; /** * Constructor * Creates a basic terminal simulation on the provided HTMLElement. * * @param {HTMLElement} $output - a dom element */ constructor($output) { // Store the output DOM element in a local variable. this.$output = $output; // Clear terminal. this.clear(); // Add the call "terminal" to the $output element. this.$output.classList.add('terminal'); // Create a prompt element. // This element gets added if input is needed this.#$prompt = document.createElement("span"); this.#$prompt.setAttribute("id", "prompt"); this.#$prompt.innerText = ""; //TODO: this handler shouls be only on the propt element and only active if cursor is visible document.addEventListener("keyup", this.#handleKey.bind(this)); } /** * Creates a new HTMLElement with the given text content. * This element than gets added to the $output as a new "line". * * @private * @memberof MinimalTerminal * @param {String} text - text that should be displayed in the new "line". * @returns {HTMLElement} return a new DOM Element

   */
  #newLine(text) {
    const $lineNode = document.createElement("pre");
    $lineNode.classList.add("line");
    $lineNode.innerText = text;
    return $lineNode;
  }

  /**
   * TODO
   * 
   * @private
   * @param {*} e 
   */
  #handleKey(e) {
    // if no input-callback is defined 
    if (!this.#inputCallback) {
      return;
    }

    if (e.keyCode === 13 /* ENTER */) {
      // create a new line with the text input and remove the prompt
      const text = this.#$prompt.innerText;
      this.write(text + "\n");
      this.#$prompt.innerText = "";
      this.#$prompt.remove();

      // return the inputed text
      this.#inputCallback(text);
      
      // remove the callback and the key handler
      this.#inputCallback = undefined;
    } else if (e.keyCode === 8 /* BACKSPACE */) {
      this.#$prompt.innerText = this.#$prompt.innerText.slice(0, -1);
    } else {
      this.#$prompt.innerHtml = '';
      this.#$prompt.innerText =  this.#$prompt.innerText + e.key;
    }
  }

  /**
   * Clear the terminal.
   * Remove all lines.
   * 
   * @public
   */
  clear() {
    this.$output.innerText = "";
  }

  /**
   * TODO:
   * 
   * @public
   * @param {*} htmlContent 
   */
  inserHtml(htmlContent) {
    const $htmlNode = document.createElement("div");
    $htmlNode.innerHTML = htmlContent;
    this.$output.appendChild($htmlNode);
    document.body.scrollTo(0, document.body.scrollHeight);
  }

  /**
   * Write a text to the terminal.
   * By default there is no linebreak at the end of a new line
   * except the line ensd with a "\n".
   * If the given text has multible linebreaks, multibe lines are inserted.
   * 
   * @public
   * @param {string} text 
   */
  write(text) {
    if (text.match(/^\n*$/)) {
      // empty new line
      text.match(/\n/g).forEach(() => {
        const $br = document.createElement("br");
        this.$output.appendChild($br);
      });
    } else if (text && text.length && text.includes("\n")) {
      const lines = text.split("\n");
      lines.forEach((line) => {
        if (line.length === 0 || line.match(/^\s*$/)) {
          this.$output.appendChild(document.createElement("br"));
        } else {
          const $lineNode = this.#newLine(line);
          this.$output.appendChild($lineNode);
          //this.$node.appendChild(document.createElement("br"));
        }
      });
    } else if (text && text.length) {
      // simple line
      const $lineNode = this.#newLine(text);
      this.$output.appendChild($lineNode);
    }

    // scroll to the buttom of the page
    document.body.scrollTo(0, document.body.scrollHeight);
  }

  /**
   * Like "write" but with a newline at the end.
   * 
   * @public
   * @param {*} text 
   */
  writeln(text) {
    this.write(text + "\n");
  }

  /**
   * Query from user input.
   * This is done by adding a input-element at the end of the terminal,
   * that showes a prompt and a blinking cursor.
   * If a key is pressed the input is added to the prompt element.
   * The input ends with a linebreak.
   * 
   * @public
   * @param {*} callback 
   */
  input(callback) {
    // show prompt with a blinking prompt
    this.$output.appendChild(this.#$prompt);
    this.#inputCallback = callback;
  }
}