/*
 * $Id: BoundingBox.java,v 1.2 2007/08/26 18:56:35 gil1 Exp $
 *
 * $Date: 2007/08/26 18:56:35 $
 *
 * Copyright (c) Eric Z. Beard, ericzbeard@hotmail.com 
 *
 * This library 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.
 *
 * This library 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 this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 */
package gnu.jpdf;

import java.awt.*;
import java.util.*;

/**
 * <p>
 * This class simplifies the placement of Strings within a canvas area where the
 * placement of objects is absolute</p>
 *
 * <p>
 * A <code>BoundingBox</code> is just a Rectangle that knows how to find the
 * coordinates for a String based on the desired alignment and
 * <code>FontMetrics</code>. For each new String, a new child
 * <code>BoundingBox</code> is made that can be subtracted from the original box
 * so new Strings can be added</p>
 *
 * <p>
 * One of the more helpful features of this class is the string wrap feature of
 * <code>getStringBounds</code>. The box returned by that method will contain an
 * array of strings that have been broken down to fit the box. The box's
 * coordinates and size will reflect the size of the entire group of strings if
 * it is laid out as expected. Using the returned box and iterating through the
 * array of strings from top to bottom, getting new bounding boxes for each one
 * (with upper left alignment and no padding) will result in the correct string
 * wrap.</p>
 *
 * <p>
 * Note that you will need to have Xvfb running on a Unix server to use this
 * class</p>
 *
 * @author Eric Z. Beard, ericzbeard@hotmail.com
 * @version $Revision: 1.2 $, $Date: 2007/08/26 18:56:35 $
 */
public class BoundingBox extends Rectangle {

    /**
     * Percent f line height to space lines
     */
    public static final int LINE_SPACING_PERCENTAGE = 20;

    /**
     * Used to a align a String centered vertically
     */
    public static final int VERT_ALIGN_CENTER = 0;

    /**
     * Used to align a String at the top of the box
     */
    public static final int VERT_ALIGN_TOP = 1;

    /**
     * Used to align a String at the bottom of the box
     */
    public static final int VERT_ALIGN_BOTTOM = 2;

    /**
     * Used to align a String horizontally in the center of the box
     */
    public static final int HORIZ_ALIGN_CENTER = 3;

    /**
     * Used to align a String to the left in the box
     */
    public static final int HORIZ_ALIGN_LEFT = 4;

    /**
     * Used to aling a String to the right in a box
     */
    public static final int HORIZ_ALIGN_RIGHT = 5;

    /**
     * Used to subtract a child from a box, *leaving* the top portion
     */
    public static final int SUBTRACT_FROM_TOP = 6;

    /**
     * Used to subtract a child from a box, *leaving* the bottom portion
     */
    public static final int SUBTRACT_FROM_BOTTOM = 7;

    /**
     * Used to subtract a child from a box, *leaving* the left portion
     */
    public static final int SUBTRACT_FROM_LEFT = 8;

    /**
     * Used to subtract a child from a box, *leaving" the right portion
     */
    public static final int SUBTRACT_FROM_RIGHT = 9;

    private static final int[] VERT_ALIGNS = {VERT_ALIGN_CENTER,
        VERT_ALIGN_TOP,
        VERT_ALIGN_BOTTOM};

    private static final int[] HORIZ_ALIGNS = {HORIZ_ALIGN_CENTER,
        HORIZ_ALIGN_LEFT,
        HORIZ_ALIGN_RIGHT};

    private static final int[] SUBTRACTS = {SUBTRACT_FROM_TOP,
        SUBTRACT_FROM_BOTTOM,
        SUBTRACT_FROM_LEFT,
        SUBTRACT_FROM_RIGHT};

    /**
     * The point to use for Graphics.drawString()
     */
    private Point drawingPoint;

    /**
     * The absolute, world location of the box
     */
    private Point absoluteLocation;

    /**
     * Link to parent box
     */
    private BoundingBox parent;

    /**
     * If this box was the result of a getStringBounds call, this array will
     * hold the broken strings
     */
    private String[] stringArray;

