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

import ej.annotation.Nullable;
import ej.basictool.ArrayTools;
import ej.mwt.Container;
import ej.mwt.Widget;
import ej.mwt.util.Size;

/**
 * Lays out any number of children by docking each child one by one on a side.
 * <p>
 * Each child is laid out individually on one of the sides of the remaining space, following the order in which they
 * have been added to the dock. If a widget is set at the center of the dock, it will take the final remaining space.
 * <p>
 * In a dock, all widgets docked on the left side and on the right side will have the height of the remaining space and
 * will have their optimal width. All widgets docked on the top side and on the bottom side will have the width of the
 * remaining space and will have their optimal height. The center widget will have the size of the remaining space.
 * <p>
 * Example of dock that contains (added in this order):
 * <ol>
 * <li>widget on the left,</li>
 * <li>widget on the top,</li>
 * <li>widget on the right,</li>
 * <li>widget on the bottom,</li>
 * <li>widget on the left,</li>
 * <li>widget on the bottom,</li>
 * <li>widget on the center.</li>
 * </ol>
 * <img src="doc-files/dock.png" alt="Dock.">
 */
public class Dock extends Container {

	private static class DockedWidget {

		private static final int POSITION_TOP = 1;
		private static final int POSITION_BOTTOM = 2;
		private static final int POSITION_LEFT = 3;
		private static final int POSITION_RIGHT = 4;

		private final Widget widget;
		private final int position;

		private DockedWidget(Widget widget, int position) {
			this.widget = widget;
			this.position = position;
		}

		private boolean isHorizontal() {
			return (this.position == POSITION_LEFT || this.position == POSITION_RIGHT);
		}
	}

	private DockedWidget[] dockedWidgets;
	@Nullable
	private Widget centerWidget;

	/**
	 * Creates a dock.
	 */
	public Dock() {
		this.dockedWidgets = new DockedWidget[0];
	}

	private void addChild(Widget widget, int position) {
		this.dockedWidgets = ArrayTools.add(this.dockedWidgets, new DockedWidget(widget, position));
		addChild(widget);
	}

	/**
	 * Adds a child which will be docked on the left side.
	 *
	 * @param child
	 *            the widget to add.
	 * @throws NullPointerException
	 *             if the given widget is <code>null</code>.
	 * @throws IllegalArgumentException
	 *             if the specified widget is already in a hierarchy (already contained in a container or desktop).
	 * @see Container#addChild(Widget)
	 */
	public void addChildOnLeft(Widget child) {
		addChild(child, DockedWidget.POSITION_LEFT);
	}

	/**
	 * Adds a child which will be docked on the right side.
	 *
	 * @param child
	 *            the widget to add.
	 * @throws NullPointerException
	 *             if the given widget is <code>null</code>.
	 * @throws IllegalArgumentException
	 *             if the specified widget is already in a hierarchy (already contained in a container or desktop).
	 * @see Container#addChild(Widget)
	 */
	public void addChildOnRight(Widget child) {
		addChild(child, DockedWidget.POSITION_RIGHT);
	}

	/**
	 * Adds a child which will be docked on the top side.
	 *
	 * @param child
	 *            the widget to add.
	 * @throws NullPointerException
	 *             if the given widget is <code>null</code>.
	 * @throws IllegalArgumentException
	 *             if the specified widget is already in a hierarchy (already contained in a container or desktop).
	 * @see Container#addChild(Widget)
	 */
	public void addChildOnTop(Widget child) {
		addChild(child, DockedWidget.POSITION_TOP);
	}

	/**
	 * Adds a child which will be docked on the bottom side.
	 *
	 * @param child
	 *            the widget to add.
	 * @throws NullPointerException
	 *             if the given widget is <code>null</code>.
	 * @throws IllegalArgumentException
	 *             if the specified widget is already in a hierarchy (already contained in a container or desktop).
	 * @see Container#addChild(Widget)
	 */
	public void addChildOnBottom(Widget child) {
		addChild(child, DockedWidget.POSITION_BOTTOM);
	}

	/**
	 * Sets the child which will be at the center.
	 *
	 * @param child
	 *            the widget to add.
	 * @throws NullPointerException
	 *             if the given widget is <code>null</code>.
	 * @throws IllegalArgumentException
	 *             if the specified widget is already in a hierarchy (already contained in a container or desktop).
	 * @see Container#addChild(Widget)
	 */
	public void setCenterChild(Widget child) {
		Widget centerWidget = this.centerWidget;
		if (centerWidget != null) {
			removeChild(centerWidget);
		}

		this.centerWidget = child;
		addChild(child);
	}

