/*
 * Copyright 2022-2023 MicroEJ Corp. All rights reserved.
 * This library is provided in source code for use, modification and test, subject to license terms.
 * Any modification of the source code will break MicroEJ Corp. warranties on the whole library.
 */
package com.microej.painter;

import ej.microui.display.GraphicsContext;
import ej.microui.display.Image;
import ej.microvg.BlendMode;
import ej.microvg.Matrix;

/**
 * The <code>TransformedImagePainter</code> class provides static methods to draw an image with a transformation defined
 * by a {@link Matrix}.
 * <p>
 * In all the provided drawing APIs, the image pixels color is multiplied with the graphics context's color.
 * <p>
 * The drawings can be performed either using the bilinear algorithm or the nearest neighbor algorithm. The bilinear
 * algorithm produces a prettier rendering but takes more time.
 */
public class TransformedImagePainter {

	private TransformedImagePainter() {
		// private constructor
	}

	/**
	 * Draws the given image with a transformation defined by the given matrix.
	 * <p>
	 * Equivalent to calling {@link #drawImage(GraphicsContext, Image, Matrix, boolean, int, BlendMode)} with
	 * {@link GraphicsContext#OPAQUE} as alpha and {@link BlendMode#SRC_OVER} as blend mode.
	 *
	 * @param gc
	 *            the graphics context to use.
	 * @param image
	 *            the image to render.
	 * @param matrix
	 *            the transformation matrix to apply.
	 * @param bilinear
	 *            whether to use the bilinear algorithm or the nearest neighbor algorithm.
	 */
	public static void drawImage(GraphicsContext gc, Image image, Matrix matrix, boolean bilinear) {
		drawImage(gc, image, 0, 0, image.getWidth(), image.getHeight(), matrix, bilinear,
				GraphicsContext.OPAQUE, BlendMode.SRC_OVER);
	}

	/**
	 * Draws the given image with a transformation defined by the given matrix.
	 * <p>
	 * In addition with {@link #drawImage(GraphicsContext, Image, Matrix, boolean)}, this method allows to
	 * specify the global opacity value to apply during the image rendering. The blend mode specifies the algorithm to
	 * use when blending the pixels of the source and destination.
	 *
	 * @param gc
	 *            the graphics context to use.
	 * @param image
	 *            the image to render.
	 * @param matrix
	 *            the transformation matrix to apply.
	 * @param bilinear
	 *            whether to use the bilinear algorithm or the nearest neighbor algorithm.
	 * @param alpha
	 *            the global opacity rendering value.
	 * @param blendMode
	 *            the blend mode.
	 * @throws IllegalArgumentException
	 *             if the given alpha is not a value between {@link GraphicsContext#TRANSPARENT} and
	 *             {@link GraphicsContext#OPAQUE}.
	 */
	public static void drawImage(GraphicsContext gc, Image image, Matrix matrix, boolean bilinear, int alpha,
			BlendMode blendMode) {
		checkAlpha(alpha);

		drawImage(gc, image, 0, 0, image.getWidth(), image.getHeight(), matrix, bilinear, alpha, blendMode);
	}

	/**
	 * Draws a region of the given image with a transformation defined by the given matrix.
	 *
	 * @param gc
	 *            the graphics context to use.
	 * @param image
	 *            the image to render.
	 * @param regionX
	 *            the x coordinate of the region in the image.
	 * @param regionY
	 *            the y coordinate of the region in the image.
	 * @param regionWidth
	 *            the width of the region.
	 * @param regionHeight
	 *            the height of the region.
	 * @param matrix
	 *            the transformation matrix to apply.
	 * @param bilinear
	 *            whether to use the bilinear algorithm or the nearest neighbor algorithm.
	 * @param alpha
	 *            the global opacity rendering value.
	 * @param blendMode
	 *            the blend mode.
	 * @throws IllegalArgumentException
	 *             if the given alpha is not a value between {@link GraphicsContext#TRANSPARENT} and
	 *             {@link GraphicsContext#OPAQUE}.
	 */
	public static void drawImageRegion(GraphicsContext gc, Image image, int regionX, int regionY, int regionWidth,
			int regionHeight, Matrix matrix, boolean bilinear, int alpha, BlendMode blendMode) {
		checkAlpha(alpha);

		drawImage(gc, image, regionX, regionY, regionWidth, regionHeight, matrix, bilinear, alpha,
				blendMode);
	}

	private static void drawImage(GraphicsContext gc, Image image, int regionX, int regionY, int regionWidth,
			int regionHeight, Matrix matrix, boolean bilinear, int alpha, BlendMode blendMode) {
		TransformedImagePainterNatives.drawImage(gc.getSNIContext(), image.getSNIContext(), regionX, regionY,
				regionWidth, regionHeight, gc.getTranslationX(), gc.getTranslationY(), matrix.getSNIContext(), bilinear,
				alpha, blendMode.ordinal());
	}

	private static void checkAlpha(int alpha) {
		if (alpha < GraphicsContext.TRANSPARENT || alpha > GraphicsContext.OPAQUE) {
			throw new IllegalArgumentException();
		}
	}
}