    /**
     * The string specified in getStringBounds
     */
    private String fullString;

    /**
     * Creates a new <code>BoundingBox</code> instance.
     *
     * @param p a <code>Point</code>, upper left coords
     * @param d a <code>Dimension</code>, used to determine height and width
     */
    public BoundingBox(Point p, Dimension d) {
        super(p, d);
        this.drawingPoint = this.getLocation();
        this.absoluteLocation = this.getLocation();
    }

    /**
     * <p>
     * Returns true if this box has a parent. The 'world', or enclosing canvas
     * is not considered a parent</p>
     *
     * @return a <code>boolean</code> value
     */
    public boolean hasParent() {
        return parent != null;
    }

    /**
     * <p>
     * Get this box's parent box</p>
     *
     * @return a <code>BoundingBox</code> value
     */
    public BoundingBox getParent() {
        return parent;
    }

    /**
     * <p>
     * Make the specified box this box's child. Equivalent to
     * <code>child.setParent(parent)</code> where the specified 'parent' is this
     * instance</p>
     *
     * @param child a <code>BoundingBox</code>, any box that can fit inside this
     * one. The results of calling <code>getAbsoluteLocation()</code> on the
     * child will be altered after this to take into account the child's new
     * location in the 'world'
     *
     */
    public void add(BoundingBox child) {
        child.setParent(this);
    }

    /**
     * <p>
     * Make the specified box this box's parent</p>
     *
     * @param parent a <code>BoundingBox</code> value
     */
    public void setParent(BoundingBox parent) {
        // Prevent infinite recursion
        if (this == parent) {
            return;
        }
        this.parent = parent;

        // If this box was created empty, without a String inside,
        // determine its absolute location
        if (this.getLocation().equals(this.getAbsoluteLocation())) {
            int ancestorTranslateX = 0;
            int ancestorTranslateY = 0;

            BoundingBox ancestor = this;
            while (ancestor.hasParent()) {
                BoundingBox oldRef = ancestor;
                ancestor = ancestor.getParent();
                // Prevent infinite recursion
                if (ancestor == oldRef) {
                    break;
                }
                ancestorTranslateX += (int) ancestor.getLocation().getX();
                ancestorTranslateY += (int) ancestor.getLocation().getY();
            }

            this.getAbsoluteLocation().translate(ancestorTranslateX,
                    ancestorTranslateY);
        } // end if
    } // end setParent

    /**
     * <p>
     * Get the wrapped strings if this box was from a call to getStringBounds,
     * otherwise this method returns null</p>
     *
     * @return a <code>String[]</code> array of strings, top to bottom in layout
     */
    public String[] getStringArray() {
        return stringArray;
    } // end getStringArray

    /**
     * <p>
     * Set the value of the string array</p>
     *
     * @param strArray a <code>String</code> array
     *
     */
    public void setStringArray(String[] strArray) {
        this.stringArray = strArray;
    }

    /**
     * <p>
     * Set the absolute upper left world location point for this box</p>
     *
     * @param point a <code>Point</code> value
     */
    public void setAbsoluteLocation(Point point) {
        this.absoluteLocation = point;
    }

    /**
     * <p>
     * Returns false if for any reason this box has negative dimensions</p>
     *
     * @return true if the box has positive height and width, false otherwise.
     */
    public boolean boxExists() {
        return (this.getHeight() > 0 && this.getWidth() > 0);
    } // end boxExists

    /**
     * <p>
     * Get the absolute upper left location point for this box</p>
     *
     * @return a <code>Point</code> value
     */
    public Point getAbsoluteLocation() {
        return absoluteLocation;
    }

    /**
     * <p>
     * Returns the full string associated with a call to
     * <code>getStringBounds</code></p>
     *
     * @return the full string.
     */
    public String getFullString() {
        return fullString;
    }

    /**
     * <p>
     * Sets the full string associated with <code>getStringBounds</code></p>
     *
     * @param string a <code>String</code>
     */
    public void setFullString(String string) {
        this.fullString = string;
    }

