/* SpringLayout.java -- 
   Copyright (C) 2004 Roman Kennke <roman@ontographics.com>

This file is part of SableVM Classpath.

SableVM Classpath is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

SableVM Classpath is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with SableVM Classpath; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

package javax.swing;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.LayoutManager2;

import java.util.HashMap;
import java.util.Map;

import javax.swing.Spring;

/**
 * <p>
 * A very flexible layout manager. Components are layed out by defining the
 * relationships between them. The relationships are expressed as
 * {@link Spring}s. You can attach a Spring for each edge of a component and
 * link it to an edge of a different component. For example, you can say,
 * the northern edge of component A should be attached to the southern edge
 * of component B, and the space between them should be something between
 * x and y pixels, and preferably z pixels.</p>
 * <p>While quite simple, this layout manager can be used to emulate most other
 * layout managers, and can also be used to solve some layout problems, which
 * would be hard to solve with other layout managers.</p>
 */
public class SpringLayout implements LayoutManager2
{

    /** The right edge of a component. */
    public static final String EAST = "East";

    /** The top edge of a component. */
    public static final String NORTH = "North";

    /** The bottom edge of a component. */
    public static final String SOUTH = "South";

    /** The left edge of a component. */
    public static final String WEST = "West";

    /** maps components to their constraints. */
    private Map constraintsMap;

    /** The constraints that define the relationships between components.
     * Each Constraints object can hold 4 Springs: one for each edge of the
     * component. Additionally it can hold Springs for the components width
     * and the components height. Since the height and width constraints are
     * dependend on the other constraints, a component can be over-constraint.
     * In this case (like when all of NORTH, SOUTH and HEIGHT are constraint),
     * the values are adjusted, so that the mathematics still hold true.
     */
    public final static class Constraints
    {
	
	/** The constraints for each edge, and width and height. */
	private Spring x, y, height, width, east, south;

	/** Creates a new Constraints object.
	 * There is no constraint set.
	 */
	public Constraints()
	{
	    x = y = height = width = east = south = null;
	}

	/** Creates a new Constraints object.
	 * @param x the constraint for the left edge of the component.
	 * @param y the constraint for the upper edge of the component.
	 */
	public Constraints(Spring x, Spring y)
	{
	    this.x = x;
	    this.y = y;
	    width = height = east = south = null;
	    return;
	}

	/** Creates a new Constraints object.
	 * @param x the constraint for the left edge of the component.
	 * @param y the constraint for the upper edge of the component.
	 * @param width the constraint for the width of the component.
	 * @param height the constraint for the height of the component.
	 */
	public Constraints(Spring x, Spring y, Spring width, Spring height)
	{
	    this.x = x;
	    this.y = y;
	    this.width = width;
	    this.height = height;
	    east = south = null;
	    return;
	}

	/** @return the constraint for the specified edge.
	 * @param edgeName the name of the edge, one of {@link #EAST},
	 *     {@link #WEST}, {@link NORTH} or {@link SOUTH}.
	 */
	public Spring getConstraint(String edgeName)
	{
	    Spring retVal = null;
	    if (edgeName.equals(SpringLayout.NORTH))
		retVal = y;
	    else if (edgeName.equals(SpringLayout.WEST))
		retVal = x;
	    else if (edgeName.equals(SpringLayout.SOUTH))
		{
		    retVal = south;
		    if ((retVal == null) && (y != null) && (height != null))
			retVal = Spring.sum(y, height);
		}
	    else if (edgeName.equals(SpringLayout.EAST))
		{
		    retVal = east;
		    if ((retVal == null) && (x != null) && (width != null))
			retVal = Spring.sum(x, width);
		}
	    
	    return retVal;
	}

	/** @return the height constraint. 
	 */
	public Spring getHeight()
	{
	    Spring retVal = height;
	    if ((retVal == null) && (y != null) && (south != null)) {
		retVal = Spring.sum(south, Spring.minus(y));
	    }
	    return retVal;
	}
	
