package jas.plot;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager2;

public final class DataAreaLayout implements LayoutManager2
{
	public static final String X_AXIS             = "x";
	public static final String Y_AXIS_LEFT        = "yl";
	public static final String Y_AXIS_RIGHT       = "yr";
	public static final String X_AXIS_LABEL       = "xl";
	public static final String Y_AXIS_LEFT_LABEL  = "yll";
	public static final String Y_AXIS_RIGHT_LABEL = "yrl";

	public void addLayoutComponent(final Component c, final Object constraints)
	{
		if (c instanceof Axis)
		{
		          if (X_AXIS.equals(constraints))       {xAxis       = (Axis) c; xAxisLength      = -1;}
			 else if (Y_AXIS_LEFT.equals(constraints))  {yAxis_left  = (Axis) c; yLeftAxisLength  = -1;}
			 else if (Y_AXIS_RIGHT.equals(constraints)) {
                              yAxis_right = (Axis) c; yRightAxisLength = -1;
                              if ( c instanceof ColorMapAxis )
                                  hasColorMap = true;
                         }
		}
		else if (c instanceof EditableLabel)
		{
			      if (X_AXIS_LABEL.equals(constraints))       xAxis_label       = (EditableLabel) c;
			 else if (Y_AXIS_LEFT_LABEL.equals(constraints))  yAxis_left_label  = (EditableLabel) c;
			 else if (Y_AXIS_RIGHT_LABEL.equals(constraints)) yAxis_right_label = (EditableLabel) c;
		}
	}
	public void addLayoutComponent(final String s, final Component c) 
	{
		addLayoutComponent(c,s);
	}
	public void removeLayoutComponent(final Component c)
	{
		     if (c == xAxis)             xAxis             = null;
		else if (c == yAxis_left)        yAxis_left        = null;
		else if (c == yAxis_right) {
                         yAxis_right       = null;
                         if ( hasColorMap)
                             hasColorMap = false;
                }
		else if (c == xAxis_label)       xAxis_label       = null;
		else if (c == yAxis_left_label)  yAxis_left_label  = null;
		else if (c == yAxis_right_label) yAxis_right_label = null;
	}
	private int getLabelSpaceOnTop()
	{
		int labelSpaceOnTop = 0;
		if (yAxis_left_label != null && ! yAxis_left_label.isRotated() )
			labelSpaceOnTop = yAxis_left_label.getPreferredSize().height + pad;
		if (yAxis_right_label != null && /* no label without matching axis */ yAxis_right != null  && ! yAxis_right_label.isRotated() )
			labelSpaceOnTop = Math.max(yAxis_right_label.getPreferredSize().height, labelSpaceOnTop);
		return labelSpaceOnTop;
	}
	public void layoutContainer(final Container parent)
	{
		if (yAxis_left != null && xAxis != null) // there must be a left axis; right one optional
		{
                    
			final Dimension parentSize = parent.getSize();
			if (parentSize.width <= 0 || parentSize.height <= 0) return; // not sure why this is necessary
			final int labelSpaceOnTop = getLabelSpaceOnTop();
			final int labelSpaceOnBottom = xAxis_label != null ? xAxis_label.getPreferredSize().height + pad : 0;
			final Insets insets = parent.getInsets();
			final int width = parentSize.width - insets.right - insets.left - (hasColorMap ? 30 : 0);
			final int height = parentSize.height - insets.top - insets.bottom - labelSpaceOnTop - labelSpaceOnBottom;
			// we know that the axes always use the same objects for space requirements, so we can obtain
			// final references to them now and have them calculate values later as many times as we need
			final SpaceRequirements x = xAxis.type.spaceRequirements;
			final SpaceRequirements y_left = yAxis_left.type.spaceRequirements;
			final SpaceRequirements y_right = yAxis_right != null ? yAxis_right.type.spaceRequirements : nullSpaceRequirements;

			// now we have to set the axis lengths
			if (lastParentSize != null && xAxisLength      > 0) xAxisLength      += parentSize.width  - lastParentSize.width;
			else                                                xAxisLength       = parentSize.width  * 9 / 10; // a first estimate
			if (lastParentSize != null && yLeftAxisLength  > 0) yLeftAxisLength  += parentSize.height - lastParentSize.height;
			else                                                yLeftAxisLength   = parentSize.height * 9 / 10; // a first estimate
			if (lastParentSize != null && yRightAxisLength > 0 && yAxis_right != null)
				                                                yRightAxisLength += parentSize.height - lastParentSize.height;
			else                                                yRightAxisLength  = parentSize.height * 9 / 10; // a first estimate

			int xorigin;
			int yorigin;
			int distFromRightSide;

			int iterationCounter = 0;
			final int maxIterations = 8; // If we do more that this many iterations, we will give up and quit

			int x_smallest = 0;
			int y_left_smallest = 0;
			int y_right_smallest = 0;
			final int normalMaximumNumberOfIterations = 2; // If we do more than this many iterations, we have an
			                                               // unusual situation, so we will start work in plan B
			final int criticalNumberOfIterations = 5; // If we do more than this many iterations, plan A has definitely
			                                          // failed so we need to start implement the work we started earlier
			// Definitions of plans A and B:
			//  * In plan A, we hope to get an exact fit.  This works in one or two iterations in the vast majority of cases.
			//    We just keep adjusting the axis lengths until we get that perfect fit.
			//  * Plan B is our last resort when plan A takes too many iterations.  We will take the smallest axis lengths
			//    from all of the attempts and use those for the lengths.  This means that an all axes will assume
			//    smaller lengths than they actually get, so there will be fewer labels than there could be.

			while (true)
			{
				boolean allLengthsAreAdequate = true;

				if (iterationCounter < criticalNumberOfIterations)
				// This is the normal case, where we have few iterations so far
				{
					xAxis.assumeAxisLength(xAxisLength);
					yAxis_left.assumeAxisLength(yLeftAxisLength);
					if (yAxis_right != null) yAxis_right.assumeAxisLength(yRightAxisLength);
				}
				else
				// We've gone too far for plan A, so we'd better start using the minimum values we've
				// been tracking
				{
					if (debug) System.out.println("******* USED BACKUP PLAN FOR LAYOUT");
					xAxis.assumeAxisLength(x_smallest);
					yAxis_left.assumeAxisLength(y_left_smallest);
					if (yAxis_right != null) yAxis_right.assumeAxisLength(y_right_smallest);
				}

				xorigin = Math.max(x.width, y_left.width) + Axis.padAroundEdge;
				yorigin = Math.max(Math.max(x.height , y_left.height), y_right.height) + Axis.padAroundEdge;
				distFromRightSide = Math.max(x.flowPastEnd, y_right.width) + Axis.padAroundEdge;

				final int minWidth = xorigin + distFromRightSide + xAxisLength + insets.left + insets.right;
				if (minWidth != parentSize.width)
				{
					xAxisLength = Math.max(0,xAxisLength + parentSize.width - minWidth);
					allLengthsAreAdequate = false;
				}

				int minHeight = yorigin + y_left.flowPastEnd + Axis.padAroundEdge +
					yLeftAxisLength + insets.top + insets.bottom;
				if (minHeight != parentSize.height)
				{
					yLeftAxisLength = Math.max(0,yLeftAxisLength + parentSize.height - minHeight);
					allLengthsAreAdequate = false;
				}

				if (yAxis_right != null)
				{
					minHeight = yorigin + y_right.flowPastEnd + Axis.padAroundEdge
						+ yRightAxisLength + insets.top + insets.bottom;
					if (minHeight != parentSize.height)
					{
						yRightAxisLength = Math.max (0,yRightAxisLength + parentSize.height - minHeight);
						allLengthsAreAdequate = false;
					}
				}

				iterationCounter++;
				if (allLengthsAreAdequate)
				{
					if (debug) System.out.println("layout required "+ iterationCounter +" iteration(s)");
					break;
				}
				if (iterationCounter > normalMaximumNumberOfIterations)
				// If this executes, we've had more than normal number of
				// iterations, so we start tracking the sizes to come up with
				// a size that will work for all sizes
				{
					if (xAxisLength > 0)
						if (x_smallest == 0) x_smallest = xAxisLength;
						else if (xAxisLength < x_smallest) x_smallest = xAxisLength;
					if (yLeftAxisLength > 0)
						if (y_left_smallest == 0) y_left_smallest = yLeftAxisLength;
						else if (yLeftAxisLength < y_left_smallest) y_left_smallest = yLeftAxisLength;
					if (yAxis_right != null && yRightAxisLength > 0)
						if (y_right_smallest == 0) y_right_smallest = yRightAxisLength;
						else if (yRightAxisLength < y_right_smallest) y_right_smallest = yRightAxisLength;
				}
				if (iterationCounter >= maxIterations) throw new LayoutFailed();
			}
			
                        if (yAxis_left_label != null && yAxis_left_label.isRotated()) xorigin += yAxis_left_label.getPreferredSize().getHeight()+5;
                                                
			yAxis_left.setLocation(xorigin - y_left.width + insets.left, insets.top + labelSpaceOnTop);
			yAxis_left.setSize(y_left.width, height - yorigin + y_left.height);

                        if (yAxis_left_label != null)
			{
                            if ( yAxis_left_label.isRotated() ) {
				yAxis_left_label.setLocation(pad, pad + insets.top + (height - (int)yAxis_left_label.getPreferredSize().getWidth())/2);
				yAxis_left_label.setSize((int)yAxis_left_label.getPreferredSize().getWidth(),(int)yAxis_left_label.getPreferredSize().getWidth());
                            } else {
                                yAxis_left_label.setLocation(insets.left + pad , insets.top + pad);
				yAxis_left_label.setSize(yAxis_left_label.getPreferredSize());
                            }
			}

                        if ( yAxis_right_label != null && yAxis_right_label.isRotated() )
                            distFromRightSide += (int) yAxis_right_label.getPreferredSize().getHeight();
                        
			xAxis.setLocation(xorigin - x.width + insets.left, height + insets.top - yorigin + labelSpaceOnTop);
			xAxis.setSize(width - xorigin + x.width - distFromRightSide + Axis.padAroundEdge + x.flowPastEnd , x.height);
                        
                        if (xAxis_label != null)
			{
				final Dimension prefSize = xAxis_label.getPreferredSize();
				xAxis_label.setLocation((width - xorigin - distFromRightSide) / 2 + xorigin - prefSize.width / 2 + insets.left,
					parentSize.height - insets.bottom - pad - prefSize.height);
				xAxis_label.setSize(prefSize);
			}

                        
                        
			if (yAxis_right != null)
			{
                            
                            yAxis_right.setLocation(width - distFromRightSide - insets.left ,insets.top + labelSpaceOnTop);
                            yAxis_right.setSize(y_right.width+(hasColorMap?30:0), height - yorigin + y_right.height);

                            if (yAxis_right_label != null)
				{
					final Dimension prefSize = yAxis_right_label.getPreferredSize();
                                        if ( yAxis_right_label.isRotated() ) {       
                                            yAxis_right_label.setLocation(parentSize.width - insets.right - pad - prefSize.height , pad + insets.top + (height - (int)prefSize.getWidth())/2);
                                            yAxis_right_label.setSize((int)prefSize.getWidth(), (int) prefSize.getWidth());                                            
                                        } else {
                                            yAxis_right_label.setLocation(parentSize.width - insets.right - pad - prefSize.width , insets.top + pad);
                                            yAxis_right_label.setSize(prefSize);
                                        }
				}
			}
			lastParentSize = parentSize;
		}
	}
	public Dimension minimumLayoutSize(final Container parent)
	{
		return preferredLayoutSize(parent);
	}
	public Dimension maximumLayoutSize(final Container parent)
	{
		return preferredLayoutSize(parent);
	}
	public Dimension preferredLayoutSize(final Container parent)
	{
		return new Dimension(10,10);
	}
    /**
     * Returns the alignment along the x axis.  This specifies how
     * the component would like to be aligned relative to other 
     * components.  The value should be a number between 0 and 1
     * where 0 represents alignment along the origin, 1 is aligned
     * the furthest away from the origin, 0.5 is centered, etc.
     */
    public float getLayoutAlignmentX(final Container parent)
	{
		return 0.5f;
    }