    /**
     * <p>
     * Gets the location of a String after it is adjusted for alignment within
     * this box. The point's coordinates are either within this box or within
     * the enclosing area.</p>
     *
     * @param string a <code>String</code>, the String to be placed
     * @param hAlign an <code>int</code>, HORIZ_ALIGN_CENTER, HORIZ_ALIGN_LEFT,
     * HORIX_ALIGN_RIGHT
     * @param vAlign an <code>int</code>, VERT_ALIGN_CENTER, VERT_ALIGN_TOP,
     * VERT_ALIGN_BOTTOM
     * @param fm a <code>FontMetrics</code> object for this String
     * @param padding an <code>int</code>, the padding around the String
     * @param enforce a <code>boolean</code>, if true the method will throw an
     * exception when the string is too big, if not true it will break the
     * string down and overrun the bottom of the box. If the box is too small
     * for even one word, the exception will be thrown
     * @return a <code>Point</code>, the coords to use in drawString()
     * @see #HORIZ_ALIGN_LEFT
     * @see #HORIZ_ALIGN_CENTER
     * @see #HORIZ_ALIGN_RIGHT
     * @see #VERT_ALIGN_TOP
     * @see #VERT_ALIGN_CENTER
     * @see #VERT_ALIGN_BOTTOM
     * @throws IllegalArgumentException if the args are invalid
     * @throws StringTooLongException if the string won't fit and enforce is set
     * to true. The exception can still be thrown if enforce is false, but only
     * in cases such as the box having no height or width
     */
    public BoundingBox getStringBounds(String string,
            int hAlign,
            int vAlign,
            FontMetrics fm,
            int padding,
            boolean enforce)
            throws IllegalArgumentException, StringTooLongException {
        // Check to make sure the values passed in are valid
        if (!checkHAlign(hAlign)) {
            throw new IllegalArgumentException("BoundingBox.getStringBounds, "
                    + "hAlign invalid : " + hAlign);
        }
        if (!checkVAlign(vAlign)) {
            throw new IllegalArgumentException("BoundingBox.getStringBounds, "
                    + "vAlign invalid : " + hAlign);
        }
        if (fm == null) {
            throw new IllegalArgumentException("BoundingBox.getStringBounds, "
                    + "FontMetrics null");
        }
        if (string == null) {
            throw new IllegalArgumentException("BoundingBox.getStringBounds, "
                    + "String null");
        }

        // NOTE: For this portion of the method, parent refers 
        // to this object and child refers to the object about 
        // to be created.  When the absolute point for drawing the 
        // String is determined, this object's ancestors are checked.
        Dimension parentSize = this.getSize();

        Point childLocation;
        Dimension childSize;

        // String ascent, width, height, parent, child width, height
        int sa, sw, sh, pw, ph, cw, ch;

        // Child, parent x, y coords for upper left
        int cx, cy, px, py;

        sa = fm.getMaxAscent();
        sw = fm.stringWidth(string);
        sh = sa + fm.getMaxDescent();
        pw = (int) parentSize.getWidth();
        ph = (int) parentSize.getHeight();
        if (pw < 0) {
            throw new StringTooLongException("The parent box has a negative width "
                    + " (" + pw + ")");
        }
        if (ph < 0) {
            throw new StringTooLongException("The parent box has a negative height"
                    + " (" + ph + ")");
        }
        cw = sw + padding * 2;
        ch = sh + padding * 2;
        px = (int) this.getX();
        py = (int) this.getY();

        String[] childStrArray = null;

        if ((cw > pw) || (string.indexOf("\n") != -1)) {
            cw = pw - (padding * 2);
            childStrArray = createStringArray(string, fm, padding, pw);
            ch = getWrappedHeight(childStrArray, fm, padding);
            if (ch > ph) {
                // If enforce is not true, it means we want the box to 
                // be returned anyway (along with the strings in the array)
                // so we can chop them manually and try again
                if (enforce) {
                    throw new StringTooLongException("The wrapped strings do not "
                            + "fit into the parent box, pw=" + pw
                            + ", ph=" + ph + ", ch=" + ch + ", cw=" + cw
                            + ", string: " + string);
                }
            }
        }

        // Need to have child width and height, and string array set
        // Child location is relative to this (parent) box, not the world
        if (vAlign == VERT_ALIGN_TOP) {
            cy = 0;
        } else if (vAlign == VERT_ALIGN_CENTER) {
            cy = (ph / 2) - (ch / 2);
        } else {
            cy = ph - ch;
        }

        if (hAlign == HORIZ_ALIGN_LEFT) {
            cx = 0;
        } else if (hAlign == HORIZ_ALIGN_CENTER) {
            cx = (pw / 2) - (cw / 2);
        } else {
            cx = pw - cw;
        }

        childLocation = new Point(cx, cy);
        childSize = new Dimension(cw, ch);

        // Drawing location is based on the baseline of the String, and 
        // relative to the world, not this box.  The drawing point differs 
        // from the absolute box location because of padding and ascent
        int dpx, dpy, abx, aby;

        // If this object also has a parent (maybe grandparents), iterate 
        // through them and find the absolute 'world' location
        int ancestorTranslateX = 0;
        int ancestorTranslateY = 0;

        BoundingBox ancestor = this;
        while (ancestor.hasParent()) {
            BoundingBox oldRef = ancestor;
            ancestor = ancestor.getParent();
            // Prevent infinite recursion
            if (ancestor == oldRef) {
                break;
            }
            ancestorTranslateX += (int) ancestor.getLocation().getX();
            ancestorTranslateY += (int) ancestor.getLocation().getY();
        }

        // Determine the absolute location for the box
        abx = px + cx + ancestorTranslateX;
        aby = py + cy + ancestorTranslateY;

        // Determine the absolute drawing point for the String
        dpx = abx + padding;
        dpy = aby + padding + sa;

        Point drawingPoint = new Point(dpx, dpy);
        BoundingBox returnChild = new BoundingBox(childLocation,
                childSize,
                drawingPoint,
                new Point(abx, aby));
        this.add(returnChild);
        returnChild.setFullString(string);
        returnChild.setStringArray(childStrArray);
        return returnChild;

    } // end getStringBounds

