/*******************************************************************************
 * Copyright (c) 2000, 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
 *     Randy Hudson <hudsonr@us.ibm.com>
 *     - Fix for bug 19524 - Resizing WorkbenchWindow resizes Views
 *     Cagatay Kavukcuoglu <cagatayk@acm.org>
 *     - Fix for bug 10025 - Resizing views should not use height ratios
 *******************************************************************************/
package org.eclipse.ui.internal;

import java.util.ArrayList;

import org.eclipse.jface.util.Assert;
import org.eclipse.jface.util.Geometry;
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.Sash;
import org.eclipse.ui.IPageLayout;

/**
 * Implementation of a tree node. The node represents a
 * sash and it allways has two children.
 */
public class LayoutTreeNode extends LayoutTree {
	
	static class ChildSizes {
		int left;
		int right;
		boolean resizable = true;
		
		public ChildSizes (int l, int r, boolean resize) {
			left = l;
			right = r;
			resizable = resize;
		}
	}
	
    /* The node children witch may be another node or a leaf */
    private LayoutTree children[] = new LayoutTree[2];

    /* The sash's width when vertical and hight on horizontal */
    final static int SASH_WIDTH = 3;

    /**
     * Initialize this tree with its sash.
     */
    public LayoutTreeNode(LayoutPartSash sash) {
        super(sash);
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.internal.LayoutTree#flushChildren()
     */
    public void flushChildren() {
        super.flushChildren();
        
        children[0].flushChildren();
        children[1].flushChildren();
    }
    
    /**
     * Traverses the tree to find the part that intersects the given point
     * 
     * @param toFind
     * @return the part that intersects the given point
     */
    public LayoutPart findPart(Point toFind) {
        if (!children[0].isVisible()) {
            if (!children[1].isVisible()) {
                return null;
            }

            return children[1].findPart(toFind);
        } else {
            if (!children[1].isVisible()) {
                return children[0].findPart(toFind);
            }
        }

        LayoutPartSash sash = getSash();

        Rectangle bounds = sash.getBounds();

        if (sash.isVertical()) {
            if (toFind.x < bounds.x + (bounds.width / 2)) {
                return children[0].findPart(toFind);
            }
            return children[1].findPart(toFind);
        } else {
            if (toFind.y < bounds.y + (bounds.height / 2)) {
                return children[0].findPart(toFind);
            }
            return children[1].findPart(toFind);
        }
    }

    /**
     * Add the relation ship between the children in the list
     * and returns the left children.
     */
    public LayoutPart computeRelation(ArrayList relations) {
        PartSashContainer.RelationshipInfo r = new PartSashContainer.RelationshipInfo();
        r.relative = children[0].computeRelation(relations);
        r.part = children[1].computeRelation(relations);
        r.left = getSash().getLeft();
        r.right = getSash().getRight();
        r.relationship = getSash().isVertical() ? IPageLayout.RIGHT
                : IPageLayout.BOTTOM;
        relations.add(0, r);
        return r.relative;
    }

    /**
     * Dispose all Sashs in this tree
     */
    public void disposeSashes() {
        children[0].disposeSashes();
        children[1].disposeSashes();
        getSash().dispose();
    }

    /**
     * Find a LayoutPart in the tree and return its sub-tree. Returns
     * null if the child is not found.
     */
    public LayoutTree find(LayoutPart child) {
        LayoutTree node = children[0].find(child);
        if (node != null)
            return node;
        node = children[1].find(child);
        return node;
    }

    /**
     * Find the part that is in the bottom right position.
     */
    public LayoutPart findBottomRight() {
        if (children[1].isVisible())
            return children[1].findBottomRight();
        return children[0].findBottomRight();
    }

    /**
     * Go up in the tree finding a parent that is common of both children.
     * Return the subtree.
     */
    public LayoutTreeNode findCommonParent(LayoutPart child1, LayoutPart child2) {
        return findCommonParent(child1, child2, false, false);
    }

    /**
     * Go up in the tree finding a parent that is common of both children.
     * Return the subtree.
     */
    LayoutTreeNode findCommonParent(LayoutPart child1, LayoutPart child2,
            boolean foundChild1, boolean foundChild2) {
        if (!foundChild1)
            foundChild1 = find(child1) != null;
        if (!foundChild2)
            foundChild2 = find(child2) != null;
        if (foundChild1 && foundChild2)
            return this;
        if (parent == null)
            return null;
        return parent
                .findCommonParent(child1, child2, foundChild1, foundChild2);
    }

    /**
     * Find a sash in the tree and return its sub-tree. Returns
     * null if the sash is not found.
     */
    public LayoutTreeNode findSash(LayoutPartSash sash) {
        if (this.getSash() == sash)
            return this;
        LayoutTreeNode node = children[0].findSash(sash);
        if (node != null)
            return node;
        node = children[1].findSash(sash);
        if (node != null)
            return node;
        return null;
    }

    /**
     * Sets the elements in the array of sashes with the
     * Left,Rigth,Top and Botton sashes. The elements
     * may be null depending whether there is a shash
     * beside the <code>part</code>
     */
    void findSashes(LayoutTree child, PartPane.Sashes sashes) {
        Sash sash = (Sash) getSash().getControl();
        boolean leftOrTop = children[0] == child;
        if (sash != null) {
            LayoutPartSash partSash = getSash();
            //If the child is in the left, the sash 
            //is in the rigth and so on.
            if (leftOrTop) {
                if (partSash.isVertical()) {
                    if (sashes.right == null)
                        sashes.right = sash;
                } else {
                    if (sashes.bottom == null)
                        sashes.bottom = sash;
                }
            } else {
                if (partSash.isVertical()) {
                    if (sashes.left == null)
                        sashes.left = sash;
                } else {
                    if (sashes.top == null)
                        sashes.top = sash;
                }
            }
        }
        if (getParent() != null)
            getParent().findSashes(this, sashes);
    }

    /**
     * Returns the sash of this node.
     */
    public LayoutPartSash getSash() {
        return (LayoutPartSash) part;
    }

    /**
     * Returns true if this tree has visible parts otherwise returns false.
     */
    public boolean isVisible() {
        return children[0].isVisible() || children[1].isVisible();
    }

    /**
     * Remove the child and this node from the tree
     */
    LayoutTree remove(LayoutTree child) {
        getSash().dispose();
        if (parent == null) {
            //This is the root. Return the other child to be the new root.
            if (children[0] == child) {
                children[1].setParent(null);
                return children[1];
            }
            children[0].setParent(null);
            return children[0];
        }

        LayoutTreeNode oldParent = parent;
        if (children[0] == child)
            oldParent.replaceChild(this, children[1]);
        else
            oldParent.replaceChild(this, children[0]);
        return oldParent;
    }

    /**
     * Replace a child with a new child and sets the new child's parent.
     */
    void replaceChild(LayoutTree oldChild, LayoutTree newChild) {
        if (children[0] == oldChild)
            children[0] = newChild;
        else if (children[1] == oldChild)
            children[1] = newChild;
        newChild.setParent(this);
        if (!children[0].isVisible() || !children[0].isVisible())
            getSash().dispose();

        flushCache();
    }

    /**
     * Go up from the subtree and return true if all the sash are 
     * in the direction specified by <code>isVertical</code>
     */
    public boolean sameDirection(boolean isVertical, LayoutTreeNode subTree) {
        boolean treeVertical = getSash().isVertical();
        if (treeVertical != isVertical)
            return false;
        while (subTree != null) {
            if (this == subTree)
                return true;
            if (subTree.children[0].isVisible()
                    && subTree.children[1].isVisible())
                if (subTree.getSash().isVertical() != isVertical)
                    return false;
            subTree = subTree.getParent();
        }
        return true;
    }
    
    public int doComputePreferredSize(boolean width, int availableParallel, int availablePerpendicular, int preferredParallel) {
    	assertValidSize(availablePerpendicular);
    	assertValidSize(availableParallel);
    	assertValidSize(preferredParallel);
    	
    	// If one child is invisible, defer to the other child
    	if (!children[0].isVisible()) {
    		return children[1].computePreferredSize(width, availableParallel, availablePerpendicular, preferredParallel);
    	}
    	
    	if (!children[1].isVisible()) {
    		return children[0].computePreferredSize(width, availableParallel, availablePerpendicular, preferredParallel);
    	}
    	
    	if (availableParallel == 0) {
    		return 0;
    	}
    	
    	// If computing the dimension perpendicular to our sash
    	if (width == getSash().isVertical()) {
    		// Compute the child sizes
    		ChildSizes sizes = computeChildSizes(availableParallel, availablePerpendicular,
    				getSash().getLeft(), getSash().getRight(), preferredParallel);
    		
    		// Return the sum of the child sizes plus the sash size
    		return add(sizes.left, add(sizes.right, SASH_WIDTH));
    	} else {
    		// Computing the dimension parallel to the sash. We will compute and return the preferred size
    		// of whichever child is closest to the ideal size.
    		
    		ChildSizes sizes;
    		// First compute the dimension of the child sizes perpendicular to the sash
			sizes = computeChildSizes(availablePerpendicular, availableParallel,
				getSash().getLeft(), getSash().getRight(), availablePerpendicular);
    		
    		// Use this information to compute the dimension of the child sizes parallel to the sash.
    		// Return the preferred size of whichever child is largest
    		int leftSize = children[0].computePreferredSize(width, availableParallel, sizes.left, preferredParallel);
    		
    		// Compute the preferred size of the right child
    		int rightSize = children[1].computePreferredSize(width, availableParallel, sizes.right, preferredParallel); 
    		
    		// Return leftSize or rightSize: whichever one is largest
    		int result = rightSize;
    		if (leftSize > rightSize) {
    			result = leftSize;
    		}

    		assertValidSize(result);
    		
    		return result;
    	}
    }
	
    /**
     * Computes the pixel sizes of this node's children, given the available
     * space for this node. Note that "width" and "height" actually refer
     * to the distance perpendicular and parallel to the sash respectively.
     * That is, their meaning is reversed when computing a horizontal sash. 
     * 
     * @param width the pixel width of a vertical node, or the pixel height
     * of a horizontal node (INFINITE if unbounded)
     * @param height the pixel height of a vertical node, or the pixel width
     * of a horizontal node (INFINITE if unbounded)
     * @return a struct describing the pixel sizes of the left and right children
     * (this is a width for horizontal nodes and a height for vertical nodes)
     */
    ChildSizes computeChildSizes(int width, int height, int left, int right, int preferredWidth) {
    	Assert.isTrue(children[0].isVisible());
    	Assert.isTrue(children[1].isVisible());
    	assertValidSize(width);
    	assertValidSize(height);
    	assertValidSize(preferredWidth);
    	Assert.isTrue(left >= 0);
    	Assert.isTrue(right >= 0);
    	Assert.isTrue(preferredWidth >= 0);
    	Assert.isTrue(preferredWidth <= width);
    	boolean vertical = getSash().isVertical();
    	
        if (width <= SASH_WIDTH) {
        	return new ChildSizes(0,0, false);
        }
        
        if (width == INFINITE) {
        	if (preferredWidth == INFINITE) {
        		return new ChildSizes(children[0].computeMaximumSize(vertical, height),
        				children[1].computeMaximumSize(vertical, height), false);
        	}
        	
        	if (preferredWidth == 0) {
        		return new ChildSizes(children[0].computeMinimumSize(vertical, height),
        				children[1].computeMinimumSize(vertical, height), false);
        	}
        }
        
        int total = left + right;

        // Use all-or-none weighting
        double wLeft = left, wRight = right;
        switch (getCompressionBias()) {
        case -1:
            wLeft = 0.0;
            break;
        case 1:
            wRight = 0.0;
            break;
        default:
            break;
        }
        double wTotal = wLeft + wRight;
        
        // Subtract the SASH_WIDTH from preferredWidth and width. From here on, we'll deal with the
        // width available to the controls and neglect the space used by the sash.
        preferredWidth = Math.max(0, subtract(preferredWidth, SASH_WIDTH));
        width = Math.max(0, subtract(width, SASH_WIDTH));
        
        int redistribute = subtract(preferredWidth, total);
        
        // Compute the minimum and maximum sizes for each child
    	int leftMinimum = children[0].computeMinimumSize(vertical, height);
    	int rightMinimum = children[1].computeMinimumSize(vertical, height);
    	int leftMaximum = children[0].computeMaximumSize(vertical, height);
    	int rightMaximum = children[1].computeMaximumSize(vertical, height);
    	
    	// Keep track of the available space for each child, given the minimum size of the other child
    	int leftAvailable = Math.min(leftMaximum, Math.max(0, subtract(width, rightMinimum)));
    	int rightAvailable = Math.min(rightMaximum, Math.max(0, subtract(width, leftMinimum)));
    	
        // Figure out the ideal size of the left child
    	int idealLeft = Math.max(leftMinimum, Math.min(preferredWidth,  
    			left + (int) Math.round(redistribute * wLeft / wTotal)));
    	
    	// If the right child can't use all its available space, let the left child fill it in
    	idealLeft = Math.max(idealLeft, preferredWidth - rightAvailable);
    	// Ensure the left child doesn't get larger than its available space
    	idealLeft = Math.min(idealLeft, leftAvailable);
    	
    	// Check if the left child would prefer to be a different size 
    	idealLeft = children[0].computePreferredSize(vertical, leftAvailable, height, idealLeft);
    	
    	// Ensure that the left child is larger than its minimum size
    	idealLeft = Math.max(idealLeft, leftMinimum);
        idealLeft = Math.min(idealLeft, leftAvailable);
    	
    	// Compute the right child width
    	int idealRight = Math.max(rightMinimum, preferredWidth - idealLeft);
    	
		rightAvailable = Math.max(0, Math.min(rightAvailable, subtract(width, idealLeft)));
    	idealRight = Math.min(idealRight, rightAvailable);
    	idealRight = children[1].computePreferredSize(vertical, rightAvailable, height, idealRight);
    	idealRight = Math.max(idealRight, rightMinimum);
    	
    	return new ChildSizes(idealLeft, idealRight, leftMaximum > leftMinimum 
    	        && rightMaximum > rightMinimum 
    	        && leftMinimum + rightMinimum < width);    	    	
    }
    
    protected int doGetSizeFlags(boolean width) {
        if (!children[0].isVisible()) {
            return children[1].getSizeFlags(width);
        }
        
        if (!children[1].isVisible()) {
            return children[0].getSizeFlags(width);
        }
        
        int leftFlags = children[0].getSizeFlags(width);
        int rightFlags = children[1].getSizeFlags(width);
        
        return ((leftFlags | rightFlags) & ~SWT.MAX) | (leftFlags & rightFlags & SWT.MAX);
    }
	
    /**
     * Resize the parts on this tree to fit in <code>bounds</code>.
     */
    public void doSetBounds(Rectangle bounds) {
        if (!children[0].isVisible()) {
            children[1].setBounds(bounds);
            getSash().setVisible(false);
            return;
        }
        if (!children[1].isVisible()) {
            children[0].setBounds(bounds);
            getSash().setVisible(false);
            return;
        }
        
        bounds = Geometry.copy(bounds);
        
        boolean vertical = getSash().isVertical();

        // If this is a horizontal sash, flip coordinate systems so 
        // that we can eliminate special cases
        if (!vertical) {
        	Geometry.flipXY(bounds);
        }

        ChildSizes childSizes = computeChildSizes(bounds.width, bounds.height, getSash().getLeft(), getSash().getRight(), bounds.width);
        
        getSash().setVisible(true);
        getSash().setEnabled(childSizes.resizable);
        
        Rectangle leftBounds = new Rectangle(bounds.x, bounds.y, childSizes.left, bounds.height);
        Rectangle sashBounds = new Rectangle(leftBounds.x + leftBounds.width, bounds.y, SASH_WIDTH, bounds.height);
        Rectangle rightBounds = new Rectangle(sashBounds.x + sashBounds.width, bounds.y, childSizes.right, bounds.height);
        
        if (!vertical) {
        	Geometry.flipXY(leftBounds);
        	Geometry.flipXY(sashBounds);
        	Geometry.flipXY(rightBounds);
        }
        
        getSash().setBounds(sashBounds);
        children[0].setBounds(leftBounds);
        children[1].setBounds(rightBounds);
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.internal.LayoutTree#createControl(org.eclipse.swt.widgets.Composite)
     */
    public void createControl(Composite parent) {
        children[0].createControl(parent);
        children[1].createControl(parent);
        getSash().createControl(parent);
        
        super.createControl(parent);
    }
    
    //Added by hudsonr@us.ibm.com - bug 19524

    public boolean isCompressible() {
        return children[0].isCompressible() || children[1].isCompressible();
    }

    /**
     * Returns 0 if there is no bias. Returns -1 if the first child should be of
     * fixed size, and the second child should be compressed. Returns 1 if the
     * second child should be of fixed size.
     * @return the bias
     */
    public int getCompressionBias() {
        boolean left = children[0].isCompressible();
        boolean right = children[1].isCompressible();
        if (left == right)
            return 0;
        if (right)
            return -1;
        return 1;
    }
	
    boolean isLeftChild(LayoutTree toTest) {
        return children[0] == toTest;
    }

    LayoutTree getChild(boolean left) {
        int index = left ? 0 : 1;
        return (children[index]);
    }

    /**
     * Sets a child in this node
     */
    void setChild(boolean left, LayoutPart part) {
        LayoutTree child = new LayoutTree(part);
        setChild(left, child);
        flushCache();
    }

    /**
     * Sets a child in this node
     */
    void setChild(boolean left, LayoutTree child) {
        int index = left ? 0 : 1;
        children[index] = child;
        child.setParent(this);
        flushCache();
    }

    /**
     * Returns a string representation of this object.
     */
    public String toString() {
        String s = "<null>\n";//$NON-NLS-1$
        if (part.getControl() != null)
            s = "<@" + part.getControl().hashCode() + ">\n";//$NON-NLS-2$//$NON-NLS-1$
        String result = "["; //$NON-NLS-1$
        if (children[0].getParent() != this)
            result = result + "{" + children[0] + "}" + s;//$NON-NLS-2$//$NON-NLS-1$
        else
            result = result + children[0] + s;

        if (children[1].getParent() != this)
            result = result + "{" + children[1] + "}]";//$NON-NLS-2$//$NON-NLS-1$
        else
            result = result + children[1] + "]";//$NON-NLS-1$
        return result;
    }
    
    /**
     * Create the sashes if the children are visible
     * and dispose it if they are not.
     */
//    public void updateSashes(Composite parent) {
//        if (parent == null)
//            return;
//        children[0].updateSashes(parent);
//        children[1].updateSashes(parent);
//        if (children[0].isVisible() && children[1].isVisible())
//            getSash().createControl(parent);
//        else
//            getSash().dispose();
//    }

    /**
     * Writes a description of the layout to the given string buffer.
     * This is used for drag-drop test suites to determine if two layouts are the
     * same. Like a hash code, the description should compare as equal iff the
     * layouts are the same. However, it should be user-readable in order to
     * help debug failed tests. Although these are english readable strings,
     * they should not be translated or equality tests will fail.
     * 
     * @param buf
     */
    public void describeLayout(StringBuffer buf) {
        if (!(children[0].isVisible())) {
            if (!children[1].isVisible()) {
                return;
            }

            children[1].describeLayout(buf);
            return;
        }

        if (!children[1].isVisible()) {
            children[0].describeLayout(buf);
            return;
        }

        buf.append("("); //$NON-NLS-1$
        children[0].describeLayout(buf);

        buf.append(getSash().isVertical() ? "|" : "-"); //$NON-NLS-1$ //$NON-NLS-2$

        children[1].describeLayout(buf);
        buf.append(")"); //$NON-NLS-1$
    }

}
