import { EXIFOrientation, orient } from "../../../util";
import { LayerDrawer } from "./Layer";

export type ImageLayer = {
  type: "IMAGE";
  offsetX: number;
  offsetY: number;
  sizeX: number;
  sizeY: number;
  src: string;
  cornerCutout?: number;
  rotation: EXIFOrientation;
  zoomFactor?: number;
};

export const drawImage: LayerDrawer<ImageLayer> = async function drawImage(
  ctx,
  { src, sizeX, sizeY, offsetX, offsetY, rotation, cornerCutout, zoomFactor },
  width,
  height
) {
  const img = new Image();
  img.crossOrigin = "anonymous";
  img.src = src;
  // Wait for the image to be loaded so it can be copied to the canvas.
  // If it isn't loaded an empty image would be copied.
  await new Promise(resolve => img.addEventListener("load", resolve));

  ctx.save();

  orient(ctx, rotation, Math.max(width, height));

  const refSrcOffset = getOffsetForAspectRatio(
    img.width,
    img.height,
    sizeX / sizeY
  );
  const refSrcCropArea = {
    width: img.width - refSrcOffset.x * 2,
    height: img.height - refSrcOffset.y * 2
  };
  const zoomFactorSafe = zoomFactor || 1;
  const zoomedSrcCropArea = {
    width: Math.min(refSrcCropArea.width / zoomFactorSafe, img.width),
    height: Math.min(refSrcCropArea.height / zoomFactorSafe, img.height)
  };
  const zoomedSrcOffset = {
    x: (img.width - zoomedSrcCropArea.width) / 2,
    y: (img.height - zoomedSrcCropArea.height) / 2
  };
  const destAspectRatio = zoomedSrcCropArea.width / zoomedSrcCropArea.height;
  const destArea = {
    width: Math.min(sizeX, sizeY * destAspectRatio),
    height: Math.min(sizeY, sizeX / destAspectRatio)
  };

  drawImageWithCornerCutout(
    ctx,
    img,
    zoomedSrcOffset.x,
    zoomedSrcOffset.y,
    zoomedSrcCropArea.width,
    zoomedSrcCropArea.height,
    offsetX + (sizeX - destArea.width) / 2,
    offsetY + (sizeY - destArea.height) / 2,
    destArea.width,
    destArea.height,
    cornerCutout
  );

  ctx.restore();
};

function ctxDrawImage(
  ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
  img: CanvasImageSource,
  sx: number,
  sy: number,
  sw: number,
  sh: number,
  dx: number,
  dy: number,
  dw: number,
  dh: number
) {
  if (sw === 0 || sh === 0 || dh === 0 || dw === 0) return;

  ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
}

function drawUpperPartOfImageWithCornerCutout(
  ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
  img: CanvasImageSource,
  sx: number,
  sy: number,
  sw: number,
  sh: number,
  dx: number,
  dy: number,
  dw: number,
  dh: number,
  cornerCutout: number
) {
  const scaleFactorWidth = sw / dw;
  const scaleFactorHeight = sh / dh;

  ctxDrawImage(
    ctx,
    img,
    sx + scaleFactorWidth * cornerCutout,
    sy,
    sw - scaleFactorWidth * cornerCutout * 2,
    scaleFactorHeight * cornerCutout,
    dx + cornerCutout,
    dy,
    dw - cornerCutout * 2,
    cornerCutout
  );
}

function drawMiddlePartOfImageWithCornerCutout(
  ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
  img: CanvasImageSource,
  sx: number,
  sy: number,
  sw: number,
  sh: number,
  dx: number,
  dy: number,
  dw: number,
  dh: number,
  cornerCutout: number
) {
  const scaleFactorHeight = sh / dh;

  ctxDrawImage(
    ctx,
    img,
    sx,
    sy + cornerCutout * scaleFactorHeight,
    sw,
    sh - cornerCutout * scaleFactorHeight * 2,
    dx,
    dy + cornerCutout,
    dw,
    dh - cornerCutout * 2
  );
}

