/*
 * Copyright (C) 2005-2006 Sun Microsystems, Inc. All rights reserved. Use is
 * subject to license terms.
 */ 

package org.jdesktop.layout;

import javax.swing.border.EmptyBorder;
import org.jdesktop.layout.*;
import java.awt.*;
import java.lang.reflect.*;
import javax.swing.*;
import java.util.*;

/**
 * An implementation of <code>LayoutStyle</code> for Mac OS X Tiger.
 * <p>
 * The information used for this layout style comes from:
 * http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/
 *
 * @author  Werner Randelshofer
 * @version $Revision: 1.3 $
 */
class AquaLayoutStyle extends LayoutStyle {
    private static final Insets EMPTY_INSETS = new Insets(0, 0, 0, 0);
    /** Mini size style. */
    private final static int MINI = 0;
    /** Small size style. */
    private final static int SMALL = 1;
    /** Regular size style. */
    private final static int REGULAR = 2;
    

    /**
     * The containerGapDefinitions array defines the preferred insets (child gaps)
     * of a parent container towards one of its child components.
     *
     * Note: As of now, we do not yet specify the preferred gap from a child
     * to its parent. Therefore we may not be able to treat all special cases.
     *
     * This array is used to initialize the containerGaps HashMap.
     *
     * The array has the following structure, which is supposed to be a
     * a compromise between legibility and code size.
     * containerGapDefinitions[0..n] = preferred insets for some parent UI's
     * containerGapDefinitions[][0..m-3] = name of parent UI,
     *                                 optionally followed by a full stop and
     *                                 a style name
     * containerGapDefinitions[][m-2] = mini insets
     * containerGapDefinitions[][m-1] = small insets
     * containerGapDefinitions[][m] = regular insets
     */
    private final static Object[][] containerGapDefinitions = {
        // Format:
        // { list of parent UI's,
        //   mini insets, small insets, regular insets }
        
        { "TabbedPaneUI",
          new Insets(6,10,10,10), new Insets(6,10,10,12),
          new Insets(12,20,20,20)
        },
        
        // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGLayout/chapter_19_section_3.html#//apple_ref/doc/uid/TP30000360/DontLinkElementID_27
        // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGLayout/chapter_19_section_3.html#//apple_ref/doc/uid/TP30000360/DontLinkElementID_26
        // note for small and mini size: leave 8 to 10 pixels on top
        // note for regular size: leave only 12 pixel at top if tabbed pane UI
        { "RootPaneUI",
          new Insets(8,10,10,10), new Insets(8,10,10,12),
          new Insets(14,20,20,20)
        },
        
        // These child gaps are used for all other components
        { "default",
          new Insets(8,10,10,10), new Insets(8,10,10,12),
          new Insets(14,20,20,20)
        },
    };
    