    /**
     * <p>
     * Gets the location of a String after it is adjusted for alignment within
     * this box. The point's coordinates are either within this box or within
     * the enclosing area.</p>
     *
     * <p>
     * By default, this method enforces string length and throws the exception
     * if it is too long</p>
     *
     * @param string a <code>String</code>, the String to be placed
     * @param hAlign an <code>int</code>, HORIZ_ALIGN_CENTER, HORIZ_ALIGN_LEFT,
     * HORIX_ALIGN_RIGHT
     * @param vAlign an <code>int</code>, VERT_ALIGN_CENTER, VERT_ALIGN_TOP,
     * VERT_ALIGN_BOTTOM
     * @param fm a <code>FontMetrics</code> object for this String
     * @param padding an <code>int</code>, the padding around the String
     * @return a <code>Point</code>, the coords to use in drawString()
     * @throws IllegalArgumentException if the args are invalid
     * @throws StringTooLongException if the string won't fit
     */
    public BoundingBox getStringBounds(String string,
            int hAlign,
            int vAlign,
            FontMetrics fm,
            int padding)
            throws StringTooLongException, IllegalArgumentException {
        return getStringBounds(string, hAlign, vAlign, fm, padding, true);
    } // end getStringBounds (enforce true by default)

    /**
     * <p>
     * This method is called after getting the box by calling
     * <code>getStringBounds</code> on the parent. Wraps the string at word
     * boundaries and draws it to the specified <code>Graphics</code> context.
     * Make sure padding is the same as specified for the
     * <code>getStringBounds</code> call, or you may get an unexpected
     * {@link gnu.jpdf.StringTooLongException}</p>
     *
     * @param g the <code>Graphics</code> object
     * @param fm the <code>FontMetrics</code> to use for sizing
     * @param padding an int, the padding around the strings
     * @param hAlign the <code>int</code> horizontal alignment
     * @throws IllegalArgumentException if the args are invalid
     * @throws StringTooLongException if the string won't fit this will only
     * happen if the fm or padding has been changed since getStringBounds was
     * called succesfully
     */
    public void drawWrappedString(Graphics g,
            FontMetrics fm,
            int padding,
            int hAlign)
            throws IllegalArgumentException, StringTooLongException {
        if (getStringArray() == null) {
            Point p = getDrawingPoint();
            int xx = (int) p.getX();
            int yy = (int) p.getY();
            g.drawString(getFullString(), xx, yy);
        } else {
            int len = stringArray.length;
            for (int i = 0; i < len; i++) {
                BoundingBox wrappedBox = null;
                wrappedBox = getStringBounds(stringArray[i],
                        hAlign,
                        BoundingBox.VERT_ALIGN_TOP,
                        fm,
                        0);
                Point pp = wrappedBox.getDrawingPoint();
                int xx = (int) pp.getX();
                if (hAlign == BoundingBox.HORIZ_ALIGN_RIGHT) {
                    xx -= padding;
                }
                if (hAlign == BoundingBox.HORIZ_ALIGN_LEFT) {
                    xx += padding;
                }
                int yy = (int) pp.getY() + padding;
                g.drawString(stringArray[i], xx, yy);
                subtract(wrappedBox, BoundingBox.SUBTRACT_FROM_BOTTOM);
            }
        }
    } // end drawWrappedString

