/*******************************************************************************
 * Copyright (c) 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.bpel.common.ui.flatui;
 
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;

public final class FlatFormLayout extends BorderLayout {
	/**
	 * marginWidth specifies the number of pixels of horizontal margin
	 * that will be placed along the left and right edges of the layout.
	 *
	 * The default value is 0.
	 */
	public int marginWidth = 0;
	/**
	 * marginHeight specifies the number of pixels of vertical margin
	 * that will be placed along the top and bottom edges of the layout.
	 *
	 * The default value is 0.
	 */
	public int marginHeight = 0;
	
/**
 * Constructs a new instance of this class.
 */
public FlatFormLayout () {
}

/**
 * Computes the preferred height of the form with
 * respect to the preferred height of the control.
 * 
 * Given that the equations for top (T) and bottom (B)
 * of the control in terms of the height of the form (X)
 * are:
 *		T = AX + B
 *		B = CX + D
 * 
 * The equation for the height of the control (H)
 * is bottom (B) minus top (T) or (H = B - T) or:
 * 
 *		H = (CX + D) - (AX + B)
 * 
 * Solving for (X), the height of the form, we get:
 * 
 *		X = (H + B - D) / (C - A)
 * 
 * When (A = C), (C - A = 0) and the equation has no
 * solution for X.  This is a special case meaning that
 * the control does not constrain the height of the
 * form.  In this case, we need to arbitrarily define
 * the height of the form (X):
 * 
 * Case 1: A = C, A = 0, C = 0
 *
 * 		Let X = D, the distance from the top of the form
 * 		to the bottom edge of the control.  In this case,
 * 		the control was attatched to the top of the form
 * 		and the form needs to be large enough to show the
 * 		bottom edge of the control.
 * 
 * Case 2: A = C, A = 1, C = 1
 * 
 * 		Let X = -B, the distance from the bottom of the
 *		form to the top edge of the control.  In this case,
 * 		the control was attached to the bottom of the form
 * 		and the only way that the control would be visible
 * 		is if the offset is negative.  If the offset is
 * 		positive, there is no possible height for the form
 * 		that will show the control as it will always be
 * 		below the bottom edge of the form.
 * 
 * Case 3: A = C, A != 0, C != 0 and A != 1, C != 0
 * 
 * 		Let X = D / (1 - C), the distance from the top of the 
 * 		form to the bottom edge of the control.  In this case, 
 * 		since C is not 0 or 1, it must be a fraction, U / V.  
 * 		The offset D is the distance from CX to the bottom edge 
 * 		of the control.  This represents a fraction of the form 
 * 		(1 - C)X. Since the height of a fraction of the form is 
 * 		known, the height of the entire form can be found by setting
 * 		(1 - C)X = D.  We solve this equation for X in terms of U 
 * 		and V, giving us X = (U * D) / (U - V). Similarily, if the 
 * 		offset D is	negative, the control is positioned above CX.
 * 		The offset -B is the distance from the top edge of the control
 * 		to CX. We can find the height of the entire form by setting 
 * 		CX = -B. Solving in terms of U and V gives us X = (-B * V) / U.
 */
int computeHeight (FlatFormData data) {
	FlatFormAttachment top = data.getTopAttachment ();
	FlatFormAttachment bottom = data.getBottomAttachment ();
	FlatFormAttachment height = bottom.minus (top);
	if (height.numerator == 0) {
		if (bottom.numerator == 0) return bottom.offset;
		if (bottom.numerator == bottom.denominator) return -top.offset;
		if (bottom.offset <= 0) {
			return -top.offset * top.denominator / bottom.numerator;
		}
		int divider = bottom.denominator - bottom.numerator; 
		return bottom.denominator * bottom.offset / divider;
	}
	return height.solveY (data.cacheHeight);
}

@Override
protected Point computeSize (Composite composite, int wHint, int hHint, boolean flushCache) {
	Point size = layout (composite, false, 0, 0, 0, 0, flushCache);
	size.x += marginWidth * 2;
	size.y += marginHeight * 2;
	return size;
}

/**
 * Computes the preferred height of the form with
 * respect to the preferred height of the control.
 */
int computeWidth (FlatFormData data) {
	FlatFormAttachment left = data.getLeftAttachment ();
	FlatFormAttachment right = data.getRightAttachment ();
	FlatFormAttachment width = right.minus (left);
	if (width.numerator == 0) {
		if (right.numerator == 0) return right.offset;
		if (right.numerator == right.denominator) return -left.offset;
		if (right.offset <= 0) {
			return -left.offset * left.denominator / left.numerator;
		}
		int divider = right.denominator - right.numerator; 
		return right.denominator * right.offset / divider;
	}
	return width.solveY (data.cacheWidth);
}

Point getSize (Control control, boolean flushCache) {
	int wHint = SWT.DEFAULT, hHint = SWT.DEFAULT;
	FlatFormData data = (FlatFormData) control.getLayoutData ();
	if (data != null) {
		wHint = data.width;
		hHint = data.height;
	}
	/* Reserve additional space here for the border! */
	Point pt = control.computeSize (wHint, hHint, flushCache);
	int bshape = BorderShape[getBorderType(
		(data==null)? BORDER_DEFAULT : data.borderType, control)];
	pt.x += LeftBorderSize[bshape] + RightBorderSize[bshape];
	pt.y += TopBorderSize[bshape] + BottomBorderSize[bshape];
	return pt;
}

@Override
protected void layout (Composite composite, boolean flushCache) {
	Rectangle rect = composite.getClientArea ();
	int x = rect.x + marginWidth;
	int y = rect.y + marginHeight;
	int width = Math.max (0, rect.width - 2 * marginWidth);
	int height = Math.max (0, rect.height - 2 * marginHeight);
	layout (composite, true, x, y, width, height, flushCache);
}

Point layout (Composite composite, boolean move, int x, int y, int width, int height, boolean flushCache) {
	Control [] children = composite.getChildren ();
	for (int i = 0; i < children.length; i++) {
		Control child = children [i];
		Point pt = getSize (child, false);
		FlatFormData data = (FlatFormData) child.getLayoutData ();
		if (data == null) {
			child.setLayoutData (data = new FlatFormData ());
		}
		data.cacheWidth = pt.x;
		data.cacheHeight = pt.y;
	}
	for (int i=0; i<children.length; i++) {
		Control child = children [i];
		FlatFormData data = (FlatFormData) child.getLayoutData ();
		/* Adjust the child's position according to its border type! */
		int bshape = BorderShape[getBorderType(
			(data==null)? BORDER_DEFAULT : data.borderType, child)];
		if (move) {
			int x1 = data.getLeftAttachment ().solveX (width) + LeftBorderSize[bshape];
			int y1 = data.getTopAttachment ().solveX (height) + TopBorderSize[bshape];
			int x2 = data.getRightAttachment ().solveX (width) - RightBorderSize[bshape];
			int y2 = data.getBottomAttachment ().solveX (height) - BottomBorderSize[bshape];
			child.setBounds (x + x1, y + y1, x2 - x1, y2 - y1);
		} else {
			width = Math.max (computeWidth (data), width);
			height = Math.max (computeHeight (data), height);
		}
	}
	return move ? null : new Point (width, height);
}
	

@Override
public String toString () {
 	String string = getClass().getName ()+" {";
 	if (marginWidth != 0) string += "marginWidth=" + marginWidth + " " ;
 	if (marginHeight != 0) string += "marginHeight="+marginHeight+" ";
 	
 	string = string.trim();
 	string += "}";
 	return string;
}

}