    /**
     * The relatedGapDefinitions table defines the preferred gaps
     * of one party of two related components.
     *
     * The effective preferred gap is the maximum of the preferred gaps of
     * both parties.
     *
     * This array is used to initialize the relatedGaps HashMap.
     *
     * The array has the following structure, which is supposed to be a
     * a compromise between legibility and code size.
     * containerGapDefinitions[0..n] = preferred gaps for a party of a two related UI's
     * containerGapDefinitions[][0..m-3] = name of UI
     *                                 optionally followed by a full stop and
     *                                 a style name
     * containerGapDefinitions[][m-2] = mini insets
     * containerGapDefinitions[][m-1] = small insets
     * containerGapDefinitions[][m] = regular insets
     */
    private final static Object[][] relatedGapDefinitions = {
        // Format:
        // { list of UI's,
        //   mini insets, small insets, regular insets }
        
        // Push Button:
        // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_2.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF104
        { "ButtonUI", "ButtonUI.push", "ButtonUI.text",
          "ToggleButtonUI.push", "ToggleButtonUI.text",
          new Insets(8,8,8,8), new Insets(10,10,10,10), new Insets(12,12,12,12)
        },
        
        // Metal Button
        // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_2.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF187
        { "ButtonUI.metal", "ToggleButtonUI.metal",
          new Insets(8,8,8,8), new Insets(8,8,8,8), new Insets(12,12,12,12)
        },
        
        // Bevel Button (Rounded and Square)
        // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_2.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF112
        { "ButtonUI.bevel", "ButtonUI.toggle", "ButtonUI.square",
          "ToggleButtonUI", "ToggleButtonUI.bevel", "ToggleButtonUI.square",
          "ToggleButtonUI.toggle",
          new Insets(0,0,0,0), new Insets(0,0,0,0), new Insets(0,0,0,0)
        },
        
        // Bevel Button (Rounded and Square)
        // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_2.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF112
        { "ButtonUI.bevel.largeIcon", "ToggleButtonUI.bevel.largeIcon",
          new Insets(8,8,8,8), new Insets(8,8,8,8), new Insets(8,8,8,8)
        },
        
        // Icon Button
        // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_2.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF189
        { "ButtonUI.icon",
          new Insets(0,0,0,0), new Insets(0,0,0,0), new Insets(0,0,0,0)
        },
        { "ButtonUI.icon.largeIcon",
          new Insets(8,8,8,8), new Insets(8,8,8,8), new Insets(8,8,8,8)
        },
        
        // Round Button
        // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_2.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF191
        { "ButtonUI.round", "ToggleButtonUI.round",
          new Insets(12,12,12,12), new Insets(12,12,12,12),
          new Insets(12,12,12,12)
        },
        
        // Help Button
        // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_2.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF193
        { "ButtonUI.help",
          new Insets(12,12,12,12), new Insets(12,12,12,12),
          new Insets(12,12,12,12)
        },
        
        // Segmented Control
        // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_3.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF196
        { "ButtonUI.toggleCenter", "ToggleButtonUI.toggleCenter",
          new Insets(8,0,8,0), new Insets(10,0,10,0), new Insets(12,0,12,0)
        },
        { "ButtonUI.toggleEast", "ToggleButtonUI.toggleEast",
          new Insets(8,0,8,8), new Insets(10,0,10,10), new Insets(12,0,12,12)
        },
        { "ButtonUI.toggleWest", "ToggleButtonUI.toggleWest",
          new Insets(8,8,8,0), new Insets(10,10,10,0), new Insets(12,12,12,0)
        },
        
        { "ButtonUI.toolBarTab", "ToggleButtonUI.toolBarTab",
          new Insets(0,0,0,0), new Insets(0,0,0,0), new Insets(0,0,0,0)
        },
        
        // Color Well Button
        // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_3.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF213
        { "ButtonUI.colorWell", "ToggleButtonUI.colorWell",
          new Insets(0,0,0,0), new Insets(0,0,0,0), new Insets(0,0,0,0)
        },
        
        // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_3.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF198
        // FIXME - The following values are given in the AHIG.
        // In reality, the values further below seem to be more appropriate.
        // Which ones are right?
        //{ "CheckBoxUI", new Insets(7, 5, 7, 5), new Insets(8, 6, 8, 6), new Insets(8, 8, 8, 8) },
        { "CheckBoxUI",
          new Insets(6, 5, 6, 5), new Insets(7, 6, 7, 6), new Insets(7, 6, 7, 6)
        },
        
        // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_3.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF198
        { "ComboBoxUI",
          new Insets(8, 5, 8, 5), new Insets(10, 6, 10, 6),
          new Insets(12, 8, 12, 8)
        },
        
        // There is no spacing given for labels.
        // This comes from playing with IB.
        // We use the values here, which is the minimum of the spacing of all
        // other components.
        { "LabelUI",
          new Insets(8, 8, 8, 8), new Insets(8, 8, 8, 8), new Insets(8, 8, 8, 8)
        },
        
        // ? spacing not given
        { "ListUI",
          new Insets(5, 5, 5, 5), new Insets(6, 6, 6, 6), new Insets(6, 6, 6, 6)
        },
        
        // ? spacing not given
        { "PanelUI",
          new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0)
        },
        
        // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_5.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF106
        // ? spacing not given
        { "ProgressBarUI",
          new Insets(8,8,8,8), new Insets(10,10,10,10), new Insets(12,12,12,12)
        },
        
        // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_3.html#//apple_ref/doc/uid/20000957-TP30000359-BIAHBFAD
        { "RadioButtonUI",
          new Insets(5, 5, 5, 5), new Insets(6, 6, 6, 6), new Insets(6, 6, 6, 6)
        },
        
