// Code adapted from `xywh.js` by Thomas Steiner
// https://github.com/tomayac/xywh.js

export type MediaFragment = (
  | {
      mediaItem: HTMLImageElement;
      mediaType: "img";
    }
  | {
      mediaItem: HTMLVideoElement;
      mediaType: "video";
    }
) & {
  unit: string;
  x: number;
  y: number;
  w: number;
  h: number;
};

export function xywh(mediaItem: HTMLImageElement | HTMLVideoElement) {
  const source = mediaItem.src || mediaItem.currentSrc;
  // See http://www.w3.org/TR/media-frags/#naming-space
  const xywhRegEx =
    /[#&?]xywh=(pixel:|percent:)?([\d.]+),([\d.]+),([\d.]+),([\d.]+)/;
  const match = xywhRegEx.exec(source);
  if (match) {
    const mediaFragment = {
      mediaItem: mediaItem,
      mediaType: mediaItem.nodeName.toLowerCase(),
      unit: match[1] ? match[1] : "pixel:",
      x: parseFloat(match[2]),
      y: parseFloat(match[3]),
      w: parseFloat(match[4]),
      h: parseFloat(match[5]),
    } as MediaFragment;
    if (mediaFragment.mediaType === "img") {
      addImageLoadListener(mediaFragment);
    } else {
      addVideoLoadListener(mediaFragment);
    }
  }
}

/**
 * Applies the media fragment when the image has loaded.
 */
function addImageLoadListener(mediaFragment: MediaFragment) {
  const mediaItem = mediaFragment.mediaItem;
  // Prevent onload firing when the fragment loads; but still react when `src`
  // is changed programatically.
  let lastSrc: string;
  function onload() {
    if (mediaItem.src !== lastSrc) {
      // Required on reloads because of size calculations.
      applyFragment(mediaFragment);
      lastSrc = mediaItem.src;
    }
  }
  mediaItem.addEventListener("load", onload);
}

/**
 * Applies the media fragment when all metadata of the video have loaded. We
 * need the video's original width and height.
 */
function addVideoLoadListener(mediaFragment: MediaFragment) {
  mediaFragment.mediaItem.addEventListener("loadedmetadata", function () {
    applyFragment(mediaFragment);
  });
}

/**
 * Applies the spatial media fragment:
 *
 * For images, we replace the @src attribute with a transparent GIF data URL,
 * resize the image to match the fragment's dimensions, and set the image's
 * CSS background-image according to the fragment's x and y values.
 *
 * For videos, we wrap the video in a <div> wrapper of the fragment's
 * dimensions, set its CSS overflow value to "hidden", and apply a CSS3
 * 2D transformation according to the fragment's x and y values.
 */
function applyFragment(fragment: MediaFragment) {
  // Media item is a video
  if (fragment.mediaType === "video") {
    let x: string, y: string, w: string, h: string;
    const originalWidth = fragment.mediaItem.videoWidth;
    const originalHeight = fragment.mediaItem.videoHeight;
    // Unit is pixel:
    if (fragment.unit === "pixel:") {
      const scale = originalWidth / fragment.mediaItem.clientWidth;
      w = fragment.w * scale + "px";
      h = fragment.h * scale + "px";
      x = "-" + fragment.x * scale + "px";
      y = "-" + fragment.y * scale + "px";
      // Unit is percent:
    } else {
      w = (originalWidth * fragment.w) / 100 + "px";
      h = (originalHeight * fragment.h) / 100 + "px";
      x = "-" + (originalWidth * fragment.x) / 100 + "px";
      y = "-" + (originalHeight * fragment.y) / 100 + "px";
    }

    const wrapper = document.createElement("div");
    wrapper.style.cssText +=
      "overflow:hidden;" +
      "width:" +
      w +
      ";" +
      "height:" +
      h +
      ";" +
      "padding:0;" +
      "margin:0;" +
      "border-radius:0;" +
      "border:none;";
    fragment.mediaItem.style.cssText +=
      "transform:translate(" +
      x +
      "," +
      y +
      ");" +
      "-webkit-transform:translate(" +
      x +
      "," +
      y +
      ");";
    // Evil DOM operations
    fragment.mediaItem.parentNode.insertBefore(wrapper, fragment.mediaItem);
    wrapper.appendChild(fragment.mediaItem);

    // We need to manually trigger @autoplay, as DOM access seems to kill it
    if (fragment.mediaItem.hasAttribute("autoplay")) {
      fragment.mediaItem.play();
    }
    // Media item is an image
  } else {
    let x: number, y: number, w: number, h: number;
    if (fragment.unit === "pixel:") {
      x = fragment.x;
      y = fragment.y;
      w = fragment.w;
      h = fragment.h;
    } else {
      x = (fragment.x / 100) * fragment.mediaItem.naturalWidth;
      y = (fragment.y / 100) * fragment.mediaItem.naturalHeight;
      w = (fragment.w / 100) * fragment.mediaItem.naturalWidth;
      h = (fragment.h / 100) * fragment.mediaItem.naturalHeight;
    }
    const canvas = document.createElement("canvas");
    canvas.width = w;
    canvas.height = h;
    const context = canvas.getContext("2d");
    context.drawImage(fragment.mediaItem, x, y, w, h, 0, 0, w, h);
    fragment.mediaItem.src = canvas.toDataURL();
  }
}
