/*******************************************************************************
 * Copyright (c) 2000, 2008 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.core.runtime.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.ui.ISizeProvider;

/**
 * Implementation of a tree where the node is always a sash
 * and it always has two children. If a children is removed
 * the sash, i.e. the node, is removed as well and its other children
 * placed on its parent.
 */
public class LayoutTree implements ISizeProvider {
    /* The parent of this tree or null if it is the root */
    LayoutTreeNode parent;

    /* Any LayoutPart if this is a leaf or a LayoutSashPart if it is a node */
    LayoutPart part;
    
    // Cached information
    private int cachedMinimumWidthHint = SWT.DEFAULT;
    private int cachedMinimumWidth = SWT.DEFAULT;
    private int cachedMinimumHeightHint = SWT.DEFAULT;
    private int cachedMinimumHeight = SWT.DEFAULT;
    private int cachedMaximumWidthHint = SWT.DEFAULT;
    private int cachedMaximumWidth = SWT.DEFAULT;
    private int cachedMaximumHeightHint = SWT.DEFAULT;
    private int cachedMaximumHeight = SWT.DEFAULT;
    
    // Cached size flags
    private boolean sizeFlagsDirty = true;
    private int widthSizeFlags = 0;
    private int heightSizeFlags = 0;
    
    // Cache statistics. For use in benchmarks and test suites only!
    public static int minCacheHits;
    public static int minCacheMisses;
    public static int maxCacheHits;
    public static int maxCacheMisses;
    
    private boolean forceLayout = true;
    private Rectangle currentBounds = new Rectangle(0,0,0,0);
    
    /**
     * Initialize this tree with its part.
     */
    public LayoutTree(LayoutPart part) {
        this.part = part;
    }

    /**
     * Add the relation ship between the children in the list
     * and returns the left children.
     */
    public LayoutPart computeRelation(ArrayList relations) {
        return part;
    }

    /**
     * Locates the part that intersects the given point
     * 
     * @param toFind
     * @return
     */
    public LayoutPart findPart(Point toFind) {
        return part;
    }
    
    /**
     * Dispose all Sashs in this tree
     */
    public void disposeSashes() {
    }
    
    /**
     * Find a LayoutPart in the tree and return its sub-tree. Returns
     * null if the child is not found.
     */
    public LayoutTree find(LayoutPart child) {
        if (part != child) {
			return null;
		}
        return this;
    }

    /**
     * Find the Left,Right,Top and Botton 
     * sashes around this tree and set them
     * in <code>sashes</code>
     */
    public void findSashes(PartPane.Sashes sashes) {
        if (getParent() == null) {
			return;
		}
        getParent().findSashes(this, sashes);
    }

    /**
     * Find the part that is in the bottom rigth possition.
     */
    public LayoutPart findBottomRight() {
        return part;
    }
    
    /**
     * Find a sash in the tree and return its sub-tree. Returns
     * null if the sash is not found.
     */
    public LayoutTreeNode findSash(LayoutPartSash sash) {
        return null;
    }

    /**
     * Return the bounds of this tree which is the rectangle that
     * contains all Controls in this tree.
     */
    public final Rectangle getBounds() {
        return Geometry.copy(currentBounds);
    }
    
    /**
     * Subtracts two integers. If a is INFINITE, this is treated as
     * positive infinity. 
     * 
     * @param a a positive integer or INFINITE indicating positive infinity
     * @param b a positive integer (may not be INFINITE)
     * @return a - b, or INFINITE if a == INFINITE
     * @since 3.1
     */
    public static int subtract(int a, int b) {
        Assert.isTrue(b >= 0 && b < INFINITE);
        
    	return add(a, -b);
    }
    
    /**
     * Adds two positive integers. Treates INFINITE as positive infinity.
     * 
     * @param a a positive integer
     * @param b a positive integer
     * @return a + b, or INFINITE if a or b are positive infinity
     * @since 3.1
     */
    public static int add(int a, int b) {
    	if (a == INFINITE || b == INFINITE) {
    		return INFINITE;
    	}
    	
    	return a + b;
    }
    
    /**
     * Asserts that toCheck is a positive integer less than INFINITE / 2 or equal
     * to INFINITE. Many of the methods of this class use positive integers as sizes,
     * with INFINITE indicating positive infinity. This picks up accidental addition or
     * subtraction from infinity. 
     * 
     * @param toCheck integer to validate
     * @since 3.1
     */
    public static void assertValidSize(int toCheck) {
    	Assert.isTrue(toCheck >= 0 && (toCheck == INFINITE || toCheck < INFINITE / 2));
    }
    
