/*******************************************************************************
 * Copyright (c) 2000, 2016 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.swt.custom;


import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.*;

/**
 * The SashForm is a composite control that lays out its children in a
 * row or column arrangement (as specified by the orientation) and places
 * a Sash between each child. One child may be maximized to occupy the
 * entire size of the SashForm.  The relative sizes of the children may
 * be specified using weights.
 * <dl>
 * <dt><b>Styles:</b></dt>
 * <dd>HORIZONTAL, VERTICAL, SMOOTH</dd>
 * </dl>
 *
 * @see <a href="https://eclipse.dev/eclipse/swt/snippets/#sashform">SashForm snippets</a>
 * @see <a href="https://eclipse.dev/eclipse/swt/examples.html">SWT Example: CustomControlExample</a>
 * @see <a href="https://eclipse.dev/eclipse/swt/">Sample code and further information</a>
 */
public class SashForm extends Composite {

	/**
	* The width of all sashes in the form.
	*/
	public int SASH_WIDTH = 3;

	int sashStyle;
	Sash[] sashes = new Sash[0];
	// Remember background and foreground
	// colors to determine whether to set
	// sashes to the default color (null) or
	// a specific color
	Color background = null;
	Color foreground = null;
	Control[] controls = new Control[0];
	Control maxControl = null;
	Listener sashListener;
	static final int DRAG_MINIMUM = 20;

/**
 * Constructs a new instance of this class given its parent
 * and a style value describing its behavior and appearance.
 * <p>
 * The style value is either one of the style constants defined in
 * class <code>SWT</code> which is applicable to instances of this
 * class, or must be built by <em>bitwise OR</em>'ing together
 * (that is, using the <code>int</code> "|" operator) two or more
 * of those <code>SWT</code> style constants. The class description
 * lists the style constants that are applicable to the class.
 * Style bits are also inherited from superclasses.
 * </p>
 *
 * @param parent a widget which will be the parent of the new instance (cannot be null)
 * @param style the style of widget to construct
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
 * </ul>
 * @exception SWTException <ul>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
 * </ul>
 *
 * @see SWT#HORIZONTAL
 * @see SWT#VERTICAL
 * @see #getStyle()
 */
public SashForm(Composite parent, int style) {
	super(parent, checkStyle(style));
	super.setLayout(new SashFormLayout());
	sashStyle = ((style & SWT.VERTICAL) != 0) ? SWT.HORIZONTAL : SWT.VERTICAL;
	if ((style & SWT.BORDER) != 0) sashStyle |= SWT.BORDER;
	if ((style & SWT.SMOOTH) != 0) sashStyle |= SWT.SMOOTH;
	sashListener = this::onDragSash;
}
static int checkStyle (int style) {
	int mask = SWT.BORDER | SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT;
	return style & mask;
}
Sash createSash() {
	Sash sash = new Sash(this, sashStyle);
	sash.setBackground(background);
	sash.setForeground(foreground);
	sash.setToolTipText(getToolTipText());
	sash.addListener(SWT.Selection, sashListener);
	return sash;
}
/**
 * Returns SWT.HORIZONTAL if the controls in the SashForm are laid out side by side
 * or SWT.VERTICAL   if the controls in the SashForm are laid out top to bottom.
 *
 * <p>
 * To retrieve the bidi orientation of the SashForm use <code>{@link #getStyle()}</code>
 * and test if the SWT.RIGHT_TO_LEFT or SWT.LEFT_TO_RIGHT bits are set.
 * </p>
 *
 * @return SWT.HORIZONTAL or SWT.VERTICAL
 */
@Override
public int getOrientation() {
	/*
	 * This call is intentionally commented out, to allow this getter method to be
	 * called from a thread which is different from one that created the widget.
	 */
	//checkWidget();
	return (sashStyle & SWT.VERTICAL) != 0 ? SWT.HORIZONTAL : SWT.VERTICAL;
}
/**
 * Returns the width of the sashes when the controls in the SashForm are
 * laid out.
 *
 * @return the width of the sashes
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 *
 * @since 3.4
 */
public int getSashWidth() {
	checkWidget();
	return SASH_WIDTH;
}
@Override
public int getStyle() {
	int style = super.getStyle();
	style |= getOrientation() == SWT.VERTICAL ? SWT.VERTICAL : SWT.HORIZONTAL;
	if ((sashStyle & SWT.SMOOTH) != 0) style |= SWT.SMOOTH;
	return style;
}
/**
 * Answer the control that currently is maximized in the SashForm.
 * This value may be null.
 *
 * @return the control that currently is maximized or null
 */
public Control getMaximizedControl(){
	/*
	 * This call is intentionally commented out, to allow this getter method to be
	 * called from a thread which is different from one that created the widget.
	 */
	//checkWidget();
	return this.maxControl;
}
/**
 * Answer the relative weight of each child in the SashForm.  The weight represents the
 * percent of the total width (if SashForm has Horizontal orientation) or
 * total height (if SashForm has Vertical orientation) each control occupies.
 * The weights are returned in order of the creation of the widgets (weight[0]
 * corresponds to the weight of the first child created).
 *
 * @return the relative weight of each child
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */

public int[] getWeights() {
	checkWidget();
	Control[] cArray = getControls(false);
	int[] ratios = new int[cArray.length];
	for (int i = 0; i < cArray.length; i++) {
		Object data = cArray[i].getLayoutData();
		if (data instanceof SashFormData) {
			ratios[i] = (int)(((SashFormData)data).weight * 1000 >> 16);
		} else {
			ratios[i] = 200;
		}
	}
	return ratios;
}
Control[] getControls(boolean onlyVisible) {
	Control[] result = new Control[0];
	for (Control element : getChildren()) {
		if (element instanceof Sash) continue;
		if (onlyVisible && !element.getVisible()) continue;

		Control[] newResult = new Control[result.length + 1];
		System.arraycopy(result, 0, newResult, 0, result.length);
		newResult[result.length] = element;
		result = newResult;
	}
	return result;
}
void onDragSash(Event event) {
	Sash sash = (Sash)event.widget;
	int sashIndex = -1;
	for (int i= 0; i < sashes.length; i++) {
		if (sashes[i] == sash) {
			sashIndex = i;
			break;
		}
	}
	if (sashIndex == -1) return;

	Control c1 = controls[sashIndex];
	Control c2 = controls[sashIndex + 1];
	Rectangle b1 = c1.getBounds();
	Rectangle b2 = c2.getBounds();

	Rectangle sashBounds = sash.getBounds();
	Rectangle area = getClientArea();
	boolean correction = false;
	if (getOrientation() == SWT.HORIZONTAL) {
		correction = b1.width < DRAG_MINIMUM || b2.width < DRAG_MINIMUM;
		int totalWidth = b2.x + b2.width - b1.x;
		int shift = event.x - sashBounds.x;
		b1.width += shift;
		b2.x += shift;
		b2.width -= shift;
		if (b1.width < DRAG_MINIMUM) {
			b1.width = DRAG_MINIMUM;
			b2.x = b1.x + b1.width + sashBounds.width;
			b2.width = totalWidth - b2.x;
			event.x = b1.x + b1.width;
			event.doit = false;
		}
		if (b2.width < DRAG_MINIMUM) {
			b1.width = totalWidth - DRAG_MINIMUM - sashBounds.width;
			b2.x = b1.x + b1.width + sashBounds.width;
			b2.width = DRAG_MINIMUM;
			event.x = b1.x + b1.width;
			event.doit = false;
		}
		Object data1 = c1.getLayoutData();
		if (data1 == null || !(data1 instanceof SashFormData)) {
			data1 = new SashFormData();
			c1.setLayoutData(data1);
		}
		Object data2 = c2.getLayoutData();
		if (data2 == null || !(data2 instanceof SashFormData)) {
			data2 = new SashFormData();
			c2.setLayoutData(data2);
		}
		((SashFormData)data1).weight = (((long)b1.width << 16) + area.width - 1) / area.width;
		((SashFormData)data2).weight = (((long)b2.width << 16) + area.width - 1) / area.width;
	} else {
		correction = b1.height < DRAG_MINIMUM || b2.height < DRAG_MINIMUM;
		int totalHeight = b2.y + b2.height - b1.y;
		int shift = event.y - sashBounds.y;
		b1.height += shift;
		b2.y += shift;
		b2.height -= shift;
		if (b1.height < DRAG_MINIMUM) {
			b1.height = DRAG_MINIMUM;
			b2.y = b1.y + b1.height + sashBounds.height;
			b2.height = totalHeight - b2.y;
			event.y = b1.y + b1.height;
			event.doit = false;
		}
		if (b2.height < DRAG_MINIMUM) {
			b1.height = totalHeight - DRAG_MINIMUM - sashBounds.height;
			b2.y = b1.y + b1.height + sashBounds.height;
			b2.height = DRAG_MINIMUM;
			event.y = b1.y + b1.height;
			event.doit = false;
		}
		Object data1 = c1.getLayoutData();
		if (data1 == null || !(data1 instanceof SashFormData)) {
			data1 = new SashFormData();
			c1.setLayoutData(data1);
		}
		Object data2 = c2.getLayoutData();
		if (data2 == null || !(data2 instanceof SashFormData)) {
			data2 = new SashFormData();
			c2.setLayoutData(data2);
		}
		((SashFormData)data1).weight = (((long)b1.height << 16) + area.height - 1) / area.height;
		((SashFormData)data2).weight = (((long)b2.height << 16) + area.height - 1) / area.height;
	}
	if (correction || (event.doit && event.detail != SWT.DRAG)) {
		c1.setBounds(b1);
		sash.setBounds(event.x, event.y, event.width, event.height);
		c2.setBounds(b2);
	}
}
/**
 * If orientation is SWT.HORIZONTAL, lay the controls in the SashForm
 * out side by side.  If orientation is SWT.VERTICAL, lay the
 * controls in the SashForm out top to bottom.
 *
 * <p>
 * Since 3.7, this method can also be called with SWT.RIGHT_TO_LEFT or SWT.LEFT_TO_RIGHT
 * to change the bidi orientation of the SashForm.
 * </p>
 *
 * @param orientation SWT.HORIZONTAL or SWT.VERTICAL, SWT.RIGHT_TO_LEFT or SWT.LEFT_TO_RIGHT
 *
 * @see Control#setOrientation(int)
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 *    <li>ERROR_INVALID_ARGUMENT - if the value of orientation is not SWT.HORIZONTAL or SWT.VERTICAL, SWT.RIGHT_TO_LEFT or SWT.LEFT_TO_RIGHT
 * </ul>
 */
@Override
public void setOrientation(int orientation) {
	checkWidget();
	if (orientation == SWT.RIGHT_TO_LEFT || orientation == SWT.LEFT_TO_RIGHT) {
		super.setOrientation(orientation);
		return;
	}
	if (getOrientation() == orientation) return;
	if (orientation != SWT.HORIZONTAL && orientation != SWT.VERTICAL) {
		SWT.error(SWT.ERROR_INVALID_ARGUMENT);
	}
	sashStyle &= ~(SWT.HORIZONTAL | SWT.VERTICAL);
	sashStyle |= orientation == SWT.VERTICAL ? SWT.HORIZONTAL : SWT.VERTICAL;
	for (int i = 0; i < sashes.length; i++) {
		sashes[i].dispose();
		sashes[i] = createSash();
	}
	layout(false);
}
@Override
public void setBackground (Color color) {
	super.setBackground(color);
	background = color;
	for (Sash sash : sashes) {
		sash.setBackground(background);
	}
}
@Override
public void setForeground (Color color) {
	super.setForeground(color);
	foreground = color;
	for (Sash sash : sashes) {
		sash.setForeground(foreground);
	}
}
/**
 * Sets the layout which is associated with the receiver to be
 * the argument which may be null.
 * <p>
 * Note: No Layout can be set on this Control because it already
 * manages the size and position of its children.
 * </p>
 *
 * @param layout the receiver's new layout or null
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
@Override
public void setLayout (Layout layout) {
	checkWidget();
	return;
}
/**
 * Specify the control that should take up the entire client area of the SashForm.
 * If one control has been maximized, and this method is called with a different control,
 * the previous control will be minimized and the new control will be maximized.
 * If the value of control is null, the SashForm will minimize all controls and return to
 * the default layout where all controls are laid out separated by sashes.
 *
 * @param control the control to be maximized or null
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public void setMaximizedControl(Control control){
	checkWidget();
	if (control == null) {
		if (maxControl != null) {
			this.maxControl = null;
			layout(false);
			for (Sash sashe : sashes) {
				sashe.setVisible(true);
			}
		}
		return;
	}

	for (Sash sash : sashes) {
		sash.setVisible(false);
	}
	maxControl = control;
	layout(false);
}

/**
 * Specify the width of the sashes when the controls in the SashForm are
 * laid out.
 *
 * @param width the width of the sashes
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 *
 * @since 3.4
 */
public void setSashWidth(int width) {
	checkWidget();
	if (SASH_WIDTH == width) return;
	SASH_WIDTH = width;
	layout(false);
}
@Override
public void setToolTipText(String string) {
	super.setToolTipText(string);
	for (Sash sash : sashes) {
		sash.setToolTipText(string);
	}
}
/**
 * Specify the relative weight of each child in the SashForm.  This will determine
 * what percent of the total width (if SashForm has Horizontal orientation) or
 * total height (if SashForm has Vertical orientation) each control will occupy.
 * The weights must be positive values and there must be an entry for each
 * non-sash child of the SashForm.
 *
 * @param weights the relative weight of each child
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 *    <li>ERROR_INVALID_ARGUMENT - if the weights value is null or of incorrect length (must match the number of children)</li>
 * </ul>
 */
public void setWeights(int... weights) {
	checkWidget();
	Control[] cArray = getControls(false);
	if (weights == null || weights.length != cArray.length) {
		SWT.error(SWT.ERROR_INVALID_ARGUMENT);
	}

	int total = 0;
	for (int weight : weights) {
		if (weight < 0) {
			SWT.error(SWT.ERROR_INVALID_ARGUMENT);
		}
		total += weight;
	}
	if (total == 0) {
		SWT.error(SWT.ERROR_INVALID_ARGUMENT);
	}

	for (int i = 0; i < cArray.length; i++) {
		Object data = cArray[i].getLayoutData();
		if (data == null || !(data instanceof SashFormData)) {
			data = new SashFormData();
			cArray[i].setLayoutData(data);
		}
		((SashFormData)data).weight = (((long)weights[i] << 16) + total - 1) / total;
	}

	layout(false);
}
}
