/*
 * Copyright 2015-2021 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.mwt.Container;
import ej.mwt.Widget;
import ej.mwt.util.Size;

/**
 * Lays out three children vertically or horizontally.
 * <p>
 * The three children are laid out on the same horizontal or vertical line, one at the center and the two others on each
 * side.
 * <p>
 * In a horizontal simple dock, the first and last widgets will have the height of the available space and will have
 * their optimal width. In a vertical simple dock, the first and last widgets will have the width of the available space
 * and will have their optimal height. Regardless of the orientation of the simple dock, the center widget will have the
 * size of the remaining space.
 * <p>
 * Horizontal:
 * <p>
 * <img src="doc-files/simpledock_horizontal.png" alt="Horizontal simple dock.">
 * <p>
 * Vertical:
 * <p>
 * <img src="doc-files/simpledock_vertical.png" alt="Vertical simple dock.">
 */
public class SimpleDock extends Container {

	private boolean orientation;
	@Nullable
	private Widget first;
	@Nullable
	private Widget center;
	@Nullable
	private Widget last;

	/**
	 * Creates a simple dock specifying its orientation.
	 *
	 * @param orientation
	 *            the orientation of the simple dock (see {@link LayoutOrientation}).
	 */
	public SimpleDock(boolean orientation) {
		this.orientation = orientation;
	}

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

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

	/**
	 * Sets the first widget of this simple dock. If this simple dock is horizontal, the first widget is on the left
	 * side, otherwise it is 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 #addChild(Widget)
	 */
	public void setFirstChild(Widget child) {
		Widget first = this.first;
		if (first != null) {
			removeChild(first);
		}

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

	/**
	 * Gets the first widget of this simple dock.
	 *
	 * @return the first widget of this simple dock, or <code>null</code> is it has not been set.
	 */
	@Nullable
	public Widget getFirstChild() {
		return this.first;
	}

	/**
	 * Sets the last widget of this simple dock. If this simple dock is horizontal, the last widget is on the right
	 * side, otherwise it is 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 #addChild(Widget)
	 */
	public void setLastChild(Widget child) {
		Widget last = this.last;
		if (last != null) {
			removeChild(last);
		}

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

	/**
	 * Gets the last widget of this simple dock.
	 *
	 * @return the last widget of this simple dock, or <code>null</code> is it has not been set.
	 */
	@Nullable
	public Widget getLastChild() {
		return this.last;
	}

	/**
	 * Sets the center widget of this simple dock.
	 *
	 * @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 #addChild(Widget)
	 */
	public void setCenterChild(Widget child) {
		Widget center = this.center;
		if (center != null) {
			removeChild(center);
		}

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

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

	@Override
	public void removeChild(Widget widget) {
		if (widget == this.first) {
			this.first = null;
		} else if (widget == this.last) {
			this.last = null;
		} else if (widget == this.center) {
			this.center = null;
		}
		super.removeChild(widget);
	}

	@Override
	public void removeAllChildren() {
		super.removeAllChildren();
		this.first = null;
		this.last = null;
		this.center = null;
	}

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

		boolean isHorizontal = isHorizontal();

		int widthHint = size.getWidth();
		int heightHint = size.getHeight();

		// compute optimal size of first widget
		Widget first = this.first;
		int firstOptimalWidth;
		int firstOptimalHeight;
		if (first != null) {
			computeChildOptimalSize(first, widthHint, heightHint);
			firstOptimalWidth = first.getWidth();
			firstOptimalHeight = first.getHeight();
		} else {
			firstOptimalWidth = 0;
			firstOptimalHeight = 0;
		}

		// compute optimal size of last widget
		Widget last = this.last;
		int lastOptimalWidth;
		int lastOptimalHeight;
		if (last != null) {
			computeChildOptimalSize(last, widthHint, heightHint);
			lastOptimalWidth = last.getWidth();
			lastOptimalHeight = last.getHeight();
		} else {
			lastOptimalWidth = 0;
			lastOptimalHeight = 0;
		}

		// compute size hint for center widget
		Widget center = this.center;
		int centerWidth;
		int centerHeight;
		if (widthHint != Widget.NO_CONSTRAINT && isHorizontal) {
			centerWidth = computeCenterSize(widthHint, firstOptimalWidth, lastOptimalWidth);
		} else {
			centerWidth = widthHint;
		}
		if (heightHint != Widget.NO_CONSTRAINT && !isHorizontal) {
			centerHeight = computeCenterSize(heightHint, firstOptimalHeight, lastOptimalHeight);
		} else {
			centerHeight = heightHint;
		}

		// compute optimal size of center widget
		int centerOptimalWidth;
		int centerOptimalHeight;
		if (center != null) {
			computeChildOptimalSize(center, centerWidth, centerHeight);
			centerOptimalWidth = center.getWidth();
			centerOptimalHeight = center.getHeight();
		} else {
			centerOptimalWidth = 0;
			centerOptimalHeight = 0;
		}

		// compute container optimal size
		int width;
		int height;
		if (isHorizontal) {
			width = firstOptimalWidth + centerOptimalWidth + lastOptimalWidth;
			height = max(firstOptimalHeight, centerOptimalHeight, lastOptimalHeight);
		} else {
			width = max(firstOptimalWidth, centerOptimalWidth, lastOptimalWidth);
			height = firstOptimalHeight + centerOptimalHeight + lastOptimalHeight;
		}

		// set container optimal size
		size.setSize(width, height);
	}

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

		boolean isHorizontal = isHorizontal();
		Widget first = this.first;
		Widget last = this.last;

		// compute size of side widgets
		int firstWidth = contentWidth;
		int firstHeight = contentHeight;
		int lastWidth = contentWidth;
		int lastHeight = contentHeight;
		if (isHorizontal) {
			firstWidth = getWidth(first);
			lastWidth = getWidth(last);
		} else {
			firstHeight = getHeight(first);
			lastHeight = getHeight(last);
		}

		// lay out first widget
		if (first != null) {
			layOutChild(first, 0, 0, firstWidth, firstHeight);
		}

		// lay out last widget
		if (last != null) {
			layOutChild(last, contentWidth - lastWidth, contentHeight - lastHeight, lastWidth, lastHeight);
		}

		// lay out center widget
		Widget center = this.center;
		if (center != null) {
			if (isHorizontal) {
				int centerWidth = computeCenterSize(contentWidth, firstWidth, lastWidth);
				layOutChild(center, firstWidth, 0, centerWidth, contentHeight);
			} else {
				int centerHeight = computeCenterSize(contentHeight, firstHeight, lastHeight);
				layOutChild(center, 0, firstHeight, contentWidth, centerHeight);
			}
		}
	}

	private boolean isHorizontal() {
		return this.orientation == LayoutOrientation.HORIZONTAL;
	}

	private int getWidth(@Nullable Widget widget) {
		if (widget == null) {
			return 0;
		} else {
			return widget.getWidth();
		}
	}

	private int getHeight(@Nullable Widget widget) {
		if (widget == null) {
			return 0;
		} else {
			return widget.getHeight();
		}
	}

	private static int computeCenterSize(int totalSize, int firstSize, int lastSize) {
		return Math.max(0, totalSize - firstSize - lastSize);
	}

	private static int max(int a, int b, int c) {
		return Math.max(Math.max(a, b), c);
	}
}