    /**
     * Computes the preferred size for this object. The interpretation of the result depends on the flags returned
     * by getSizeFlags(). If the caller is looking for a maximum or minimum size, this delegates to computeMinimumSize
     * or computeMaximumSize in order to benefit from caching optimizations. Otherwise, it delegates to 
     * doComputePreferredSize. Subclasses should overload one of doComputeMinimumSize, doComputeMaximumSize, or
     * doComputePreferredSize to specialize the return value. 
     * 
     * @see LayoutPart#computePreferredSize(boolean, int, int, int)
     */
    public final int computePreferredSize(boolean width, int availableParallel, int availablePerpendicular, int preferredParallel) {
    	assertValidSize(availableParallel);
    	assertValidSize(availablePerpendicular);
    	assertValidSize(preferredParallel);
    	
    	if (!isVisible()) {
    		return 0;
    	}

    	if (availableParallel == 0) {
    		return 0;
    	}

    	if (preferredParallel == 0) {
    		return Math.min(availableParallel, computeMinimumSize(width, availablePerpendicular));
    	} else if (preferredParallel == INFINITE && availableParallel == INFINITE) {
    		return computeMaximumSize(width, availablePerpendicular);
    	}
    	
    	// Optimization: if this subtree doesn't have any size preferences beyond its minimum and maximum
    	// size, simply return the preferred size
    	if (!hasSizeFlag(width, SWT.FILL)) {
    	    return preferredParallel;
    	}
    	
    	int result = doComputePreferredSize(width, availableParallel, availablePerpendicular, preferredParallel);

    	return result;
    }
    
    /**
     * Returns the size flags for this tree. 
     * 
     * @see org.eclipse.ui.presentations.StackPresentation#getSizeFlags(boolean)
     * 
	 * @param b indicates whether the caller wants the flags for computing widths (=true) or heights (=false)
	 * @return a bitwise combiniation of flags with the same meaning as StackPresentation.getSizeFlags(boolean)
	 */
	protected int doGetSizeFlags(boolean width) {
		return part.getSizeFlags(width);
	}

	/**
	 * Subclasses should overload this method instead of computePreferredSize(boolean, int, int, int)
	 * 
	 * @see org.eclipse.ui.presentations.StackPresentation#computePreferredSize(boolean, int, int, int)
	 * 
	 * @since 3.1
	 */
	protected int doComputePreferredSize(boolean width, int availableParallel, int availablePerpendicular, int preferredParallel) {
    	int result = Math.min(availableParallel, 
    			part.computePreferredSize(width, availableParallel, availablePerpendicular, preferredParallel));

    	assertValidSize(result);
    	return result;    	
    }
    
	/**
	 * Returns the minimum size for this subtree. Equivalent to calling 
	 * computePreferredSize(width, INFINITE, availablePerpendicular, 0).
	 * Returns a cached value if possible or defers to doComputeMinimumSize otherwise.
	 * Subclasses should overload doComputeMinimumSize if they want to specialize the
	 * return value.
	 * 
	 * @param width true iff computing the minimum width, false iff computing the minimum height
	 * @param availablePerpendicular available space (pixels) perpendicular to the dimension 
	 * being computed. This is a height when computing a width, or a width when computing a height.
	 * 
	 * @see LayoutPart#computePreferredSize(boolean, int, int, int)
	 */
    public final int computeMinimumSize(boolean width, int availablePerpendicular) {
    	assertValidSize(availablePerpendicular);
    	
    	// Optimization: if this subtree has no minimum size, then always return 0 as its
    	// minimum size.
        if (!hasSizeFlag(width, SWT.MIN)) {
            return 0;
        }
    	
    	// If this subtree doesn't contain any wrapping controls (ie: they don't care
    	// about their perpendicular size) then force the perpendicular
    	// size to be INFINITE. This ensures that we will get a cache hit
    	// every time for non-wrapping controls.
    	if (!hasSizeFlag(width, SWT.WRAP)) {
    		availablePerpendicular = INFINITE;
    	}
    	
    	if (width) {
    	    // Check if we have a cached width measurement (we can only return a cached
    	    // value if we computed it for the same height)
    		if (cachedMinimumWidthHint == availablePerpendicular) {
    			minCacheHits++;
    			return cachedMinimumWidth;
    		}
    		
    		// Recompute the minimum width and store it in the cache
    		
    		minCacheMisses++;
    		 
    		int result = doComputeMinimumSize(width, availablePerpendicular);
    		cachedMinimumWidth = result;
    		cachedMinimumWidthHint = availablePerpendicular;
    		return result;
    		
    	} else {
    	    // Check if we have a cached height measurement (we can only return a cached
    	    // value if we computed it for the same width)
    		if (cachedMinimumHeightHint == availablePerpendicular) {
    			minCacheHits++;
    			return cachedMinimumHeight;
    		}
    		
    		// Recompute the minimum width and store it in the cache
    		minCacheMisses++;
    		
    		int result = doComputeMinimumSize(width, availablePerpendicular);
    		cachedMinimumHeight = result;
    		cachedMinimumHeightHint = availablePerpendicular;
    		return result;
    	}
    }
    