        //http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_6.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF114
        // ? spacing not given. We use the same as for text fields.
        { "ScrollPaneUI",
          new Insets(6, 8, 6, 8), new Insets(6, 8, 6, 8),
          new Insets(8, 10, 8, 10)
        },
        
        //http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_8.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF214
        // ? spacing not given
        //http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGLayout/chapter_19_section_2.html#//apple_ref/doc/uid/20000957-TP30000360-CHDEACGD
        { "SeparatorUI",
          new Insets(8, 8, 8, 8), new Insets(10, 10, 10, 10),
          new Insets(12, 12, 12, 12)
        },
        
        // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_4.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF115
        { "SliderUI.horizontal",
          new Insets(8,8,8,8), new Insets(10,10,10,10), new Insets(12,12,12,12)
        },
        { "SliderUI.vertical",
          new Insets(8,8,8,8), new Insets(10,10,10,10), new Insets(12,12,12,12)
        },
        
        //http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_4.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF204
        { "SpinnerUI",
          new Insets(6, 8, 6, 8), new Insets(6, 8, 6, 8),
          new Insets(8, 10, 8, 10)
        },
        
        // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_7.html#//apple_ref/doc/uid/20000957-TP30000359-CHDDBIJE
        // ? spacing not given
        { "SplitPaneUI",
          new Insets(0,0,0,0), new Insets(0,0,0,0), new Insets(0,0,0,0)
        },
        // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_7.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF105
        // ? spacing not given
        { "TabbedPaneUI",
          new Insets(0,0,0,0), new Insets(0,0,0,0), new Insets(0,0,0,0)
        },
        { "TableUI",
          new Insets(0,0,0,0), new Insets(0,0,0,0), new Insets(0,0,0,0)
        },
        // ? spacing not given
        { "TextAreaUI", "EditorPaneUI", "TextPaneUI",
          new Insets(0,0,0,0), new Insets(0,0,0,0), new Insets(0,0,0,0)
        },
        //http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_6.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF225
        { "TextFieldUI", "FormattedTextFieldUI", "PasswordFieldUI",
          new Insets(6, 8, 6, 8), new Insets(6, 8, 6, 8),
          new Insets(8, 10, 8, 10)
        },
        