	/** @return the width constraint.
	 */
	public Spring getWidth()
	{
	    Spring retVal = width;
	    if ((retVal == null) && (x != null) && (east != null)) {
		retVal = Spring.sum(east, Spring.minus(x));
	    }
	    return retVal;
	}
	
	/** The left-edge constraint (== WEST). */
	public Spring getX()
	{
	    Spring retVal = x;
	    if ((retVal == null) && (width != null) && (east != null)) {
		retVal = Spring.sum(east, Spring.minus(width));
	    }
	    return retVal;
	}
	
	/** The upper-edge constraint (== NORTH). */
	public Spring getY()
	{
	    Spring retVal = y;
	    if ((retVal == null) && (height != null) && (south != null)) {
		retVal = Spring.sum(south, Spring.minus(height));
	    }
	    return retVal;
	}

	/** Sets a constraint for the specified edge. If this leads to an
	 * over-constrained situation, the constraints get adjusted, so that
	 * the mathematics still hold true.
	 * @param edgeName the name of the edge, one of {@link #EAST},
	 *     {@link #WEST}, {@link NORTH} or {@link SOUTH}.
	 * @param s the constraint to be set.
	 */
	public void setConstraint(String edgeName, Spring s) {
	    
	    if (edgeName.equals(SpringLayout.WEST))
		{
		    x = s;
		    if ((width != null) && (east != null))
			width = Spring.sum(east, Spring.minus(x));
		}
	    else if (edgeName.equals(SpringLayout.NORTH))
		{
		    y = s;
		    if ((height != null) && (south != null))
			height = Spring.sum(south, Spring.minus(y));
		}
	    else if (edgeName.equals(SpringLayout.EAST))
		{
		    east = s;
		    if ((x != null) && (width != null))
			x = Spring.sum(east, Spring.minus(width));
		}
	    else if (edgeName.equals(SpringLayout.SOUTH))
		{
		    south = s;
		    if ((height != null) && (y != null))
			y = Spring.sum(south, Spring.minus(height));
		}
	    
	    return;
	}

	/** Sets the height-constraint.
	 * @param s the constraint to be set.
	 */
	public void setHeight(Spring s)
	{
	    height = s;
	    if ((south != null) && (y != null))
		south = Spring.sum(y, height);
	    
	    return;
	}
	
	/** Sets the width-constraint.
	 * @param s the constraint to be set.
	 */
	public void setWidth(Spring s)
	{
	    width = s;
	    if ((east != null) && (x != null))
		east = Spring.sum(x, width);
	    
	    return;
	    
	}
	
	/** Sets the WEST-constraint.
	 * @param s the constraint to be set.
	 */
	public void setX(Spring s)
	{
	    x = s;
	    if ((width != null) && (east != null))
		width = Spring.sum(east, Spring.minus(x));
	    
	    return;
	    
	}
	
	/** Sets the NORTH-constraint.
	 * @param s the constraint to be set.
	 */
	public void setY(Spring s)
	{
	    y = s;
	    if ((height != null) && (south != null))
		height = Spring.sum(south, Spring.minus(y));
	    
	    return;
	    
	}
    }

    /** Creates a new SpringLayout. */
    public SpringLayout()
    {
	
	constraintsMap = new HashMap();
	return;
    }

    /** Adds a layout component and a constraint object to this layout.
     * This method is usually only called by a {@java.awt.Container}s add
     * Method.
     * @param component the component to be added.
     * @param constraint the constraint to be set.
     */
    public void addLayoutComponent(Component component, Object constraint)
    {
	constraintsMap.put(component, constraint);
	return;
    }
    

    /** Adds a layout component and a constraint object to this layout.
     * This method is usually only called by a {@java.awt.Container}s add
     * Method. This method does nothing, since SpringLayout does not manage
     * String-indexed components.
     * @param component the component to be added.
     * @param constraint the constraint to be set.
     */
    public void addLayoutComponent(String name, Component c)
    {
	// do nothing here.
	return;
    }
    