    /**
     * For use in benchmarks and test suites only. Displays cache utilization statistics for all
     * LayoutTree instances.
     * 
     * @since 3.1
     */
    public static void printCacheStatistics() {
    	System.out.println("minimize cache " + minCacheHits + " / " + (minCacheHits + minCacheMisses) + " hits " + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    			minCacheHits * 100 / (minCacheHits + minCacheMisses) + "%"); //$NON-NLS-1$
    	System.out.println("maximize cache " + maxCacheHits + " / " + (maxCacheHits + maxCacheMisses) + " hits" + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    			maxCacheHits * 100 / (maxCacheHits + maxCacheMisses) + "%"); //$NON-NLS-1$
    }
    
	public int doComputeMinimumSize(boolean width, int availablePerpendicular) {
		int result = doComputePreferredSize(width, INFINITE, availablePerpendicular, 0);
		assertValidSize(result);
		return result;
	}

    public final int computeMaximumSize(boolean width, int availablePerpendicular) {
    	assertValidSize(availablePerpendicular);
    	
    	// Optimization: if this subtree has no maximum size, then always return INFINITE as its
    	// maximum size.
        if (!hasSizeFlag(width, SWT.MAX)) {
            return INFINITE;
        }
    	
    	// If this subtree doesn't contain any wrapping controls (ie: they don't care
    	// about their perpendicular size) then force the perpendicular
    	// size to be INFINITE. This ensures that we will get a cache hit
    	// every time.
    	if (!hasSizeFlag(width, SWT.WRAP)) {
    		availablePerpendicular = INFINITE;
    	}
    	
    	if (width) {    	    
    	    // Check if we have a cached width measurement (we can only return a cached
    	    // value if we computed it for the same height)
    		if (cachedMaximumWidthHint == availablePerpendicular) {
    			maxCacheHits++;
    			return cachedMaximumWidth;
    		}
    		
    		maxCacheMisses++;
    		
    		// Recompute the maximum width and store it in the cache
    		int result = doComputeMaximumSize(width, availablePerpendicular);
    		cachedMaximumWidth = result;
    		cachedMaximumWidthHint = availablePerpendicular;
    		return result;
    		
    	} else {
    	    // Check if we have a cached height measurement
    		if (cachedMaximumHeightHint == availablePerpendicular) {
    			maxCacheHits++;
    			return cachedMaximumHeight;
    		}
    		
    		maxCacheMisses++;
    		
    		// Recompute the maximum height and store it in the cache
    		int result = doComputeMaximumSize(width, availablePerpendicular);
    		cachedMaximumHeight = result;
    		cachedMaximumHeightHint = availablePerpendicular;
    		return result;
    	}
    }
    
    protected int doComputeMaximumSize(boolean width, int availablePerpendicular) {        
    	return doComputePreferredSize(width, INFINITE, availablePerpendicular, INFINITE);
    }
    
    /**
     * Called to flush any cached information in this tree and its parents.
     */
    public void flushNode() {
        
        // Clear cached sizes
        cachedMinimumWidthHint = SWT.DEFAULT;
        cachedMinimumWidth = SWT.DEFAULT;
        cachedMinimumHeightHint = SWT.DEFAULT;
        cachedMinimumHeight = SWT.DEFAULT;
        cachedMaximumWidthHint = SWT.DEFAULT;
        cachedMaximumWidth = SWT.DEFAULT;
        cachedMaximumHeightHint = SWT.DEFAULT;
        cachedMaximumHeight = SWT.DEFAULT;
        
        // Flags may have changed. Ensure that they are recomputed the next time around
        sizeFlagsDirty = true;
        
        // The next setBounds call should trigger a layout even if set to the same bounds since
        // one of the children has changed.
        forceLayout = true;
    }
    
    /**
     * Flushes all cached information about this node and all of its children.
     * This should be called if something may have caused all children to become
     * out of synch with their cached information (for example, if a lot of changes
     * may have happened without calling flushCache after each change)
     * 
     * @since 3.1
     */
    public void flushChildren() {
        flushNode();
    }
    
    /**
     * Flushes all cached information about this node and all of its ancestors.
     * This should be called when a single child changes.
     * 
     * @since 3.1
     */
    public final void flushCache() {
        flushNode();
        
    	if (parent != null) {
    		parent.flushCache();
    	}        
    }
    
