"use strict";

async function openTransformTool() {
  const width = Math.min(400, window.innerWidth * 0.5);
  const previewScale = width / graphWidth;
  const height = graphHeight * previewScale;

  let mouseIsDown = false;
  let mouseX = 0;
  let mouseY = 0;

  resetInputs();
  loadPreview();

  $("#transformTool").dialog({
    title: "Transform map",
    resizable: false,
    position: {my: "center", at: "center", of: "svg"},
    buttons: {
      Transform: function () {
        closeDialogs();
        transformMap();
      },
      Cancel: function () {
        $(this).dialog("close");
      }
    }
  });

  if (modules.openTransformTool) return;
  modules.openTransformTool = true;

  // add listeners
  byId("transformToolBody").on("input", handleInput);
  byId("transformPreview")
    .on("mousedown", handleMousedown)
    .on("mouseup", _ => (mouseIsDown = false))
    .on("mousemove", handleMousemove)
    .on("wheel", handleWheel);

  async function loadPreview() {
    byId("transformPreview").style.width = width + "px";
    byId("transformPreview").style.height = height + "px";

    const options = {noWater: true, fullMap: true, noLabels: true, noScaleBar: true, noVignette: true, noIce: true};
    const url = await getMapURL("png", options);
    const SCALE = 4;

    const img = new Image();
    img.src = url;
    img.onload = function () {
      const $canvas = byId("transformPreviewCanvas");
      $canvas.style.width = width + "px";
      $canvas.style.height = height + "px";
      $canvas.width = width * SCALE;
      $canvas.height = height * SCALE;
      $canvas.getContext("2d").drawImage(img, 0, 0, width * SCALE, height * SCALE);
    };
  }

  function resetInputs() {
    byId("transformAngleInput").value = 0;
    byId("transformAngleOutput").value = "0";
    byId("transformMirrorH").checked = false;
    byId("transformMirrorV").checked = false;
    byId("transformScaleInput").value = 0;
    byId("transformScaleResult").value = 1;
    byId("transformShiftX").value = 0;
    byId("transformShiftY").value = 0;
    handleInput();

    updateCellsNumber(byId("pointsInput").value);
    byId("transformPointsInput").oninput = e => updateCellsNumber(e.target.value);

    function updateCellsNumber(value) {
      byId("transformPointsInput").value = value;
      const cells = cellsDensityMap[value];
      byId("transformPointsInput").dataset.cells = cells;
      const output = byId("transformPointsFormatted");
      output.value = cells / 1000 + "K";
      output.style.color = getCellsDensityColor(cells);
    }
  }

  function handleInput() {
    const angle = (+byId("transformAngleInput").value / 180) * Math.PI;
    const shiftX = +byId("transformShiftX").value;
    const shiftY = +byId("transformShiftY").value;
    const mirrorH = byId("transformMirrorH").checked;
    const mirrorV = byId("transformMirrorV").checked;

    const EXP = 1.0965;
    const scale = rn(EXP ** +byId("transformScaleInput").value, 2); // [0.1, 10]x
    byId("transformScaleResult").value = scale;

    byId("transformPreviewCanvas").style.transform = `
      translate(${shiftX * previewScale}px, ${shiftY * previewScale}px)
      scale(${mirrorH ? -scale : scale}, ${mirrorV ? -scale : scale})
      rotate(${angle}rad)
    `;
  }

  function handleMousedown(e) {
    mouseIsDown = true;
    const shiftX = +byId("transformShiftX").value;
    const shiftY = +byId("transformShiftY").value;
    mouseX = shiftX - e.clientX / previewScale;
    mouseY = shiftY - e.clientY / previewScale;
  }

  function handleMousemove(e) {
    if (!mouseIsDown) return;
    e.preventDefault();

    byId("transformShiftX").value = Math.round(mouseX + e.clientX / previewScale);
    byId("transformShiftY").value = Math.round(mouseY + e.clientY / previewScale);
    handleInput();
  }

  function handleWheel(e) {
    const $scaleInput = byId("transformScaleInput");
    $scaleInput.value = $scaleInput.valueAsNumber - Math.sign(e.deltaY);
    handleInput();
  }

  function transformMap() {
    INFO && console.group("transformMap");

    const transformPointsValue = byId("transformPointsInput").value;
    const globalPointsValue = byId("pointsInput").value;
    if (transformPointsValue !== globalPointsValue) changeCellsDensity(transformPointsValue);

    const [projection, inverse] = getProjection();

    applyGraphSize();
    fitMapToScreen();
    resetZoom(0);
    undraw();
    Resample.process({projection, inverse, scale: 1});

    drawLayers();

    INFO && console.groupEnd("transformMap");
  }

  function getProjection() {
    const centerX = graphWidth / 2;
    const centerY = graphHeight / 2;
    const shiftX = +byId("transformShiftX").value;
    const shiftY = +byId("transformShiftY").value;
    const angle = (+byId("transformAngleInput").value / 180) * Math.PI;
    const cos = Math.cos(angle);
    const sin = Math.sin(angle);
    const scale = +byId("transformScaleResult").value;
    const mirrorH = byId("transformMirrorH").checked;
    const mirrorV = byId("transformMirrorV").checked;

    function project(x, y) {
      // center the point
      x -= centerX;
      y -= centerY;

      // apply scale
      if (scale !== 1) {
        x *= scale;
        y *= scale;
      }

      // apply rotation
      if (angle) [x, y] = [x * cos - y * sin, x * sin + y * cos];

      // apply mirroring
      if (mirrorH) x = -x;
      if (mirrorV) y = -y;

      // uncenter the point and apply shift
      return [x + centerX + shiftX, y + centerY + shiftY];
    }

    function inverse(x, y) {
      // undo shift and center the point
      x -= centerX + shiftX;
      y -= centerY + shiftY;

      // undo mirroring
      if (mirrorV) y = -y;
      if (mirrorH) x = -x;

      // undo rotation
      if (angle !== 0) [x, y] = [x * cos + y * sin, -x * sin + y * cos];

      // undo scale
      if (scale !== 1) {
        x /= scale;
        y /= scale;
      }

      // uncenter the point
      return [x + centerX, y + centerY];
    }

    return [project, inverse];
  }
}