    /**
     * <p>
     * Draws lines from the wrapped string until there is no more room and then
     * stops. If there is no string or the box is too small for anything to be
     * drawn, does nothing</p>
     *
     * @param g the <code>Graphics</code> object to draw to
     * @param fm the <code>FontMetrics</code> object to use for string sizing
     * @param padding the <code>int</code> amount of padding around the string
     * @param hAlign the <code>int</code> horizontal alignment
     *
     */
    public void drawWrappedStringTruncate(Graphics g,
            FontMetrics fm,
            int padding,
            int hAlign) {

        if (getStringArray() == null) {
            Point p = getDrawingPoint();
            int xx = (int) p.getX();
            int yy = (int) p.getY();
            if (getFullString() != null) {
                g.drawString(getFullString(), xx, yy);
            } else {
                System.err.println("getStringArray and getFullString are null");
            }
        } else {
            int totalHeight = 0;
            int len = stringArray.length;
            for (int i = 0; i < len; i++) {
                BoundingBox wrappedBox = null;
                try {
                    wrappedBox = getStringBounds(stringArray[i],
                            hAlign,
                            BoundingBox.VERT_ALIGN_TOP,
                            fm,
                            0,
                            false);
                    totalHeight += (int) wrappedBox.getHeight();
                    if (getParent() != null) {
                        if (totalHeight > (int) (getParent().getHeight())) {
                            return;
                        }
                    }
                } catch (StringTooLongException stle) {
                    stle.printStackTrace();
                    return;
                }
                wrappedBox.drawChoppedString(g, fm, padding, hAlign);
                subtract(wrappedBox, BoundingBox.SUBTRACT_FROM_BOTTOM);
            }
        }
    } // end drawWrappedStringTruncate

