/*
 * Copyright 2015-2020 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.widget.container;

import ej.mwt.Container;
import ej.mwt.Widget;
import ej.mwt.util.Size;

/**
 * Lays out any number of children in a grid.
 * <p>
 * All the children are laid out in a grid, which has a fixed number of columns if the grid is horizontal, or a fixed
 * number of rows if the grid is vertical.
 * <p>
 * In a grid, all children have the same width and the same height, regardless of their optimal size.
 * <p>
 * Horizontal:
 * <p>
 * <img src="doc-files/grid_horizontal.png" alt="Horizontal grid.">
 * <p>
 * Vertical:
 * <p>
 * <img src="doc-files/grid_vertical.png" alt="Vertical grid.">
 */
public class Grid extends Container {

	private boolean orientation;
	private int count;

	/**
	 * Creates a grid specifying its orientation and the number of widgets per row/column (depending on the orientation
	 * chosen via the horizontal parameter).
	 *
	 * @param orientation
	 *            the orientation of the grid (see {@link LayoutOrientation}).
	 * @param count
	 *            the number of widgets per row/column to set.
	 * @throws IllegalArgumentException
	 *             if the count is negative or zero.
	 */
	public Grid(boolean orientation, int count) {
		if (count <= 0) {
			throw new IllegalArgumentException();
		}

		this.orientation = orientation;
		this.count = count;
	}

	/**
	 * Sets the orientation of this grid.
	 *
	 * @param orientation
	 *            the orientation to set (see {@link LayoutOrientation}).
	 */
	public void setOrientation(boolean orientation) {
		this.orientation = orientation;
	}

	/**
	 * Gets the orientation of this grid.
	 *
	 * @return the orientation of this grid (see {@link LayoutOrientation}).
	 */
	public boolean getOrientation() {
		return this.orientation;
	}

	/**
	 * Sets the number of widgets per row/column (depending on the grid orientation).
	 *
	 * @param count
	 *            the number of widgets per row/column to set.
	 * @throws IllegalArgumentException
	 *             if the count is negative or zero.
	 */
	public void setCount(int count) {
		if (count <= 0) {
			throw new IllegalArgumentException();
		}

		this.count = count;
	}

	/**
	 * Gets the number of widgets per row/column (depending on the grid orientation).
	 *
	 * @return the number of widgets per row/column.
	 */
	public int getCount() {
		return this.count;
	}

	/**
	 * Gets the widget at the specified column and row.
	 *
	 * @param column
	 *            the column of the widget to return.
	 * @param row
	 *            the row of the widget to return.
	 * @return the widget at the specified column and row.
	 * @throws IndexOutOfBoundsException
	 *             if the cell referenced by the given coordinates is out of range.
	 */
	public Widget getCellChild(int column, int row) {
		int index;
		if (this.orientation == LayoutOrientation.HORIZONTAL) {
			index = row * this.count + column;
		} else {
			index = column * this.count + row;
		}

		if (index < 0 || index >= getChildrenCount()) {
			throw new IndexOutOfBoundsException();
		}

		return getChild(index);
	}

	@Override
	public void addChild(Widget child) {
		super.addChild(child);
	}

	@Override
	public void removeChild(Widget child) {
		super.removeChild(child);
	}

	@Override
	public void insertChild(Widget child, int index) {
		super.insertChild(child, index);
	}

	@Override
	public void replaceChild(int index, Widget child) {
		super.replaceChild(index, child);
	}

	@Override
	public void removeAllChildren() {
		super.removeAllChildren();
	}

	@Override
	protected void computeContentOptimalSize(Size size) {
		// handle case with no widget
		int childrenCount = getChildrenCount();
		if (childrenCount == 0) {
			size.setSize(0, 0);
			return;
		}

		// compute number of columns and rows
		boolean isHorizontal = (this.orientation == LayoutOrientation.HORIZONTAL);
		int mainCount = this.count;
		int otherCount = getOtherCount(childrenCount, mainCount);
		int columns = (isHorizontal ? mainCount : otherCount);
		int rows = (isHorizontal ? otherCount : mainCount);

		// compute size hint for children
		int widthHint = size.getWidth();
		int heightHint = size.getHeight();
		int childWidthHint = (widthHint == Widget.NO_CONSTRAINT ? Widget.NO_CONSTRAINT : widthHint / columns);
		int childHeightHint = (heightHint == Widget.NO_CONSTRAINT ? Widget.NO_CONSTRAINT : heightHint / rows);

		// compute max cell size
		int maxCellWidth = 0;
		int maxCellHeight = 0;

		for (Widget widget : getChildren()) {
			assert (widget != null);

			// compute child optimal size
			computeChildOptimalSize(widget, childWidthHint, childHeightHint);

			// update max cell size
			maxCellWidth = Math.max(maxCellWidth, widget.getWidth());
			maxCellHeight = Math.max(maxCellHeight, widget.getHeight());
		}

		// set container optimal size
		size.setSize(maxCellWidth * columns, maxCellHeight * rows);
	}

	@Override
	protected void layOutChildren(int contentWidth, int contentHeight) {
		// handle case with no widget
		int childrenCount = getChildrenCount();
		if (childrenCount == 0) {
			return;
		}

		boolean isHorizontal = (this.orientation == LayoutOrientation.HORIZONTAL);
		int mainContentLength = (isHorizontal ? contentWidth : contentHeight);
		int otherContentLength = (isHorizontal ? contentHeight : contentWidth);

		// compute number of cells on both axis
		int mainCount = this.count;
		int otherCount = getOtherCount(childrenCount, mainCount);

		// lay out each child
		Widget[] children = getChildren();
		for (int i = 0; i < children.length; i++) {
			Widget child = children[i];
			assert (child != null);

			// compute position on both axis
			int otherPosition = i / mainCount;
			int mainPosition = i - otherPosition * mainCount;

			// compute start and end on main axis
			int mainStart = mainPosition * mainContentLength / mainCount;
			int mainEnd = (mainPosition + 1) * mainContentLength / mainCount;

			// compute start and end on other axis
			int otherStart = otherPosition * otherContentLength / otherCount;
			int otherEnd = (otherPosition + 1) * otherContentLength / otherCount;

			if (isHorizontal) {
				layOutChild(child, mainStart, otherStart, mainEnd - mainStart, otherEnd - otherStart);
			} else {
				layOutChild(child, otherStart, mainStart, otherEnd - otherStart, mainEnd - mainStart);
			}
		}
	}

	private static int getOtherCount(int totalCount, int mainCount) {
		return (totalCount + mainCount - 1) / mainCount;
	}
}
