import { Image } from '../../Image.js';
import type { Point } from '../../geometry/index.js';
import type { FastKeypoint } from '../keypoints/getFastKeypoints.js';
import type { Match } from '../matching/bruteForceMatch.js';

import type { DrawKeypointsOptions } from './drawKeypoints.js';
import { drawKeypoints } from './drawKeypoints.js';
import type { DrawMatchesOptions } from './drawMatches.js';
import { drawMatches } from './drawMatches.js';
import { scaleKeypoints } from './scaleKeypoints.js';

export const MontageDisposition = {
  HORIZONTAL: 'horizontal',
  VERTICAL: 'vertical',
} as const;
export type MontageDisposition =
  (typeof MontageDisposition)[keyof typeof MontageDisposition];

export interface MontageOptions {
  /**
   * Factor by which to scale the images.
   *  @default `1`
   */
  scale?: number;
  /**
   * How should the images be aligned: vertically or horizontally.
   * @default `'horizontal'`
   */
  disposition?: MontageDisposition;
}

export class Montage {
  /**
   * Scaled width of the first image.
   */
  public readonly sourceWidth: number;
  /**
   * Scaled height of the first image.
   */
  public readonly sourceHeight: number;
  /**
   * Scaled width of the second image.
   */
  public readonly destinationWidth: number;
  /**
   * Scaled height of the second image.
   */
  public readonly destinationHeight: number;
  /**
   * Origin of the destination / second image relative to top-left corner of the Montage.
   */
  public readonly destinationOrigin: Point;
  /**
   * Width of the Montage.
   */
  public readonly width: number;
  /**
   * Height of the Montage.
   */
  public readonly height: number;
  /**
   * Factor by which to scale the images are scaled in the montage.
   */
  public readonly scale: number;

  public readonly disposition: MontageDisposition;

  /**
   * Image of the Montage.
   */
  public image: Image;

  /**
   * Create a Montage of two images. The two images are placed side by side for comparison.
   * @param source - First image.
   * @param destination - Second image.
   * @param options  - Montage options.
   */
  public constructor(
    source: Image,
    destination: Image,
    options: MontageOptions = {},
  ) {
    const { scale = 1, disposition = 'horizontal' } = options;

    if (!Number.isInteger(scale)) {
      throw new TypeError('scale must be an integer');
    }

    this.scale = scale;
    this.disposition = disposition;

    this.sourceWidth = scale * source.width;
    this.destinationWidth = scale * destination.width;
    this.sourceHeight = scale * source.height;
    this.destinationHeight = scale * destination.height;

    if (disposition === 'horizontal') {
      this.destinationOrigin = { row: 0, column: this.sourceWidth };
      this.width = this.sourceWidth + this.destinationWidth;
      this.height = Math.max(this.sourceHeight, this.destinationHeight);
    } else if (disposition === 'vertical') {
      this.destinationOrigin = { row: this.sourceHeight, column: 0 };
      this.width = Math.max(this.sourceWidth, this.destinationWidth);
      this.height = this.sourceHeight + this.destinationHeight;
    } else {
      throw new RangeError(`invalid disposition type: ${disposition}`);
    }

    if (source.colorModel !== 'RGB') {
      source = source.convertColor('RGB');
    }
    if (destination.colorModel !== 'RGB') {
      destination = destination.convertColor('RGB');
    }

    const image = new Image(this.width, this.height);

    source
      .resize({ xFactor: scale, yFactor: scale })
      .copyTo(image, { out: image });
    destination.resize({ xFactor: scale, yFactor: scale }).copyTo(image, {
      out: image,
      origin: this.destinationOrigin,
    });

    this.image = image;
  }

  /**
   * Draw keypoints on the Montage.
   * @param keypoints - Keypoints to draw.
   * @param options - Draw keypoints options.
   */
  public drawKeypoints(
    keypoints: FastKeypoint[],
    options: DrawKeypointsOptions = {},
  ): void {
    const scaledKeypoints = scaleKeypoints(keypoints, this.scale);
    this.image = drawKeypoints(this.image, scaledKeypoints, options);
  }

  /**
   * Draw the matches between source and destination keypoints.
   * @param matches - Matches to draw.
   * @param sourceKeypoints - Source keypoints.
   * @param destinationKeypoints  - Destination keypoints.
   * @param options - Draw matches options.
   */
  public drawMatches(
    matches: Match[],
    sourceKeypoints: FastKeypoint[],
    destinationKeypoints: FastKeypoint[],
    options: DrawMatchesOptions = {},
  ): void {
    this.image = drawMatches(
      this,
      matches,
      sourceKeypoints,
      destinationKeypoints,
      options,
    );
  }
}
