/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

// Test the drag and zooming behavior

"use strict";

const CanvasUtils = require("resource://devtools/client/memory/components/tree-map/canvas-utils.js");
const DragZoom = require("resource://devtools/client/memory/components/tree-map/drag-zoom.js");

const TEST_URL = "data:text/html,<html><body></body></html>";
const PIXEL_SCROLL_MODE = 0;
const PIXEL_DELTA = 10;
const MAX_RAF_LOOP = 1000;

this.test = makeMemoryTest(TEST_URL, async function ({ tab, panel }) {
  const panelWin = panel.panelWin;
  const panelDoc = panelWin.document;
  const div = panelDoc.createElement("div");

  Object.assign(div.style, {
    width: "100px",
    height: "200px",
    position: "absolute",
    left: 0,
    top: 0,
  });

  const rafMock = createRAFMock();

  panelDoc.body.appendChild(div);

  const canvases = new CanvasUtils(div, 0);
  const dragZoom = new DragZoom(canvases.container, 0, rafMock.raf);
  const style = canvases.container.style;

  info("Check initial state of dragZoom");
  {
    is(dragZoom.zoom, 0, "Zooming starts at 0");
    is(dragZoom.smoothZoom, 0, "Smoothed zooming starts at 0");
    is(rafMock.timesCalled, 0, "No RAFs have been queued");
    is(
      style.transform,
      "translate(0px) scale(1)",
      "No transforms have been done."
    );

    canvases.container.dispatchEvent(
      new WheelEvent("wheel", {
        deltaY: -PIXEL_DELTA,
        deltaMode: PIXEL_SCROLL_MODE,
      })
    );

    is(
      style.transform,
      "translate(0px) scale(1.05)",
      "The div has been slightly scaled."
    );
    is(
      dragZoom.zoom,
      PIXEL_DELTA * dragZoom.ZOOM_SPEED,
      "The zoom was increased"
    );
    ok(
      floatEquality(dragZoom.smoothZoom, 0.05),
      "The smooth zoom is between the initial value and the target"
    );
    is(rafMock.timesCalled, 1, "A RAF has been queued");
  }

  info("RAF will eventually stop once the smooth values approach the target");
  {
    let i;
    let lastCallCount;
    for (i = 0; i < MAX_RAF_LOOP; i++) {
      if (lastCallCount === rafMock.timesCalled) {
        break;
      }
      lastCallCount = rafMock.timesCalled;
      rafMock.nextFrame();
    }
    is(
      style.transform,
      "translate(0px) scale(1.1)",
      "The scale has been fully applied"
    );
    is(
      dragZoom.zoom,
      dragZoom.smoothZoom,
      "The smooth and target zoom values match"
    );
    isnot(MAX_RAF_LOOP, i, "The RAF loop correctly stopped");
  }

  info("Dragging correctly translates the div");
  {
    div.dispatchEvent(
      new MouseEvent("mousemove", {
        clientX: 10,
        clientY: 10,
      })
    );
    div.dispatchEvent(new MouseEvent("mousedown"));
    div.dispatchEvent(
      new MouseEvent("mousemove", {
        clientX: 20,
        clientY: 20,
      })
    );
    div.dispatchEvent(new MouseEvent("mouseup"));

    is(
      style.transform,
      "translate(2.5px, 5px) scale(1.1)",
      "The style is correctly translated"
    );
    ok(
      floatEquality(dragZoom.translateX, 5),
      "Translate X moved by some pixel amount"
    );
    ok(
      floatEquality(dragZoom.translateY, 10),
      "Translate Y moved by some pixel amount"
    );
  }

  info("Zooming centers around the mouse");
  {
    canvases.container.dispatchEvent(
      new WheelEvent("wheel", {
        deltaY: -PIXEL_DELTA,
        deltaMode: PIXEL_SCROLL_MODE,
      })
    );
    // Run through the RAF loop to zoom in towards that value.
    let lastCallCount;
    for (let i = 0; i < MAX_RAF_LOOP; i++) {
      if (lastCallCount === rafMock.timesCalled) {
        break;
      }
      lastCallCount = rafMock.timesCalled;
      rafMock.nextFrame();
    }
    is(
      style.transform,
      "translate(8.18182px, 18.1818px) scale(1.2)",
      "Zooming affects the translation to keep the mouse centered"
    );
    ok(
      floatEquality(dragZoom.translateX, 8.181818181818185),
      "Translate X was affected by the mouse position"
    );
    ok(
      floatEquality(dragZoom.translateY, 18.18181818181817),
      "Translate Y was affected by the mouse position"
    );
    is(dragZoom.zoom, 0.2, "Zooming starts at 0");
  }

  dragZoom.destroy();

  info("Scroll isn't tracked after destruction");
  {
    const previousZoom = dragZoom.zoom;
    const previousSmoothZoom = dragZoom.smoothZoom;

    canvases.container.dispatchEvent(
      new WheelEvent("wheel", {
        deltaY: -PIXEL_DELTA,
        deltaMode: PIXEL_SCROLL_MODE,
      })
    );

    is(dragZoom.zoom, previousZoom, "The zoom stayed the same");
    is(
      dragZoom.smoothZoom,
      previousSmoothZoom,
      "The smooth zoom stayed the same"
    );
  }

  info("Translation isn't tracked after destruction");
  {
    const initialX = dragZoom.translateX;
    const initialY = dragZoom.translateY;

    div.dispatchEvent(new MouseEvent("mousedown"));
    div.dispatchEvent(new MouseEvent("mousemove"), {
      clientX: 40,
      clientY: 40,
    });
    div.dispatchEvent(new MouseEvent("mouseup"));
    is(dragZoom.translateX, initialX, "The translationX didn't change");
    is(dragZoom.translateY, initialY, "The translationY didn't change");
  }
  panelDoc.body.removeChild(div);
});