    /** @return the constraint of the edge <code>edgeName</code> of the
     * component c.
     * @param c the component from which to get the constraint.
     * @param edgeName the name of the edge, one of {@link #EAST},
     *     {@link #WEST}, {@link NORTH} or {@link SOUTH}.
     */
    public Spring getConstraint(String edgeName, Component c)
    {
	Constraints constraints = getConstraints(c);
	return constraints.getConstraint(edgeName);
    }
    
    /** @return the {@link Constraints} object associated with the specified
     *      component.
     */
    public SpringLayout.Constraints getConstraints(Component c)
    {
	Constraints constraints = (Constraints) constraintsMap.get(c);
	if (constraints == null)
	    {
		Container parent = c.getParent();
		constraints = new Constraints();
		if (parent != null)
		    {
			constraints.setX
			    (Spring.constant(parent.getInsets().left));
			constraints.setY
			    (Spring.constant(parent.getInsets().top));
		    }
		else
		    {
			constraints.setX
			    (Spring.constant(0));
			constraints.setY
			    (Spring.constant(0));
			
		    }
		constraints.setWidth
		    (Spring.constant(c.getMinimumSize().width,
				     c.getPreferredSize().width,
				     c.getMaximumSize().width));
		constraints.setHeight
		    (Spring.constant(c.getMinimumSize().height,
				     c.getPreferredSize().height,
				     c.getMaximumSize().height));
		
		constraintsMap.put(c, constraints);
		
	    }
	
	return constraints;
    }
    
    /** @return always 0.5. */
    public float getLayoutAlignmentX(Container p)
    {
	return 0.5F;
    }
    
    /** @return always 0.5. */
    public float getLayoutAlignmentY(Container p)
    {
	return 0.5F;
    }
    
    /** Recalculate a possibly cached layout. */
    public void invalidateLayout(Container p)
    {
	// nothing to do here yet
	return;
    }
    
    /** Lays out the container.
     * @param p the container to be laid out.
     */
    public void layoutContainer(Container p)
    {
	
	addLayoutComponent(p, new Constraints(Spring.constant(0),
					      Spring.constant(0)));
	
	int offsetX = p.getInsets().left;
	int offsetY = p.getInsets().right;
	
	Component[] components = p.getComponents();
	for (int index = 0; index < components.length; index++)
	    {
		Component c = components[index];
		Constraints constraints = getConstraints(c);
		int x = constraints.getX().getValue();
		int y = constraints.getY().getValue();
		int width = constraints.getWidth().getValue();
		int height = constraints.getHeight().getValue();
		
		c.setLocation(x + offsetX, y + offsetY);
		c.setSize(width, height);
	    }
	
	return;
    }
    