    /**
     * Returns the alignment along the y axis.  This specifies how
     * the component would like to be aligned relative to other 
     * components.  The value should be a number between 0 and 1
     * where 0 represents alignment along the origin, 1 is aligned
     * the furthest away from the origin, 0.5 is centered, etc.
     */
    public float getLayoutAlignmentY(final Container parent) 
	{
		return 0.5f;
    }

    /**
     * Invalidates the layout, indicating that if the layout manager
     * has cached information it should be discarded.
     */
    public void invalidateLayout(final Container target) 
	{
    }
	private Axis xAxis;
	private Axis yAxis_left;
	private Axis yAxis_right;
	private EditableLabel xAxis_label;
	private EditableLabel yAxis_left_label;
	private EditableLabel yAxis_right_label;
	private static final int pad = 5;
	private static final SpaceRequirements nullSpaceRequirements = new SpaceRequirements(); // all fields are zero
	private int xAxisLength = -1;
	private int yLeftAxisLength = -1;
	private int yRightAxisLength = -1;
        private boolean hasColorMap = false;
	private Dimension lastParentSize;
	private static final boolean debug;
	static
	{
		boolean result;
		try
		{
			result = System.getProperty("debugDataAreaLayout") != null;
		}
		catch (SecurityException x) // in case we are in an applet!
		{
			result = false;
		}
		debug = result;
	}
}
class LayoutFailed extends RuntimeException
{
}
