/* 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/. */

this.thumbnailGenerator = (function () {
  let exports = {}; // This is used in webextension/background/takeshot.js,
  // server/src/pages/shot/controller.js, and
  // server/scr/pages/shotindex/view.js. It is used in a browser
  // environment.

  // Resize down 1/2 at a time produces better image quality.
  // Not quite as good as using a third-party filter (which will be
  // slower), but good enough.
  const maxResizeScaleFactor = 0.5;

  // The shot will be scaled or cropped down to 210px on x, and cropped or
  // scaled down to a maximum of 280px on y.
  // x: 210
  // y: <= 280
  const maxThumbnailWidth = 210;
  const maxThumbnailHeight = 280;

  /**
   * @param {int} imageHeight Height in pixels of the original image.
   * @param {int} imageWidth Width in pixels of the original image.
   * @returns {width, height, scaledX, scaledY}
   */
  function getThumbnailDimensions(imageWidth, imageHeight) {
    const displayAspectRatio = 3 / 4;
    const imageAspectRatio = imageWidth / imageHeight;
    let thumbnailImageWidth, thumbnailImageHeight;
    let scaledX, scaledY;

    if (imageAspectRatio > displayAspectRatio) {
      // "Landscape" mode
      // Scale on y, crop on x
      const yScaleFactor =
        imageHeight > maxThumbnailHeight
          ? maxThumbnailHeight / imageHeight
          : 1.0;
      thumbnailImageHeight = scaledY = Math.round(imageHeight * yScaleFactor);
      scaledX = Math.round(imageWidth * yScaleFactor);
      thumbnailImageWidth = Math.min(scaledX, maxThumbnailWidth);
    } else {
      // "Portrait" mode
      // Scale on x, crop on y
      const xScaleFactor =
        imageWidth > maxThumbnailWidth ? maxThumbnailWidth / imageWidth : 1.0;
      thumbnailImageWidth = scaledX = Math.round(imageWidth * xScaleFactor);
      scaledY = Math.round(imageHeight * xScaleFactor);
      // The CSS could widen the image, in which case we crop more off of y.
      thumbnailImageHeight = Math.min(
        scaledY,
        maxThumbnailHeight,
        maxThumbnailHeight / (maxThumbnailWidth / imageWidth)
      );
    }

    return {
      width: thumbnailImageWidth,
      height: thumbnailImageHeight,
      scaledX,
      scaledY,
    };
  }

  /**
   * @param {dataUrl} String Data URL of the original image.
   * @param {int} imageHeight Height in pixels of the original image.
   * @param {int} imageWidth Width in pixels of the original image.
   * @param {String} urlOrBlob 'blob' for a blob, otherwise data url.
   * @returns A promise that resolves to the data URL or blob of the thumbnail image, or null.
   */
  function createThumbnail(dataUrl, imageWidth, imageHeight, urlOrBlob) {
    // There's cost associated with generating, transmitting, and storing
    // thumbnails, so we'll opt out if the image size is below a certain threshold
    const thumbnailThresholdFactor = 1.2;
    const thumbnailWidthThreshold =
      maxThumbnailWidth * thumbnailThresholdFactor;
    const thumbnailHeightThreshold =
      maxThumbnailHeight * thumbnailThresholdFactor;

    if (
      imageWidth <= thumbnailWidthThreshold &&
      imageHeight <= thumbnailHeightThreshold
    ) {
      // Do not create a thumbnail.
      return Promise.resolve(null);
    }

    const thumbnailDimensions = getThumbnailDimensions(imageWidth, imageHeight);

    return new Promise((resolve, reject) => {
      const thumbnailImage = new Image();
      let srcWidth = imageWidth;
      let srcHeight = imageHeight;
      let destWidth, destHeight;

      thumbnailImage.onload = function () {
        destWidth = Math.round(srcWidth * maxResizeScaleFactor);
        destHeight = Math.round(srcHeight * maxResizeScaleFactor);
        if (
          destWidth <= thumbnailDimensions.scaledX ||
          destHeight <= thumbnailDimensions.scaledY
        ) {
          srcWidth = Math.round(
            srcWidth * (thumbnailDimensions.width / thumbnailDimensions.scaledX)
          );
          srcHeight = Math.round(
            srcHeight *
              (thumbnailDimensions.height / thumbnailDimensions.scaledY)
          );
          destWidth = thumbnailDimensions.width;
          destHeight = thumbnailDimensions.height;
        }

        const thumbnailCanvas = document.createElement("canvas");
        thumbnailCanvas.width = destWidth;
        thumbnailCanvas.height = destHeight;
        const ctx = thumbnailCanvas.getContext("2d");
        ctx.imageSmoothingEnabled = false;

        ctx.drawImage(
          thumbnailImage,
          0,
          0,
          srcWidth,
          srcHeight,
          0,
          0,
          destWidth,
          destHeight
        );

        if (
          thumbnailCanvas.width <= thumbnailDimensions.width ||
          thumbnailCanvas.height <= thumbnailDimensions.height
        ) {
          if (urlOrBlob === "blob") {
            thumbnailCanvas.toBlob(blob => {
              resolve(blob);
            });
          } else {
            resolve(thumbnailCanvas.toDataURL("image/png"));
          }
          return;
        }

        srcWidth = destWidth;
        srcHeight = destHeight;
        thumbnailImage.src = thumbnailCanvas.toDataURL();
      };
      thumbnailImage.src = dataUrl;
    });
  }

  function createThumbnailUrl(shot) {
    const image = shot.getClip(shot.clipNames()[0]).image;
    if (!image.url) {
      return Promise.resolve(null);
    }
    return createThumbnail(
      image.url,
      image.dimensions.x,
      image.dimensions.y,
      "dataurl"
    );
  }

  function createThumbnailBlobFromPromise(shot, blobToUrlPromise) {
    return blobToUrlPromise.then(dataUrl => {
      const image = shot.getClip(shot.clipNames()[0]).image;
      return createThumbnail(
        dataUrl,
        image.dimensions.x,
        image.dimensions.y,
        "blob"
      );
    });
  }

  if (typeof exports !== "undefined") {
    exports.getThumbnailDimensions = getThumbnailDimensions;
    exports.createThumbnailUrl = createThumbnailUrl;
    exports.createThumbnailBlobFromPromise = createThumbnailBlobFromPromise;
  }

  return exports;
})();
null;