    /** Calculates the maximum size of the layed out container. This
     * respects the maximum sizes of all contained components.
     * @param p the container to be laid out.
     * @return the maximum size of the container.
     */
    public Dimension maximumLayoutSize(Container p)
    {
	int maxX = 0;
	int maxY = 0;
	
	int offsetX = p.getInsets().left;
	int offsetY = p.getInsets().right;
	
	Component[] components = p.getComponents();
	for (int index = 0; index < components.length; index++)
	    {
		Component c = components[index];
		Constraints constraints = getConstraints(c);
		int x = constraints.getX().getMaximumValue();
		int y = constraints.getY().getMaximumValue();
		int width = constraints.getWidth().getMaximumValue();
		int height = constraints.getHeight().getMaximumValue();
		
		int rightEdge = offsetX + x + width;
		if (rightEdge > maxX)
		    maxX = rightEdge;
		int bottomEdge = offsetY + y + height;
		if (bottomEdge > maxY)
		    maxY = bottomEdge;
	    }
	
	return new Dimension(maxX, maxY);
    }

    
    /** Calculates the minimum size of the layed out container. This
     * respects the minimum sizes of all contained components.
     * @param p the container to be laid out.
     * @return the minimum size of the container.
     */
    public Dimension minimumLayoutSize(Container p)
    {
	int maxX = 0;
	int maxY = 0;
	
	int offsetX = p.getInsets().left;
	int offsetY = p.getInsets().right;
	
	Component[] components = p.getComponents();
	for (int index = 0; index < components.length; index++)
	    {
		Component c = components[index];
		Constraints constraints = getConstraints(c);
		int x = constraints.getX().getMinimumValue();
		int y = constraints.getY().getMinimumValue();
		int width = constraints.getWidth().getMinimumValue();
		int height = constraints.getHeight().getMinimumValue();
		
		int rightEdge = offsetX + x + width;
		if (rightEdge > maxX)
		    maxX = rightEdge;
		int bottomEdge = offsetY + y + height;
		if (bottomEdge > maxY)
		    maxY = bottomEdge;
	    }
	
	return new Dimension(maxX, maxY);
    }
    
    /** Calculates the preferred size of the layed out container. This
     * respects the preferred sizes of all contained components.
     * @param p the container to be laid out.
     * @return the preferred size of the container.
     */
    public Dimension preferredLayoutSize(Container p)
    {
	int maxX = 0;
	int maxY = 0;
	
	int offsetX = p.getInsets().left;
	int offsetY = p.getInsets().right;
	
	Component[] components = p.getComponents();
	for (int index = 0; index < components.length; index++)
	    {
		Component c = components[index];
		Constraints constraints = getConstraints(c);
		int x = constraints.getX().getPreferredValue();
		int y = constraints.getY().getPreferredValue();
		int width = constraints.getWidth().getPreferredValue();
		int height = constraints.getHeight().getPreferredValue();
		
		int rightEdge = offsetX + x + width;
		if (rightEdge > maxX)
		    maxX = rightEdge;
		int bottomEdge = offsetY + y + height;
		if (bottomEdge > maxY)
		    maxY = bottomEdge;
	    }
	
	return new Dimension(maxX, maxY);
    }
    
    /** Attaches the edge <code>e1</code> of component <code>c1</code> to
     * the edge <code>e2</code> of component <code>c2</code> width the
     * fixed strut <code>pad</code>.
     * @param e1 the edge of component 1.
     * @param c1 the component 1.
     * @param pad the space between the components in pixels.
     * @param e2 the edge of component 2.
     * @param c2 the component 2.
     */
    public void putConstraint(String e1, Component c1, int pad, String e2, 
			      Component c2)
    {
	Constraints constraints1 = getConstraints(c1);
	Constraints constraints2 = getConstraints(c2);
	
	Spring strut = Spring.constant(pad);
	Spring otherEdge = constraints2.getConstraint(e2);
	constraints1.setConstraint(e1, Spring.sum(strut, otherEdge));
	
	return;
    }
    
    /** Attaches the edge <code>e1</code> of component <code>c1</code> to
     * the edge <code>e2</code> of component <code>c2</code> width the
     * {@link Spring} <code>s</code>.
     * @param e1 the edge of component 1.
     * @param c1 the component 1.
     * @param s the space between the components as a {@link Spring} object.
     * @param e2 the edge of component 2.
     * @param c2 the component 2.
     */
    public void putConstraint(String e1, Component c1, Spring s, String e2, 
			      Component c2)
    {
	Constraints constraints1 = getConstraints(c1);
	Constraints constraints2 = getConstraints(c2);
	
	Spring otherEdge = constraints2.getConstraint(e2);
	constraints1.setConstraint(e1, Spring.sum(s, otherEdge));
	
	return;
    }

    /** Removes a layout component.
     * @param c the layout component to remove.
     */
    public void removeLayoutComponent(Component c)
    {
    }
    
}
