/* class GridBagLayout
 *
 * Copyright (C) 2001  R M Pitman
 *
 * 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 charva.awt;

import java.util.Enumeration;
import java.util.Vector;

/**
 * This is an approximation of the AWT GridBagLayout layout manager.
 * See the documentation of the AWT GridBagLayout for details.
 */
public class GridBagLayout
        implements LayoutManager2 {
    public GridBagLayout() {
    }

    /**
     * Calculate the geometry for the specified list of Components,
     * and return the size of the rectangle that encloses all the
     * Components.
     */
    public Dimension minimumSize(Container container_) {
        /* First work out the dimensions of the grid (i.e. the number of
         * rows and columns).  We do this by iterating through all the
         * added components, and inspecting their gridx, gridy, gridwidth and
         * gridheight constraints.
         */
        _rows = 0;
        _columns = 0;
        Enumeration e1 = _constraints.elements();
        while (e1.hasMoreElements()) {
            GridBagConstraints gbc = (GridBagConstraints) e1.nextElement();
            if (gbc.gridx + gbc.gridwidth > _columns)
                _columns = gbc.gridx + gbc.gridwidth;

            if (gbc.gridy + gbc.gridheight > _rows)
                _rows = gbc.gridy + gbc.gridheight;
        }

        /* Now that we know the number of rows and columns, we can create
         * arrays to hold the row heights and column widths.
         */
        _calculatedRowHeights = new int[_rows];
        _calculatedRowWeights = new double[_rows];
        _calculatedColumnWidths = new int[_columns];
        _calculatedColumnWeights = new double[_columns];

        /* Create a pair of arrays, each the same length as the number of
         * contained components, to hold the "width-left" and "height-left"
         * values for each component.
         */
        int width_left[] = new int[_components.size()];
        int height_left[] = new int[_components.size()];

        e1 = _components.elements();
        Enumeration e2 = _constraints.elements();
        for (int i = 0; e1.hasMoreElements(); i++) {
            Component c = (Component) e1.nextElement();
            GridBagConstraints gbc = (GridBagConstraints) e2.nextElement();

            /* Calculate the minimum width & height required for this
             * component.
             */
            Insets insets = gbc.insets;
            Dimension minsize = c.minimumSize();
            width_left[i] =
                    minsize.width + insets.left + insets.right;
            height_left[i] =
                    minsize.height + insets.top + insets.bottom;
        }

        /* Now iterate through all the rows and allocate heights to each row.
         */
        for (int row = 0; row < _rows; row++) {
            /* Iterate through the constraints and find those whose bottom edge
             * is in the current row. The one with the maximum height_left
             * value determines the height of this row.
             */
            _calculatedRowHeights[row] = 0;
            Enumeration e = _constraints.elements();
            for (int i = 0; e.hasMoreElements(); i++) {
                GridBagConstraints gbc = (GridBagConstraints) e.nextElement();

                if (row == gbc.gridy + gbc.gridheight - 1) {
                    /* This component's bottom edge is in the current row.
                     */
                    if (height_left[i] > _calculatedRowHeights[row])
                        _calculatedRowHeights[row] = height_left[i];
                }
            }

            /* Now that we have calculated the height of this row, subtract
             * this row-height from the height_left value of each component
             * which extends to or below this row.
             */
            e = _constraints.elements();
            for (int i = 0; e.hasMoreElements(); i++) {
                GridBagConstraints gbc = (GridBagConstraints) e.nextElement();

                if (row >= gbc.gridy &&
                        row < gbc.gridy + gbc.gridheight) {
                    height_left[i] -= _calculatedRowHeights[row];
                }
            }
        }

        /* Now iterate through all the columns and allocate widths to each
         * column.
         */
        for (int column = 0; column < _columns; column++) {
            /* Iterate through the constraints and find those whose right edge
             * is in the current column. The one with the maximum width_left
             * value determines the width of this column.
             */
            _calculatedColumnWidths[column] = 0;
            Enumeration e = _constraints.elements();
            for (int i = 0; e.hasMoreElements(); i++) {
                GridBagConstraints gbc = (GridBagConstraints) e.nextElement();

                if (column == gbc.gridx + gbc.gridwidth - 1) {
                    /* This component's right edge is in the current column.
                     */
                    if (width_left[i] > _calculatedColumnWidths[column])
                        _calculatedColumnWidths[column] = width_left[i];
                }
            }

            /* Now that we have calculated the width of this column, subtract
             * this column-width from the width_left value of each component
             * which extends to or to the right of this column.
             */
            e = _constraints.elements();
            for (int i = 0; e.hasMoreElements(); i++) {
                GridBagConstraints gbc = (GridBagConstraints) e.nextElement();

                if (column >= gbc.gridx &&
                        column < gbc.gridx + gbc.gridwidth) {
                    width_left[i] -= _calculatedColumnWidths[column];
                }
            }
        }

        /* Iterate through all the components and calculate the row
         * and column weights.
         */
        e2 = _constraints.elements();
        while (e2.hasMoreElements()) {
            GridBagConstraints gbc = (GridBagConstraints) e2.nextElement();

            for (int i = gbc.gridx; i < gbc.gridx + gbc.gridwidth; i++) {
                if (gbc.weightx > _calculatedColumnWeights[i])
                    _calculatedColumnWeights[i] = gbc.weightx;
            }

            for (int i = gbc.gridy; i < gbc.gridy + gbc.gridheight; i++) {
                if (gbc.weighty > _calculatedRowWeights[i])
                    _calculatedRowWeights[i] = gbc.weighty;
            }
        }

        /* Now just add up all the column widths and row heights to find
         * the minimum size of the container.
         */
        Insets insets = container_.getInsets();
        int totalwidth = insets.left + insets.right;
        int totalheight = insets.top + insets.bottom;

        for (int i = 0; i < _columns; i++) {
            totalwidth += _calculatedColumnWidths[i];
            _totalweightx += _calculatedColumnWeights[i];
        }

        for (int i = 0; i < _rows; i++) {
            totalheight += _calculatedRowHeights[i];
            _totalweighty += _calculatedRowWeights[i];
        }

        return new Dimension(totalwidth, totalheight);
    }

    /**
     * Set the positions of the contained components.
     */
    public void doLayout(Container container_) {
        Insets insets = container_.getInsets();
        Dimension size = container_.getSize();
        Dimension minsize = minimumSize(container_);
        int extraColumns = size.width - minsize.width;
        int extraRows = size.height - minsize.height;

        Enumeration e1 = _components.elements();
        Enumeration e2 = _constraints.elements();
        while (e1.hasMoreElements()) {
            Component c = (Component) e1.nextElement();

            GridBagConstraints gbc = (GridBagConstraints) e2.nextElement();

            /* Calculate the boundaries of the grid cell that this
             * component occupies.
             */
            int left = insets.left;
            if (_totalweightx == 0.0)
                left += extraColumns / 2;

            for (int i = 0; i < gbc.gridx; i++) {
                left += _calculatedColumnWidths[i];
                if (_totalweightx != 0.0)
                    left += (extraColumns * _calculatedColumnWeights[i]) /
                            _totalweightx;
            }

            int right = left;
            for (int i = 0; i < gbc.gridwidth; i++)
                right += _calculatedColumnWidths[gbc.gridx + i];

            int top = insets.top;
            if (_totalweighty == 0.0)
                top += extraRows / 2;

            for (int i = 0; i < gbc.gridy; i++) {
                top += _calculatedRowHeights[i];
                if (_totalweighty != 0.0)
                    top += (extraRows * _calculatedRowWeights[i]) /
                            _totalweighty;
            }

            int bottom = top;
            for (int i = 0; i < gbc.gridheight; i++)
                bottom += _calculatedRowHeights[gbc.gridy + i];

            if (c instanceof Container) {
                Container cont = (Container) c;

                /* Get the contained container to lay itself out at its
                 * preferred size, if it is not already laid out.
                 */
                if (cont.isValid() == false)
                    cont.setSize(cont.minimumSize());

                switch (gbc.fill) {
                    case GridBagConstraints.NONE:
                        break;

                    case GridBagConstraints.HORIZONTAL:
                        cont.setWidth(right - left);
                        break;

                    case GridBagConstraints.VERTICAL:
                        cont.setHeight(bottom - top);
                        break;

                    case GridBagConstraints.BOTH:
                        cont.setSize(right - left, bottom - top);
                        break;

                    default:
                        throw new IllegalArgumentException("Invalid fill parameter");
                }
                cont.doLayout();
            }


            /* Calculate the x position of the component's origin (i.e. top
             * left corner).
             */
            int cx = 0;
            switch (gbc.anchor) {
                case GridBagConstraints.WEST:
                case GridBagConstraints.NORTHWEST:
                case GridBagConstraints.SOUTHWEST:
                    cx = left + gbc.insets.left;
                    break;

                case GridBagConstraints.EAST:
                case GridBagConstraints.NORTHEAST:
                case GridBagConstraints.SOUTHEAST:
                    cx = right - gbc.insets.right - c.getSize().width;
                    break;

                case GridBagConstraints.CENTER:
                case GridBagConstraints.NORTH:
                case GridBagConstraints.SOUTH:
                    cx = (left + gbc.insets.left) +
                            (right - gbc.insets.right);
                    cx -= c.getSize().width;
                    cx = cx / 2;
                    break;

                default:
                    throw new IllegalArgumentException("invalid anchor paremeter");
            }

            /* Calculate the y position of the component's origin (i.e. top
             * left corner).
             */
            int cy = 0;
            switch (gbc.anchor) {
                case GridBagConstraints.NORTH:
                case GridBagConstraints.NORTHWEST:
                case GridBagConstraints.NORTHEAST:
                    cy = top + gbc.insets.top;
                    break;

                case GridBagConstraints.SOUTH:
                case GridBagConstraints.SOUTHWEST:
                case GridBagConstraints.SOUTHEAST:
                    cy = bottom - gbc.insets.bottom - c.getSize().height;
                    break;

                case GridBagConstraints.CENTER:
                case GridBagConstraints.WEST:
                case GridBagConstraints.EAST:
                    cy = (top + gbc.insets.top) +
                            (bottom - gbc.insets.bottom);
                    cy -= c.getSize().height;
                    cy = cy / 2;
            }

            c.setLocation(cx, cy);
        }
    }

    public void addLayoutComponent(Component component_, Object constraint_) {
        _components.add(component_);

        /* Make a copy of the constraints object passed to us, so that the
         * caller can re-use it for other components.
         */
        GridBagConstraints constraint = (GridBagConstraints) constraint_;
        GridBagConstraints newc = new GridBagConstraints();
        newc.gridx = constraint.gridx;
        newc.gridy = constraint.gridy;
        newc.gridwidth = constraint.gridwidth;
        newc.gridheight = constraint.gridheight;
        newc.weightx = constraint.weightx;
        newc.weighty = constraint.weighty;
        newc.anchor = constraint.anchor;
        newc.fill = constraint.fill;
        newc.insets = new Insets(constraint.insets.top,
                constraint.insets.left,
                constraint.insets.bottom,
                constraint.insets.right);
        newc.ipadx = constraint.ipadx;
        newc.ipady = constraint.ipady;
        _constraints.add(newc);
    }

    /**
     * Invalidates the layout, indicating that if the layout manager has cached
     * information it should be discarded.
     */
    public void invalidateLayout(Container target_) {

    }

    //====================================================================
    // INSTANCE VARIABLES

    /**
     * This field holds the overrides to the column minimum widths.
     */
    public int[] columnWidths;

    /**
     * This field holds the overrides to the row minimum heights.
     */
    public int[] rowHeights;

    /**
     * This field is not used in the CHARVA package but is present to allow
     * compile-time compatibility with AWT.
     */
    public double[] columnWeights;

    /**
     * This field is not used in the CHARVA package but is present to allow
     * compile-time compatibility with AWT.
     */
    public double[] rowWeights;

    /**
     * As components are added, they are stored in this vector.
     */
    private Vector _components = new Vector();

    /**
     * As components are added, their constraint objects are stored in
     * this vector.
     */
    private Vector _constraints = new Vector();

    /**
     * The number of rows in the grid (calculated from all the added
     * components and their gridx, gridy, gridwidth and gridheight
     * constraints).
     */
    private int _rows;

    /**
     * The number of rows in the grid (calculated from all the added
     * components and their gridx, gridy, gridwidth and gridheight
     * constraints).
     */
    private int _columns;

    /**
     * This array holds the row heights that we calculate.
     */
    private int[] _calculatedRowHeights;

    /**
     * This array holds the columns widths that we calculate.
     */
    private int[] _calculatedColumnWidths;

    /**
     * This array holds the row weights that we calculate.
     */
    private double[] _calculatedRowWeights;

    /**
     * This array holds the column weights that we calculate.
     */
    private double[] _calculatedColumnWeights;

    private double _totalweightx = 0.0;
    private double _totalweighty = 0.0;
}