    public final int getSizeFlags(boolean width) {
        if (sizeFlagsDirty) {
            widthSizeFlags = doGetSizeFlags(true);
            heightSizeFlags = doGetSizeFlags(false);
            sizeFlagsDirty = false;
        }
        
        return width ? widthSizeFlags : heightSizeFlags;
    }
        
    /**
     * Returns the parent of this tree or null if it is the root.
     */
    public LayoutTreeNode getParent() {
        return parent;
    }

    /**
     * Inserts a new child on the tree. The child will be placed beside 
     * the <code>relative</code> child. Returns the new root of the tree.
     */
    public LayoutTree insert(LayoutPart child, boolean left,
            LayoutPartSash sash, LayoutPart relative) {
        LayoutTree relativeChild = find(relative);
        LayoutTreeNode node = new LayoutTreeNode(sash);
        if (relativeChild == null) {
            //Did not find the relative part. Insert beside the root.
            node.setChild(left, child);
            node.setChild(!left, this);
            return node;
        } else {
            LayoutTreeNode oldParent = relativeChild.getParent();
            node.setChild(left, child);
            node.setChild(!left, relativeChild);
            if (oldParent == null) {
                //It was the root. Return a new root.
                return node;
            }
            oldParent.replaceChild(relativeChild, node);
            return this;
        }
    }

    /**
     * Returns true if this tree can be compressed and expanded.
     * @return true if springy
     */
    public boolean isCompressible() {
        //Added for bug 19524
        return part.isCompressible();
    }

    /**
     * Returns true if this tree has visible parts otherwise returns false.
     */
    public boolean isVisible() {
        return !(part instanceof PartPlaceholder);
    }

    /**
     * Recompute the ratios in this tree.
     */
    public void recomputeRatio() {
    }

    /**
     * Find a child in the tree and remove it and its parent.
     * The other child of its parent is placed on the parent's parent.
     * Returns the new root of the tree.
     */
    public LayoutTree remove(LayoutPart child) {
        LayoutTree tree = find(child);
        if (tree == null) {
			return this;
		}
        LayoutTreeNode oldParent = tree.getParent();
        if (oldParent == null) {
            //It was the root and the only child of this tree
            return null;
        }
        if (oldParent.getParent() == null) {
			return oldParent.remove(tree);
		}

        oldParent.remove(tree);
        return this;
    }

    /**
     * Sets the bounds of this node. If the bounds have changed or any children have
     * changed then the children will be recursively layed out. This implementation
     * filters out redundant calls and delegates to doSetBounds to layout the children. 
     * Subclasses should overload doSetBounds to lay out their children.  
     * 
     * @param bounds new bounds of the tree
     */
    public final void setBounds(Rectangle bounds) {
        if (!bounds.equals(currentBounds) || forceLayout) {
            currentBounds = Geometry.copy(bounds);
            
            doSetBounds(currentBounds);
            forceLayout = false;
        } 
    }
    
    /**
     * Resize the parts on this tree to fit in <code>bounds</code>.
     */
    protected void doSetBounds(Rectangle bounds) {
        part.setBounds(bounds);
    }

    /**
     * Set the parent of this tree.
     */
    void setParent(LayoutTreeNode parent) {
        this.parent = parent;
    }

    /**
     * Set the part of this leaf
     */
    void setPart(LayoutPart part) {
        this.part = part;
        flushCache();
    }

    /**
     * Returns a string representation of this object.
     */
    public String toString() {
        return "(" + part.toString() + ")";//$NON-NLS-2$//$NON-NLS-1$
    }

    /**
     * Creates SWT controls owned by the LayoutTree (ie: the sashes). Does not affect the 
     * LayoutParts that are being arranged by the LayoutTree. 
     * 
     * @param parent
     * @since 3.1
     */
    public void createControl(Composite parent) {        
    }
        
    /**
     * 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.
     * <p>
     * This is only intended for use by test suites.
     * </p>
     * 
     * @param buf
     */
    public void describeLayout(StringBuffer buf) {
        part.describeLayout(buf);
    }

    /**
     * This is a shorthand method that checks if the tree contains the
     * given size flag. For example, hasSizeFlag(false, SWT.MIN) returns
     * true iff the receiver enforces a minimum height, or 
     * hasSizeFlag(true, SWT.WRAP) returns true iff the receiver needs to
     * know its height when computing its preferred width.
     * 
     * @param vertical 
     * @return
     * @since 3.1
     */
    public final boolean hasSizeFlag(boolean width, int flag) {        
        return (getSizeFlags(width) & flag) != 0;
    }

}