	/**
	 * Gets the child at the center of this dock.
	 *
	 * @return the center child, or <code>null</code> is it has not been set.
	 */
	@Nullable
	public Widget getCenterChild() {
		return this.centerWidget;
	}

	@Override
	public void removeChild(Widget child) {
		if (child == this.centerWidget) {
			this.centerWidget = null;
		} else {
			DockedWidget[] dockedWidgets = this.dockedWidgets;
			for (DockedWidget dockedWidget : dockedWidgets) {
				if (dockedWidget.widget == child) {
					this.dockedWidgets = ArrayTools.remove(dockedWidgets, dockedWidget);
					break;
				}
			}
		}
		super.removeChild(child);
	}

	@Override
	public void removeAllChildren() {
		super.removeAllChildren();
		this.dockedWidgets = new DockedWidget[0];
		this.centerWidget = null;
	}

	@Override
	protected void computeContentOptimalSize(Size size) {
		int widthHint = size.getWidth();
		int heightHint = size.getHeight();

		// the remaining size is used as size hint for children
		int remainingWidth = widthHint;
		int remainingHeight = heightHint;

		// compute optimal size of each docked child
		DockedWidget[] dockedWidgets = this.dockedWidgets;
		for (DockedWidget dockedWidget : dockedWidgets) {
			Widget widget = dockedWidget.widget;

			// compute child optimal size
			computeChildOptimalSize(widget, remainingWidth, remainingHeight);

			// update remaining size
			if (dockedWidget.isHorizontal()) {
				if (widthHint != Widget.NO_CONSTRAINT) {
					remainingWidth = Math.max(0, remainingWidth - widget.getWidth());
				}
			} else {
				if (heightHint != Widget.NO_CONSTRAINT) {
					remainingHeight = Math.max(0, remainingHeight - widget.getHeight());
				}
			}
		}

		// compute optimal size of container
		int totalWidth = 0;
		int totalHeight = 0;

		Widget center = this.centerWidget;
		if (center != null) {
			// compute optimal size of center widget
			computeChildOptimalSize(center, remainingWidth, remainingHeight);
			// add center widget size
			totalWidth += center.getWidth();
			totalHeight += center.getHeight();
		}

		// iterate from last added widget (closest to center) to first added widget (farthest from center)
		for (int i = dockedWidgets.length - 1; i >= 0; i--) {
			DockedWidget dockedWidget = dockedWidgets[i];
			Widget widget = dockedWidget.widget;
			int widgetWidth = widget.getWidth();
			int widgetHeight = widget.getHeight();

			if (dockedWidget.isHorizontal()) {
				totalWidth += widgetWidth;
				totalHeight = Math.max(totalHeight, widgetHeight);
			} else {
				totalWidth = Math.max(totalWidth, widgetWidth);
				totalHeight += widgetHeight;
			}
		}

		// set container optimal size
		size.setSize(totalWidth, totalHeight);
	}

	@Override
	protected void layOutChildren(int contentWidth, int contentHeight) {
		int x = 0;
		int y = 0;

		// lay out docked widgets
		for (DockedWidget dockedWidget : this.dockedWidgets) {
			Widget widget = dockedWidget.widget;
			int widgetWidth = widget.getWidth();
			int widgetHeight = widget.getHeight();

			switch (dockedWidget.position) {
			case DockedWidget.POSITION_LEFT:
				layOutChild(widget, x, y, widgetWidth, contentHeight);
				x += widgetWidth;
				contentWidth -= widgetWidth;
				break;
			case DockedWidget.POSITION_RIGHT:
				layOutChild(widget, x + contentWidth - widgetWidth, y, widgetWidth, contentHeight);
				contentWidth -= widgetWidth;
				break;
			case DockedWidget.POSITION_TOP:
				layOutChild(widget, x, y, contentWidth, widgetHeight);
				y += widgetHeight;
				contentHeight -= widgetHeight;
				break;
			case DockedWidget.POSITION_BOTTOM:
				layOutChild(widget, x, y + contentHeight - widgetHeight, contentWidth, widgetHeight);
				contentHeight -= widgetHeight;
				break;
			default:
				// Cannot occur.
			}
		}

		// lay out center widget
		Widget center = this.centerWidget;
		if (center != null) {
			layOutChild(center, x, y, contentWidth, contentHeight);
		}
	}
}