    /**
     * <p>
     * Take the first line of the string (if it is wrapped, otherwise just take
     * the whole string) and chop the end of it off to make it fit in the box.
     * If the box is smaller than one letter, draw nothing</p>
     *
     * @param g the <code>Graphics</code> object to draw to
     * @param fm the <code>FontMetrics</code> object to use for string sizing
     * @param padding the <code>int</code> amount of padding around the string
     * @param hAlign the <code>int</code> horizontal alignment
     */
    public void drawChoppedString(Graphics g,
            FontMetrics fm,
            int padding,
            int hAlign) {

        String string = "";
        if (getStringArray() != null) {
            string = new String(getStringArray()[0]);
        } else {
            string = new String(getFullString());
        }
        BoundingBox choppedBox = null;
        try {
            choppedBox = getStringBounds(string,
                    hAlign,
                    VERT_ALIGN_TOP,
                    fm,
                    padding);
            Point p = choppedBox.getDrawingPoint();
            int x = (int) p.getX();
            int y = (int) p.getY();
            g.drawString(string, x, y);
        } catch (StringTooLongException stle) {
            // Doesn't fit - start cutting from the end until it does
            StringBuffer buf = new StringBuffer().append(string);
            if (buf.length() == 0) {
                System.out.println("BoundingBox.drawChoppedString, buf len 0 ??");
                //return;
                throw new RuntimeException();
            }
            buf.deleteCharAt(buf.length() - 1);
            while ((fm.stringWidth(buf.toString()) > (int) getWidth())
                    && (buf.length() > 0)) {
                buf.deleteCharAt(buf.length() - 1);
            }

            try {
                choppedBox = getStringBounds(buf.toString(),
                        hAlign,
                        VERT_ALIGN_TOP,
                        fm,
                        padding);
                Point pp = choppedBox.getDrawingPoint();
                int xx = (int) pp.getX();
                int yy = (int) pp.getY();
                g.drawString(string, xx, yy);
            } catch (StringTooLongException sstle) {
                // Must be a really small box!
                sstle.printStackTrace();
            }
        }
    } // end drawChoppedString

    /**
     * <p>
     * Get the total height of the box needed to contain the strings in the
     * specified array</p>
     */
    private int getWrappedHeight(String[] strings, FontMetrics fm, int padding) {
        int ma = fm.getMaxAscent();
        int md = fm.getMaxDescent();
        int sh = ma + md;
        int hPad = sh / LINE_SPACING_PERCENTAGE;
        sh += hPad;
        int total = sh * strings.length;

        return total + (padding * 2);
    } // end getWrappedHeight

    /**
     *
     * <p>
     * Make a string array from a string, wrapped to fit the box</p>
     *
     * <p>
     * If the line width is too short, the array is just a tokenized version of
     * the string</p>
     *
     * @param string - the <code>String</code> to convert to an array
     * @param
     */
    private String[] createStringArray(String string,
            FontMetrics fm,
            int padding,
            int pw) {
        if (string == null) {
            System.err.println("Tried createStringArray with null String");
            return null;
        }
        if (fm == null) {
            System.err.println("Tried createStringArray with null FontMetrics");
        }

        int lw = pw - (padding * 2);

        Vector<String> returnVector = new Vector<String>();
        // Return delimiters as tokens
        StringTokenizer st = new StringTokenizer(string, " \t\n\r\f", true);
        StringBuffer tempBuffer = new StringBuffer();
        StringBuffer finalBuffer = new StringBuffer();

        while (st.hasMoreTokens()) {
            // Get the next word and add a space after it 
            String tempString = st.nextToken();
            tempBuffer.append(tempString);

            // If we haven't reached the width with our current 
            // line, keep adding tokens.  Also, check for hard returns
            if ((fm.stringWidth(tempBuffer.toString()) < lw)
                    && (tempBuffer.toString()
                            .charAt(tempBuffer.toString().length() - 1) != '\n')
                    && (tempBuffer.toString()
                            .charAt(tempBuffer.toString().length() - 1) != '\r')) {
                finalBuffer.append(tempString);
                continue;
            }
            returnVector.addElement(finalBuffer.toString());
            finalBuffer.delete(0, finalBuffer.length());
            tempBuffer.delete(0, tempBuffer.length());
            if ((tempString.charAt(0) != '\n')
                    && (tempString.charAt(0) != '\r')) {
                tempBuffer.append(tempString);
                finalBuffer.append(tempString);
            }
            continue;

        } // end while
        returnVector.addElement(finalBuffer.toString());

        int len = returnVector.size();
        // Init the class member field stringArray
        String[] childStrArray = new String[len];
        for (int i = 0; i < len; i++) {
            String curStr = (String) returnVector.get(i);
            childStrArray[i] = curStr;
        }

        return childStrArray;

    } // end createStringArray 