function drawLowerPartOfImageWithCornerCutout(
  ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
  img: CanvasImageSource,
  sx: number,
  sy: number,
  sw: number,
  sh: number,
  dx: number,
  dy: number,
  dw: number,
  dh: number,
  cornerCutout: number
) {
  const scaleFactorWidth = sw / dw;
  const scaleFactorHeight = sh / dh;

  ctxDrawImage(
    ctx,
    img,
    sx + scaleFactorWidth * cornerCutout,
    sy + sh - scaleFactorHeight * cornerCutout,
    sw - scaleFactorWidth * cornerCutout * 2,
    scaleFactorHeight * cornerCutout,
    dx + cornerCutout,
    dy + dh - cornerCutout,
    dw - cornerCutout * 2,
    cornerCutout
  );
}

/**
 * Draws the image onto the canvas while cutting out cornerCutout pixels from each of the corners. Results in an image like:
 *
 *  ##
 * ####
 * ####
 *  ##
 */
function drawImageWithCornerCutout(
  ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
  img: CanvasImageSource,
  sx: number,
  sy: number,
  sw: number,
  sh: number,
  dx: number,
  dy: number,
  dw: number,
  dh: number,
  cornerCutout: number = 0
) {
  drawMiddlePartOfImageWithCornerCutout(
    ctx,
    img,
    sx,
    sy,
    sw,
    sh,
    dx,
    dy,
    dw,
    dh,
    cornerCutout
  );
  drawUpperPartOfImageWithCornerCutout(
    ctx,
    img,
    sx,
    sy,
    sw,
    sh,
    dx,
    dy,
    dw,
    dh,
    cornerCutout
  );
  drawLowerPartOfImageWithCornerCutout(
    ctx,
    img,
    sx,
    sy,
    sw,
    sh,
    dx,
    dy,
    dw,
    dh,
    cornerCutout
  );
}

/**
 * Calculate the offset on the src image in pixels on the x axis so it can be cut to an image of the
 * target aspect ratio with as little cutoff as possible.
 *
 * @param srcWidth
 * @param srcHeight
 * @param targetAspectRatio width / height
 */
function getOffsetForAspectRatioX(
  srcWidth: number,
  srcHeight: number,
  targetAspectRatio: number
) {
  if (srcWidth > srcHeight * targetAspectRatio) {
    if (srcWidth > srcHeight) {
      return (srcWidth - srcHeight * targetAspectRatio) / 2;
    } else {
      return (srcWidth / targetAspectRatio - srcHeight) / targetAspectRatio / 2;
    }
  }

  return 0;
}

/**
 * Calculate the offset on the src image in pixels on the y axis so it can be cut to an image of the
 * target aspect ratio with as little cutoff as possible.
 *
 * @param srcWidth
 * @param srcHeight
 * @param targetAspectRatio width / height
 */
function getOffsetForAspectRatioY(
  srcWidth: number,
  srcHeight: number,
  targetAspectRatio: number
) {
  if (srcWidth < srcHeight * targetAspectRatio) {
    if (srcWidth > srcHeight) {
      return (
        ((targetAspectRatio * srcHeight - srcWidth) * targetAspectRatio) / 2
      );
    } else {
      return (srcHeight - srcWidth * (1 / targetAspectRatio)) / 2;
    }
  }

  return 0;
}

/**
 * Calculate the offset on the src image in pixels so it can be cut to an image of the
 * target aspect ratio with as little cutoff as possible.
 *
 * @param srcWidth
 * @param srcHeight
 * @param targetAspectRatio width / height
 */
function getOffsetForAspectRatio(
  srcWidth: number,
  srcHeight: number,
  targetAspectRatio: number
) {
  return {
    x: getOffsetForAspectRatioX(srcWidth, srcHeight, targetAspectRatio),
    y: getOffsetForAspectRatioY(srcWidth, srcHeight, targetAspectRatio)
  };
}
