// Copyright 2024 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

const wasmFilePath = "main.wasm";
const term = document.getElementById("terminal");
var width = 80;
var height = 24;
const beepAudio = new Audio("beep.wav");

var cx = -1;
var cy = -1;
var cursorClass = "cursor-blinking-block";
var cursorColor = "";

var content; // {data: row[height], dirty: bool}
// row = {data: element[width], previous: span}
// dirty/[previous being null] indicates if previous (or entire terminal) needs to be recalculated.
// dirty is true/null if terminal/previous need to be re-calculated/shown

function initialize() {
  resize(width, height); // initialize content
  show(); // then show the screen
}

function resize(w, h) {
  width = w;
  height = h;
  content = { data: new Array(height), dirty: true };
  for (let i = 0; i < height; i++) {
    content.data[i] = { data: new Array(width), previous: null };
  }

  clearScreen();
}

function clearScreen(fg, bg) {
  if (fg) {
    term.style.color = intToHex(fg);
  }
  if (bg) {
    term.style.backgroundColor = intToHex(bg);
  }

  content.dirty = true;
  for (let i = 0; i < height; i++) {
    content.data[i].previous = null; // we set the row to be recalculated later
    for (let j = 0; j < width; j++) {
      content.data[i].data[j] = document.createTextNode(" "); // set the entire row to spaces.
    }
  }
}

function drawCell(x, y, s, fg, bg, attrs, us, uc) {
  var span = document.createElement("span");
  var use = false;

  if ((attrs & (1 << 2)) != 0) {
    // reverse video
    var temp = bg;
    bg = fg;
    fg = temp;
    use = true;
  }
  if (fg != -1) {
    span.style.color = intToHex(fg);
    use = true;
  }
  if (bg != -1) {
    span.style.backgroundColor = intToHex(bg);
    use = true;
  }

  // NB: these has to be updated if Attrs.go changes
  if (attrs != 0) {
    use = true;
    if ((attrs & 1) != 0) {
      span.classList.add("bold");
    }
    if ((attrs & (1 << 4)) != 0) {
      span.classList.add("dim");
    }
    if ((attrs & (1 << 5)) != 0) {
      span.classList.add("italic");
    }
    if ((attrs & (1 << 6)) != 0) {
      span.classList.add("strikethrough");
    }
  }
  if (us != 0) {
    use = true;
    if (us == 1) {
      span.classList.add("underline");
    } else if (us == 2) {
      span.classList.add("double_underline");
    } else if (us == 3) {
      span.classList.add("curly_underline");
    } else if (us == 4) {
      span.classList.add("dotted_underline");
    } else if (us == 5) {
      span.classList.add("dashed_underline");
    }
    if (uc != -1) {
      span.style.textDecorationColor = intToHex(uc);
    }
  }

  if ((attrs & (1 << 1)) != 0) {
    var blink = document.createElement("span");
    blink.classList.add("blink");
    var textnode = document.createTextNode(s);
    blink.appendChild(textnode);
    span.appendChild(blink);
  } else {
    var textnode = document.createTextNode(s);
    span.appendChild(textnode);
  }

  content.dirty = true; // invalidate terminal- new cell
  content.data[y].previous = null; // invalidate row- new row
  content.data[y].data[x] = use ? span : textnode;
}

function show() {
  if (!content.dirty) {
    return; // no new draws; no need to update
  }

  displayCursor();

  term.innerHTML = "";
  content.data.forEach((row) => {
    if (row.previous == null) {
      row.previous = document.createElement("span");
      row.data.forEach((c) => {
        row.previous.appendChild(c);
      });
      row.previous.appendChild(document.createTextNode("\n"));
    }
    term.appendChild(row.previous);
  });

  content.dirty = false;
}

function showCursor(x, y) {
  content.dirty = true;

  if (!(cx < 0 || cy < 0)) {
    // if original position is a valid cursor position
    content.data[cy].previous = null;
    if (content.data[cy].data[cx].classList) {
      content.data[cy].data[cx].classList.remove(cursorClass);
    }
  }

  cx = x;
  cy = y;
}

function displayCursor() {
  content.dirty = true;

  if (!(cx < 0 || cy < 0)) {
    // if new position is a valid cursor position
    content.data[cy].previous = null;

    if (!content.data[cy].data[cx].classList) {
      var span = document.createElement("span");
      span.appendChild(content.data[cy].data[cx]);
      content.data[cy].data[cx] = span;
    }

    if (cursorColor != "") {
      term.style.setProperty("--cursor-color", cursorColor);
    } else {
      term.style.setProperty("--cursor-color", "lightgrey");
    }

    content.data[cy].data[cx].classList.add(cursorClass);
  }
}

function setCursorStyle(newClass, newColor) {
  if (newClass == cursorClass && newColor == cursorColor) {
    return;
  }

  if (!(cx < 0 || cy < 0)) {
    // mark cursor row as dirty; new class has been applied to (cx, cy)
    content.dirty = true;
    content.data[cy].previous = null;

    if (content.data[cy].data[cx].classList) {
      content.data[cy].data[cx].classList.remove(cursorClass);
    }

    // adding the new class will be dealt with when displayCursor() is called
  }

  cursorClass = newClass;
  cursorColor = newColor;
}

function beep() {
  beepAudio.currentTime = 0;
  beepAudio.play();
}

function setTitle(title) {
  document.title = title;
}

function intToHex(n) {
  return "#" + n.toString(16).padStart(6, "0");
}

initialize();

let fontwidth = term.clientWidth / width;
let fontheight = term.clientHeight / height;

document.addEventListener("keydown", (e) => {
  onKeyEvent(e.key, e.shiftKey, e.altKey, e.ctrlKey, e.metaKey);
});

term.addEventListener("click", (e) => {
  onMouseClick(
    Math.min((e.offsetX / fontwidth) | 0, width - 1),
    Math.min((e.offsetY / fontheight) | 0, height - 1),
    e.which,
    e.shiftKey,
    e.altKey,
    e.ctrlKey
  );
});

term.addEventListener("mousemove", (e) => {
  onMouseMove(
    Math.min((e.offsetX / fontwidth) | 0, width - 1),
    Math.min((e.offsetY / fontheight) | 0, height - 1),
    e.which,
    e.shiftKey,
    e.altKey,
    e.ctrlKey
  );
});

term.addEventListener("focus", (e) => {
  onFocus(true);
});

term.addEventListener("blur", (e) => {
  onFocus(false);
});
term.tabIndex = 0;

document.addEventListener("paste", (e) => {
  e.preventDefault();
  var text = (e.originalEvent || e).clipboardData.getData("text/plain");
  onPaste(true);
  for (let i = 0; i < text.length; i++) {
    onKeyEvent(text.charAt(i), false, false, false, false);
  }
  onPaste(false);
});

const go = new Go();
WebAssembly.instantiateStreaming(fetch(wasmFilePath), go.importObject).then(
  (result) => {
    go.run(result.instance);
  }
);