    /**
     * <p>
     * Removes the child box from this parent box. The child must have this
     * object as its parent or the method does nothing. The BoundingBox returned
     * will be cut by an area equal to the child area plus the horizontal or
     * vertical strip in which it sits, depending on the 'subtractFrom' value
     * passed in</p>
     *
     * @param child a <code>BoundingBox</code> value
     * @param subtractFrom an <code>int</code>, SUBTRACT_FROM_LEFT,
     * SUBTRACT_FROM_RIGHT, SUBTRACT_FROM_TOP, SUBTRACT_FROM_BOTTOM
     * @return a <code>BoundingBox</code> value
     * @see #SUBTRACT_FROM_LEFT
     * @see #SUBTRACT_FROM_RIGHT
     * @see #SUBTRACT_FROM_TOP
     * @see #SUBTRACT_FROM_BOTTOM
     */
    public BoundingBox subtract(BoundingBox child, int subtractFrom) {
        // First, check to see if the params are valid
        if (child == null) {
            throw new IllegalArgumentException("BoundingBox.subtract, "
                    + "BoundingBox child is null");
        }
        if (!child.hasParent()) {
            throw new IllegalArgumentException("BoundingBox.subtract, "
                    + "BoundingBox child has no parent");
        }
        if (!(child.getParent() == this)) {
            throw new IllegalArgumentException("BoundingBox.subtract, "
                    + "this is not BoundingBox child's parent");
        }
        // Now that we know the child is this object's child, we continue
        // and check the subtractFrom param
        int len = SUBTRACTS.length;
        boolean valid = false;
        for (int i = 0; i < len; i++) {
            if (subtractFrom == SUBTRACTS[i]) {
                valid = true;
            }
        }
        if (!valid) {
            throw new IllegalArgumentException("BoundingBox.subtract, "
                    + "subtractFrom invalid: " + subtractFrom);
        }

        // Now we know the child is valid, and if the subtractFrom
        // preference was invalid, we subtract from the bottom
        // The child should no longer be used, since the parent
        // reference will be invalid
        child.setParent(null);

        int cx = (int) child.getLocation().getX();
        int cy = (int) child.getLocation().getY();
        int cw = (int) child.getSize().getWidth();
        int ch = (int) child.getSize().getHeight();
        int px = (int) this.getLocation().getX();
        int py = (int) this.getLocation().getY();
        int pw = (int) this.getSize().getWidth();
        int ph = (int) this.getSize().getHeight();

        switch (subtractFrom) {
            case SUBTRACT_FROM_LEFT:
                // This will be useful for right-justified Strings in tables
                pw = cx;
                this.setSize(new Dimension(pw, ph));
                return this;

            case SUBTRACT_FROM_RIGHT:
                // This will be useful for left justified Strings in tables
                px = px + cw + cx;
                pw = pw - cw - cx;
                this.setLocation(new Point(px, py));
                this.setSize(new Dimension(pw, ph));
                return this;

            case SUBTRACT_FROM_BOTTOM:
                py = py + ch + cy;
                ph = ph - ch - cy;
                this.setLocation(new Point(px, py));
                this.setSize(new Dimension(pw, ph));
                return this;

            case SUBTRACT_FROM_TOP:
                ph = cy;
                this.setSize(new Dimension(pw, ph));
                return this;

            default: // Should never happen
                break;
        } // end switch
        return this;
    } // end subtract

    /**
     * <p>
     * Gets the drawing point to use in Graphics drawing methods. After getting
     * a new BoundingBox with getStringBounds(), calling this method will give
     * you an absolute point, accounting for alignment and padding, etc, from
     * which to start drawing the String
     * </p>
     *
     * <p>
     * If getStringBounds was not called (this is a parent box), the upper left
     * coordinates will be returned (this.getLocation())
     * </p>
     *
     * @return a <code>Point</code>
     */
    public Point getDrawingPoint() {
        return drawingPoint;
    }