        // ? spacing not given
        { "TreeUI",
          new Insets(0,0,0,0), new Insets(0,0,0,0), new Insets(0,0,0,0)
        },
    };
    private final static Object[][] unrelatedGapDefinitions = {
        // UI, mini, small, regular
        { "ButtonUI.help",
          new Insets(24,24,24,24), new Insets(24,24,24,24),
          new Insets(24,24,24,24)
        },
        { "default",
          new Insets(10, 10, 10, 10), new Insets(12, 12, 12, 12),
          new Insets(14, 14, 14, 14)
        },
    };
    /**
     * The indentGapDefinitions table defines the preferred indentation
     * for components that are indented after the specified component.
     *
     * This array is used to initialize the indentGaps HashMap.
     *
     * The array has the following structure, which is supposed to be a
     * a compromise between legibility and code size.
     * indentGapDefinitions[0..n] = preferred gaps for a party of a two related UI's
     * indentGapDefinitions[][0..m-3] = name of UI
     *                                 optionally followed by a full stop and
     *                                 a style name
     * indentGapDefinitions[][m-2] = mini insets
     * indentGapDefinitions[][m-1] = small insets
     * indentGapDefinitions[][m] = regular insets
     */
    private final static Object[][] indentGapDefinitions = {
        // UI, mini, small, regular
        
        // The Aqua L&F does not scale button images of check boxes and radio 
        // buttons. Therefore we use to the same horizontal indents for all sizes.
        { "CheckBoxUI", "RadioButtonUI",
          new Insets(16, 24, 16, 24), new Insets(20, 24, 20, 24),
          new Insets(24, 24, 24, 24) },
          
        { "default",
          new Insets(16, 16, 16, 16), new Insets(20, 20, 20, 20),
          new Insets(24, 24, 24, 24) },
    };
    /**
     * The visualMarginDefinition table defines the visually perceived
     * margin of the components.
     *
     * This array is used to initialize the visualMargins HashMap.
     *
     * The array has the following structure, which is supposed to be a
     * a compromise between legibility and code size.
     * visualMarginDefinitions[0..n] = preferred gaps for a party of a two related UI's
     * visualMarginDefinitions[][0..m-1] = name of UI
     *                                 optionally followed by a full stop and
     *                                 a style name
     * containerGapDefinitions[][m] = visual margins
     */
    private final static Object[][] visualMarginDefinitions = {
        // UI, regular
        { "ButtonUI", "ButtonUI.text",
          "ToggleButtonUI", "ToggleButtonUI.text",
          new Insets(5, 3, 3, 3)
        },
        { "ButtonUI.icon",
          "ToggleButtonUI.icon",
          new Insets(5, 2, 3, 2)
        },
        { "ButtonUI.toolbar",
          "ToggleButtonUI.toolbar",
          new Insets(0, 0, 0, 0)
        },
        { "CheckBoxUI", new Insets(4, 4, 3, 3) },
        { "ComboBoxUI", new Insets(2, 3, 4, 3) },
        { "DesktopPaneUI", new Insets(0, 0, 0, 0) },
        { "EditorPaneUI", "TextAreaUI", "TextPaneUI",
          new Insets(0, 0, 0, 0)
        },
        { "FormattedTextFieldUI", "PasswordFieldUI", "TextFieldUI",
          new Insets(0, 0, 0, 0)
        },
        { "LabelUI", new Insets(0, 0, 0, 0) },
        { "ListUI", new Insets(0, 0, 0, 0) },
        { "PanelUI", new Insets(0, 0, 0, 0) },
        { "ProgressBarUI", "ProgressBarUI.horizontal", new Insets(0, 2, 4, 2) },
        { "ProgressBarUI.vertical", new Insets(2, 0, 2, 4) },
        { "RadioButtonUI", new Insets(4, 4, 3, 3) },
        { "ScrollBarUI", new Insets(0, 0, 0, 0) },
        { "ScrollPaneUI", new Insets(0, 0, 0, 0) },
        { "SpinnerUI", new Insets(0, 0, 0, 0) },
        { "SeparatorUI", new Insets(0, 0, 0, 0) },
        { "SplitPaneUI", new Insets(0, 0, 0, 0) },
        { "SliderUI", "SliderUI.horizontal", new Insets(3, 6, 3, 6) },
        { "SliderUI.vertical", new Insets(6, 3, 6, 3) },
        { "TabbedPaneUI", "TabbedPaneUI.top", new Insets(5, 7, 10, 7) },
        { "TabbedPaneUI.bottom", new Insets(4, 7, 5, 7) },
        { "TabbedPaneUI.left", new Insets(4, 6, 10, 7) },
        { "TabbedPaneUI.right", new Insets(4, 7, 10, 6) },
        { "TableUI", new Insets(0, 0, 0, 0) },
        { "TreeUI", new Insets(0, 0, 0, 0) },
        { "default", new Insets(0, 0, 0, 0) },
    };

    /**
     * The relatedGaps map defines the preferred gaps
     * of one party of two related components.
     */
    private final static Map RELATED_GAPS = createInsetsMap(relatedGapDefinitions);
    /**
     * The unrelatedGaps map defines the preferred gaps
     * of one party of two unrelated components.
     */
    private final static Map UNRELATED_GAPS = createInsetsMap(unrelatedGapDefinitions);
    /**
     * The containerGaps map defines the preferred insets (child gaps)
     * of a parent component towards one of its children.
     */
    private final static Map CONTAINER_GAPS = createInsetsMap(containerGapDefinitions);
    /**
     * The indentGaps map defines the preferred indentation
     * for components that are indented after the specified component.
     */
    private final static Map INDENT_GAPS = createInsetsMap(indentGapDefinitions);
    /**
     * The visualMargins map defines the preferred indentation
     * for components that are indented after the specified component.
     */
    private final static Map VISUAL_MARGINS = createInsetsMap(visualMarginDefinitions);
    
    /**
     * Creates a map for the specified definitions array.
     * <p>
     * The key for the map is the name of the UI, for example, ButtonUI, with
     * a value of ComponentInsets.  Each ComponentInsets may have sub styles.
     */
    // private static Map<String,ComponentInsets> createInsetsMap(Object[][] definitions) {
    private static Map createInsetsMap(Object[][] definitions) {
        Map map = new HashMap();
        for (int i=0; i < definitions.length; i++) {
            int keys = 0;
            while (keys < definitions[i].length &&
                    (definitions[i][keys] instanceof String)) {
                keys++;
            }
            Insets[] values = new Insets[definitions[i].length - keys];
            for (int j=keys; j < definitions[i].length; j++) {
                values[j-keys] = (Insets) definitions[i][j];
            }
            for (int j=0; j < keys; j++) {
                String key = (String)definitions[i][j];
                int subindex = key.indexOf('.');
                if (subindex == -1) {
                    ComponentInsets componentInsets = (ComponentInsets)map.get(key);
                    if (componentInsets == null) {
                        componentInsets = new ComponentInsets(values);
                        map.put(key, new ComponentInsets(values));
                    } else {
                        assert (componentInsets.getInsets() == null);
                        componentInsets.setInsets(values);
                    }
                } else {
                    String subkey = key.substring(subindex + 1);
                    String parentKey = key.substring(0, subindex);
                    ComponentInsets componentInsets = (ComponentInsets)
                            map.get(parentKey);
                    if (componentInsets == null) {
                        componentInsets = new ComponentInsets();
                        map.put(parentKey, componentInsets);
                    }
                    componentInsets.addSubinsets(subkey,
                            new ComponentInsets(values));
                }
            }
        }
        return map;
    }
    
    public static void main(String[] args) {
        JButton button = new JButton();
        button.putClientProperty("JButton.buttonType", "metal");
        JButton button2 = new JButton();
        LayoutStyle style = new AquaLayoutStyle();
        int gap = 
        style.getPreferredGap(button, button2, LayoutStyle.RELATED, SwingConstants.EAST,
                null);
        System.err.println("gap= " + gap);
        button.putClientProperty("JButton.buttonType", "square");
        button2.putClientProperty("JButton.buttonType", "square");
        gap = 
        style.getPreferredGap(button, button2, LayoutStyle.RELATED, SwingConstants.EAST,
                null);
        System.err.println("gap= " + gap);
    }
    
    /**
     * Creates a new instance.
     */
    public AquaLayoutStyle() {
    }
    
    /**
     * Returns the amount of space to use between two components.
     * The return value indicates the distance to place
     * <code>component2</code> relative to <code>component1</code>.
     * For example, the following returns the amount of space to place
     * between <code>component2</code> and <code>component1</code>
     * when <code>component2</code> is placed vertically above
     * <code>component1</code>:
     * <pre>
     *   int gap = getPreferredGap(component1, component2,
     *                             LayoutStyle.RELATED,
     *                             SwingConstants.NORTH, parent);
     * </pre>
     * The <code>type</code> parameter indicates the type
     * of gap being requested.  It can be one of the following values:
     * <table>
     * <tr><td><code>RELATED</code>
     *     <td>If the two components will be contained in
     *         the same parent and are showing similar logically related
     *         items, use <code>RELATED</code>.
     * <tr><td><code>UNRELATED</code>
     *     <td>If the two components will be
     *          contained in the same parent but show logically unrelated items
     *          use <code>UNRELATED</code>.
     * <tr><td><code>INDENT</code>
     *     <td>Used to obtain the preferred distance to indent a component
     *         relative to another.  For example, if you want to horizontally
     *         indent a JCheckBox relative to a JLabel use <code>INDENT</code>.
     *         This is only useful for the horizontal axis.
     * </table>
     * <p>
     * It's important to note that some look and feels may not distinguish
     * between <code>RELATED</code> and <code>UNRELATED</code>.
     * <p>
     * The return value is not intended to take into account the
     * current size and position of <code>component2</code> or
     * <code>component1</code>.  The return value may take into
     * consideration various properties of the components.  For
     * example, the space may vary based on font size, or the preferred
     * size of the component.
     *
     * @param component1 the <code>JComponent</code>
     *               <code>component2</code> is being placed relative to
     * @param component2 the <code>JComponent</code> being placed
     * @param type how the two components are being placed
     * @param position the position <code>component2</code> is being placed
     *        relative to <code>component1</code>; one of
     *        <code>SwingConstants.NORTH</code>,
     *        <code>SwingConstants.SOUTH</code>,
     *        <code>SwingConstants.EAST</code> or
     *        <code>SwingConstants.WEST</code>
     * @param parent the parent of <code>component2</code>; this may differ
     *        from the actual parent and may be null
     * @return the amount of space to place between the two components
     * @throws IllegalArgumentException if <code>position</code> is not
     *         one of <code>SwingConstants.NORTH</code>,
     *         <code>SwingConstants.SOUTH</code>,
     *         <code>SwingConstants.EAST</code> or
     *         <code>SwingConstants.WEST</code>; <code>type</code> not one
     *         of <code>INDENT</code>, <code>RELATED</code>
     *         or <code>UNRELATED</code>; or <code>component1</code> or
     *         <code>component2</code> is null
     */
    public int getPreferredGap(JComponent component1, JComponent component2,
                               int type, int position, Container parent) {
        // Check args
        super.getPreferredGap(component1, component2, type, position, parent);
        
        int result;
        
        // Compute gap
        if (type == INDENT) {
            // Compute gap
            if (position == SwingConstants.EAST || position == SwingConstants.WEST) {
                int gap = getButtonChildIndent(component1, position);
                if (gap != 0) {
                    return gap;
                }
            }
            int sizeStyle = getSizeStyle(component1);
            Insets gap1 = getPreferredGap(component1, type, sizeStyle);
            switch (position) {
                case SwingConstants.NORTH :
                    result = gap1.bottom;
                    break;
                case SwingConstants.SOUTH :
                    result = gap1.top;
                    break;
                case SwingConstants.EAST :
                    result = gap1.left;
                    break;
                case SwingConstants.WEST :
                default :
                    result = gap1.right;
                    break;
            }
            int raw = result;
            // Compensate for visual margin
            Insets visualMargin2 = getVisualMargin(component2);
            switch (position) {
                case SwingConstants.NORTH :
                    result -= visualMargin2.bottom;
                    break;
                case SwingConstants.SOUTH :
                    result -= visualMargin2.top;
                    break;
                case SwingConstants.EAST :
                    result -= visualMargin2.left;
                    break;
                case SwingConstants.WEST :
                    result -= visualMargin2.right;
                default :
                    break;
            }
        } else {
            // Compute gap
            int sizeStyle = Math.min(getSizeStyle(component1),
                    getSizeStyle(component2));
            Insets gap1 = getPreferredGap(component1, type, sizeStyle);
            Insets gap2 = getPreferredGap(component2, type, sizeStyle);
            switch (position) {
                case SwingConstants.NORTH :
                    result = Math.max(gap1.top, gap2.bottom);
                    break;
                case SwingConstants.SOUTH :
                    result = Math.max(gap1.bottom, gap2.top);
                    break;
                case SwingConstants.EAST :
                    result = Math.max(gap1.right, gap2.left);
                    break;
                case SwingConstants.WEST :
                default :
                    result = Math.max(gap1.left, gap2.right);
                    break;
            }
            
            // Compensate for visual margin
            Insets visualMargin1 = getVisualMargin(component1);
            Insets visualMargin2 = getVisualMargin(component2);
            
            switch (position) {
                case SwingConstants.NORTH :
                    result -= visualMargin1.top + visualMargin2.bottom;
                    break;
                case SwingConstants.SOUTH :
                    result -= visualMargin1.bottom + visualMargin2.top;
                    break;
                case SwingConstants.EAST :
                    result -= visualMargin1.right + visualMargin2.left;
                    break;
                case SwingConstants.WEST :
                    result -= visualMargin1.left + visualMargin2.right;
                default :
                    break;
            }
        }
        
        // Aqua does not support negative gaps, because all its components are
        // opaque
        return Math.max(0, result);
    }
    
    private Insets getPreferredGap(JComponent component, int type, int sizeStyle) {
        Map gapMap;
        
        switch (type) {
            case INDENT :
                gapMap = INDENT_GAPS;
                break;
            case RELATED :
                gapMap = RELATED_GAPS;
                break;
            case UNRELATED :
            default :
                gapMap = UNRELATED_GAPS;
                break;
        }
        
        String uid = component.getUIClassID();
        String style = null;
        // == is ok here as Strings from Swing get interned, if for some reason
        // need .equals then must deal with null.
        if (uid == "ButtonUI" || uid =="ToggleButtonUI") {
            style = (String) component.getClientProperty("JButton.buttonType");
        } else if (uid =="ProgressBarUI") {
            style = (((JProgressBar) component).getOrientation() ==
                    JProgressBar.HORIZONTAL) ? "horizontal" : "vertical";
        } else if (uid == "SliderUI") {
            style = (((JSlider) component).getOrientation()
                    == JProgressBar.HORIZONTAL) ? "horizontal" : "vertical";
        } else if (uid == "TabbedPaneUI") {
            switch (((JTabbedPane) component).getTabPlacement()) {
                case JTabbedPane.TOP :
                    style = "top";
                    break;
                case JTabbedPane.LEFT :
                    style = "left";
                    break;
                case JTabbedPane.BOTTOM :
                    style = "bottom";
                    break;
                case JTabbedPane.RIGHT :
                    style = "right";
                    break;
            }
        }
        return getInsets(gapMap, uid, style, sizeStyle);
    }
    
    /**
     * Returns the amount of space to position a component inside its
     * parent.
     *
     * @param component the <code>Component</code> being positioned
     * @param position the position <code>component</code> is being placed
     *        relative to its parent; one of
     *        <code>SwingConstants.NORTH</code>,
     *        <code>SwingConstants.SOUTH</code>,
     *        <code>SwingConstants.EAST</code> or
     *        <code>SwingConstants.WEST</code>
     * @param parent the parent of <code>component</code>; this may differ
     *        from the actual parent and may be null
     * @return the amount of space to place between the component and specified
     *         edge
     * @throws IllegalArgumentException if <code>position</code> is not
     *         one of <code>SwingConstants.NORTH</code>,
     *         <code>SwingConstants.SOUTH</code>,
     *         <code>SwingConstants.EAST</code> or
     *         <code>SwingConstants.WEST</code>;
     *         or <code>component</code> is null
     */
    public int getContainerGap(JComponent component, int position,
                               Container parent) {
        int result;
        int sizeStyle = Math.min(getSizeStyle(component), getSizeStyle(parent));
        
        // Compute gap
        Insets gap = getContainerGap(parent, sizeStyle);
        
        switch (position) {
            case SwingConstants.NORTH :
                result = gap.top;
                break;
            case SwingConstants.SOUTH :
                result = gap.bottom;
                break;
            case SwingConstants.EAST :
                result = gap.right;
                break;
            case SwingConstants.WEST :
            default :
                result = gap.left;
                break;
        }
        
        // Compensate for visual margin
        Insets visualMargin = getVisualMargin(component);
        switch (position) {
            case SwingConstants.NORTH :
                result -= visualMargin.top;
                break;
            case SwingConstants.SOUTH :
                result -= visualMargin.bottom;
                // Radio buttons in Quaqua are 1 pixel too high, in order
                // to align their baselines with other components, when no
                // baseline aware layout manager is used.
                if (component instanceof JRadioButton) {
                    result--;
                }
                break;
            case SwingConstants.EAST :
                result -= visualMargin.left;
                break;
            case SwingConstants.WEST :
                result -= visualMargin.right;
            default :
                break;
        }
        
        // Aqua does not support negative gaps, because all its components are
        // opaque
        return Math.max(0, result);
    }
    
    
    private Insets getContainerGap(Container container, int sizeStyle) {
        String uid;
        if (container instanceof JComponent) {
            uid = ((JComponent) container).getUIClassID();
        } else if (container instanceof Dialog) {
            uid = "Dialog";
        } else if (container instanceof Frame) {
            uid = "Frame";
        } else if (container instanceof java.applet.Applet) {
            uid = "Applet";
        } else if (container instanceof Panel) {
            uid = "Panel";
        } else {
            uid = "default";
        }
        
        // FIXME insert style code here for JInternalFrame with palette style
        return getInsets(CONTAINER_GAPS, uid, null,  sizeStyle);
    }
    
    private Insets getInsets(Map gapMap, String uid, String style,
            int sizeStyle) {
        if (uid == null) {
            uid = "default";
        }
        ComponentInsets componentInsets = (ComponentInsets)gapMap.get(uid);
        if (componentInsets == null) {
            componentInsets = (ComponentInsets)gapMap.get("default");
            if (componentInsets == null) {
                return EMPTY_INSETS;
            }
        } else if (style != null) {
            ComponentInsets subInsets = componentInsets.getSubinsets(style);
            if (subInsets != null) {
                componentInsets = subInsets;
            }
        }
        return componentInsets.getInsets(sizeStyle);
    }
    
    private Insets getVisualMargin(JComponent component) {
        String uid = component.getUIClassID();
        String style = null;
        if (uid == "ButtonUI" || uid == "ToggleButtonUI") {
            style = (String) component.getClientProperty("JButton.buttonType");
        } else if (uid == "ProgressBarUI") {
            style = (((JProgressBar) component).getOrientation()
                    == JProgressBar.HORIZONTAL) ? "horizontal" :
                    "vertical";
        } else if (uid == "SliderUI") {
            style = (((JSlider) component).getOrientation() ==
                    JProgressBar.HORIZONTAL) ? "horizontal"
                    : "vertical";
        } else if (uid == "TabbedPaneUI") {
            switch (((JTabbedPane) component).getTabPlacement()) {
                case JTabbedPane.TOP :
                    style = "top";
                    break;
                case JTabbedPane.LEFT :
                    style = "left";
                    break;
                case JTabbedPane.BOTTOM :
                    style = "bottom";
                    break;
                case JTabbedPane.RIGHT :
                    style = "right";
                    break;
            }
        }
        Insets gap = getInsets(VISUAL_MARGINS, uid, style, 0);
        // Take into account different positions of the button icon
        if (uid == "RadioButtonUI" || uid == "CheckBoxUI") {
            switch (((AbstractButton) component).getHorizontalTextPosition()) {
                case SwingConstants.RIGHT :
                    gap = new Insets(gap.top, gap.right, gap.bottom, gap.left);
                    break;
                case SwingConstants.CENTER :
                    gap = new Insets(gap.top, gap.right, gap.bottom, gap.right);
                    break;
                    /*
                case SwingConstants.LEFT :
                    break;
                     */
            }
            if (component.getBorder() instanceof EmptyBorder) {
                gap.left -= 2;
                gap.right -= 2;
                gap.top -= 2;
                gap.bottom -= 2;
            }
        }
        return gap;
    }
    
    /**
     * Returns the size style of a specified component.
     *
     * @return REGULAR, SMALL or MINI.
     */
    private int getSizeStyle(Component c) {
        // Aqua components have a different style depending on the
        // font size used.
        // 13 Point = Regular
        // 11 Point = Small
        //  9 Point = Mini
        if (c == null) {
            return REGULAR;
        }
        Font font = c.getFont();
        if (font == null) {
            return REGULAR;
        }
        int fontSize = font.getSize();
        return (fontSize >= 13) ? REGULAR : ((fontSize > 9) ? SMALL : MINI);
    }
    
    
    /**
     * ComponentInsets is used to manage the Insets for a specific Component
     * type.  Each ComponentInsets may also have children (sub) ComponentInsets.
     * Subinsets are used to represent different styles a component may have.
     * For example, a Button may not a set of insets, as well as insets when
     * it has a style of metal.
     */
    private static class ComponentInsets {
        // Map<String,ComponentInsets>
        private Map children;
        private Insets[] insets;
        
        public ComponentInsets() {
        }
        
        public ComponentInsets(Insets[] insets) {
            this.insets = insets;
        }
        
        public void setInsets(Insets[] insets) {
            this.insets = insets;
        }
        
        public Insets[] getInsets() {
            return insets;
        }
        
        public Insets getInsets(int size) {
            if (insets == null) {
                return EMPTY_INSETS;
            }
            return insets[size];
        }
        
        void addSubinsets(String subkey, ComponentInsets subinsets) {
            if (children == null) {
                children = new HashMap(5);
            }
            children.put(subkey, subinsets);
        }
        
        ComponentInsets getSubinsets(String subkey) {
            return (children == null) ? null :
                                        (ComponentInsets)children.get(subkey);
        }
    }
}
