/*
 * Copyright 2015-2024 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 ej.mwt.style.background;

import ej.annotation.Nullable;
import ej.microui.display.GraphicsContext;
import ej.microui.display.Image;
import ej.microui.display.Painter;
import ej.mwt.util.Alignment;

/**
 * Draws a repeated image on the background.
 */
public class TiledImageBackground implements Background {

	private final Image image;
	private final byte horizontalAlignment;
	private final byte verticalAlignment;
	private final boolean horizontalRepeat;
	private final boolean verticalRepeat;

	/**
	 * Creates a tiled image background aligned on the top-left corner and repeated horizontally and vertically.
	 *
	 * @param image
	 *            the background image.
	 */
	public TiledImageBackground(Image image) {
		this(image, Alignment.LEFT, Alignment.TOP, true, true);
	}

	/**
	 * Creates a tiled image background specifying its attributes.
	 *
	 * @param image
	 *            the background image.
	 * @param horizontalAlignment
	 *            the horizontal alignment.
	 * @param verticalAlignment
	 *            the vertical alignment.
	 * @param horizontalRepeat
	 *            <code>true</code> if the image is repeated horizontally, <code>false</code> otherwise.
	 * @param verticalRepeat
	 *            <code>true</code> if the image is repeated vertically, <code>false</code> otherwise.
	 * @throws IllegalArgumentException
	 *             if the horizontal or vertical alignment is not valid.
	 * @see Alignment#validateHorizontalAlignment(int)
	 * @see Alignment#validateVerticalAlignment(int)
	 */
	public TiledImageBackground(Image image, int horizontalAlignment, int verticalAlignment, boolean horizontalRepeat,
			boolean verticalRepeat) {
		Alignment.validateHorizontalAlignment(horizontalAlignment);
		Alignment.validateVerticalAlignment(verticalAlignment);
		this.image = image;
		this.horizontalAlignment = (byte) horizontalAlignment;
		this.verticalAlignment = (byte) verticalAlignment;
		this.horizontalRepeat = horizontalRepeat;
		this.verticalRepeat = verticalRepeat;
	}

	@Override
	public boolean isTransparent(int width, int height) {
		Image image = this.image;
		return (image.isTransparent() || checkTransparent(this.horizontalRepeat, image.getWidth(), width)
				|| checkTransparent(this.verticalRepeat, image.getHeight(), height));
	}

	private boolean checkTransparent(boolean repeat, int imageSize, int size) {
		return !repeat && imageSize < size;
	}

	@Override
	public void apply(GraphicsContext g, int width, int height) {
		Image image = this.image;
		int imageWidth = image.getWidth();
		int imageHeight = image.getHeight();
		boolean horizontalRepeat = this.horizontalRepeat;
		boolean verticalRepeat = this.verticalRepeat;
		int horizontalAlignment = this.horizontalAlignment;
		int verticalAlignment = this.verticalAlignment;

		// compute number of repetitions
		int hrepeat = getRepeatCount(horizontalRepeat, width, imageWidth);
		int vrepeat = getRepeatCount(verticalRepeat, height, imageHeight);

		// compute position of first repetition
		int xStart;
		int yStart;
		if (horizontalAlignment == Alignment.HCENTER) {
			if (horizontalRepeat) {
				hrepeat++; // one more because both sides can be cut
			}
			xStart = ((width - imageWidth) >> 1) - imageWidth * (hrepeat >> 1);
		} else if (horizontalAlignment == Alignment.RIGHT) {
			xStart = width - imageWidth * hrepeat;
		} else { // left or undefined
			xStart = 0;
		}
		if (verticalAlignment == Alignment.VCENTER) {
			if (verticalRepeat) {
				vrepeat++; // one more because both sides can be cut
			}
			yStart = ((height - imageHeight) >> 1) - imageHeight * (vrepeat >> 1);
		} else if (verticalAlignment == Alignment.BOTTOM) {
			yStart = height - imageHeight * vrepeat;
		} else { // top or undefined
			yStart = 0;
		}

		// draw all image repetitions
		int currentX = xStart;
		for (int i = 0; i < hrepeat; i++) {
			int currentY = yStart;
			for (int j = 0; j < vrepeat; j++) {
				Painter.drawImage(g, image, currentX, currentY);
				currentY += imageHeight;
			}
			currentX += imageWidth;
		}

		// remove background color
		g.removeBackgroundColor();
	}

	@Override
	public boolean equals(@Nullable Object obj) {
		if (obj != null && getClass() == obj.getClass()) {
			TiledImageBackground background = (TiledImageBackground) obj;
			return this.image == background.image && this.horizontalAlignment == background.horizontalAlignment
					&& this.verticalAlignment == background.verticalAlignment
					&& this.horizontalRepeat == background.horizontalRepeat
					&& this.verticalRepeat == background.verticalRepeat;
		}
		return false;
	}

	@Override
	public int hashCode() {
		// don't call this.image.hashCode() to avoid embedding the hashCode() method of every class (M0081MEJA-1240)
		return this.horizontalAlignment * this.verticalAlignment * (this.horizontalRepeat ? -3 : 3)
				* (this.verticalRepeat ? -5 : 5);
	}

	private static int getRepeatCount(boolean repeat, int size, int imageSize) {
		if (repeat) {
			return 1 + (size - 1) / imageSize;
		} else {
			return 1;
		}
	}
}