    // main method is for testing /////////////////
    /**
     * For testing
     *
     * @param args a <code>String[]</code> value
     */
    public static void main(String[] args) {
        Point upperLeft = new Point(5, 5);
        Dimension bounds = new Dimension(100, 100);
        BoundingBox parent = new BoundingBox(upperLeft, bounds);
        String string = "Hello World!";
        Font font = new Font("SansSerif", Font.PLAIN, 12);
        Frame frame = new Frame();
        frame.addNotify();
        try {
            Image image = frame.createImage(100, 100);
            if (image == null) {
                System.err.println("image is null");
            }
            Graphics graphics = image.getGraphics();
            FontMetrics fm = graphics.getFontMetrics(font);
            BoundingBox child = parent
                    .getStringBounds(string,
                            BoundingBox.HORIZ_ALIGN_LEFT,
                            BoundingBox.VERT_ALIGN_TOP,
                            fm,
                            5);
            System.out.println("Drawing Point: "
                    + child.getDrawingPoint().toString());
            System.out.println("Now testing subtract() method...");

            parent = new BoundingBox(new Point(10, 10), new Dimension(300, 300));
            System.out.println("parent: " + parent.toString());
            child = new BoundingBox(new Point(90, 110), new Dimension(100, 100));
            parent.add(child);
            System.out.println("child: " + child.toString());
            System.out.println();
            System.out.println("subtracting the child from the parent");
            System.out.println("SUBTRACT_FROM_TOP: ");
            parent = parent.subtract(child, SUBTRACT_FROM_TOP);
            System.out.println("new parent: " + parent.toString());
            System.out.println();
            System.out.println("Resetting parent");
            parent = new BoundingBox(new Point(10, 10), new Dimension(300, 300));
            parent.add(child);
            System.out.println("SUBTRACT_FROM_BOTTOM");
            parent.subtract(child, SUBTRACT_FROM_BOTTOM);
            System.out.println("new parent: " + parent.toString());
            System.out.println();
            System.out.println("Resetting parent");
            parent = new BoundingBox(new Point(10, 10), new Dimension(300, 300));
            parent.add(child);
            System.out.println("SUBTRACT_FROM_LEFT");
            parent.subtract(child, SUBTRACT_FROM_LEFT);
            System.out.println("new parent: " + parent.toString());
            System.out.println();
            System.out.println("Resetting parent");
            parent = new BoundingBox(new Point(10, 10), new Dimension(300, 300));
            parent.add(child);
            System.out.println("SUBTRACT_FROM_RIGHT");
            parent.subtract(child, SUBTRACT_FROM_RIGHT);
            System.out.println("new parent: " + parent.toString());
            System.out.println();

            System.exit(0);
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    // Private methods /////////////////////////////
    /**
     * Creates a new <code>BoundingBox</code> instance.
     *
     * @param p a <code>Point</code> value
     * @param d a <code>Dimension</code> value
     * @param drawingPoint a <code>Point</code> value
     */
    private BoundingBox(Point p,
            Dimension d,
            Point drawingPoint,
            Point absolute) {
        super(p, d);
        this.drawingPoint = drawingPoint;
        this.absoluteLocation = absolute;
    }

    /**
     * <p>
     * Checks the horizontal alignment passed into a method to make sure it is
     * one of the valid values</p>
     *
     * @param hAlign an <code>int</code> value
     * @return a <code>boolean</code> value
     */
    private boolean checkHAlign(int hAlign) {
        int len = HORIZ_ALIGNS.length;
        for (int i = 0; i < len; i++) {
            if (hAlign == HORIZ_ALIGNS[i]) {
                return true;
            }
        }
        return false;
    }

    /**
     * <p>
     * Checks the vertical alignment passed into a method to make sure it is one
     * of the valid values</p>
     *
     * @param vAlign an <code>int</code> value
     * @return a <code>boolean</code> value
     */
    private boolean checkVAlign(int vAlign) {
        int len = VERT_ALIGNS.length;
        for (int i = 0; i < len; i++) {
            if (vAlign == VERT_ALIGNS[i]) {
                return true;
            }
        }
        return false;
    }

} // end class BoundingBox

