/* dIntProg Browser. A webbrowser written in Java.
 * Copyright (C) 2001 Martin Geisler <gimpster@gimpster.com>
 *  
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 * 
 * This program 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
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the
 * 
 * Free Software Foundation, Inc.,
 * 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

import java.awt.Graphics;
import java.awt.Color;
import javax.swing.*;
import java.util.*;

/** This class models a table. A table is a box with several
 *  child-boxes. The child-boxes are laid out in rows and columns. You
 *  have to call {@link #endTableRow} at the end of a row, so that the
 *  table knows how many boxes to put into each row. */
public class Table extends AbstractFlexibleBox {

    private int rows = 0;
    private int columns = 0;
    private int column_widths[];
    private int row_heights[];
    private boolean empty_row = true;
    private boolean layout_error = false;

    /** Ends the current row. This method should be called at least
     *  once at the end of a row, so that the table knows how many
     *  cells to put in each row. */
    public void endTableRow() {
        if (!empty_row) {
            rows = rows + 1;
            empty_row = true;
        }
    }

    public void insert(Box b) {
        super.insert(b);
        empty_row = false;
    }

    /** Makes the table calculate it's width and height. This is done
     *  by laying out the child-boxes in rows and columns. Because of
     *  the complicated calculations, each box will be asked to do
     *  layout twice.
     *
     *  <p>The columns will be sized according to the contents. As a
     *  minimum, each column will be as wide as the greatest minimum
     *  width of the boxes in that column, so that the boxes doens't
     *  bleed into each other. If there's still room left, the rest of
     *  the width is distributed amoung the columns, so that the
     *  column that needs the most space gets the most space. If a
     *  column is at it's preferred size, then it won't be resized.
     *
     *  <p>The rows will be as heigh as necessary to accomodate for
     *  the contents.
     *
     *  @param g the graphics context. Some boxes, like TextFragments,
     *  need a graphics context before they can determine their width
     *  and height.
     *  @param c the component into which the box will be drawn. An
     *  ImageBox needs to know the component to calculate the width
     *  and height.
     *  @param w the width available for to the Box. */
    public void doLayout(Graphics g, JComponent comp, int w) {
	/* A table without any rows is no good. And a table where
           rows*columns != cells doesn't work either.  */
	if (rows == 0) {
            layout_error = true;
	    return;
	} else if (boxes.size() % rows != 0) {
            layout_error = true;
	    return;
	}

        columns = boxes.size()/rows;
        row_heights = new int[rows];
        column_widths = new int[columns];

        Browser.debug("Table.doLayout: " + rows + " rows and " +
                      columns + " columns: " + boxes.size() + " cells.");
        
        int c = 0;
        int[] pref_widths = new int[columns];
        int[] min_widths = new int[columns];

        height = 0;
        width = 0;
        min_width = 0;
        pref_width = 0;

        ListIterator iterator = boxes.listIterator();
        while (iterator.hasNext()) {
            Box b = (Box)iterator.next();
            /* We give the box a chance to calculate it's width and height. */
            b.doLayout(g, comp, w);
            /* We then give each column as much space as it needs. */
            if (b.getMinimumWidth() > min_widths[c]) {
                min_width = min_width - min_widths[c] + b.getMinimumWidth();
                min_widths[c] = b.getMinimumWidth();
                column_widths[c] = b.getMinimumWidth();
            }

            /* We store the maximum preferred width of the column. */
            if (b.getPreferredWidth() > pref_widths[c]) {
                pref_width = pref_width - pref_widths[c] +
                    b.getPreferredWidth();
                pref_widths[c] = b.getPreferredWidth();
            }

            c = c + 1;
            /* End of row */
            if (c == columns) {
                c = 0;
            }
        }

        if (min_width < w && pref_width != min_width) {
            /* We have some extra space */
            int extra = w - min_width;
            
            for (int i = 0; i < columns; i = i + 1) {
                column_widths[i] = column_widths[i] +
                    (pref_widths[i] - min_widths[i]) * extra /
                    (pref_width - min_width);
            }
            width = w;
        } else {
            width = min_width;
        }

        int r = 0;
        iterator = boxes.listIterator();
        while (iterator.hasNext()) {
            Box b = (Box)iterator.next();

            b.doLayout(g, comp, column_widths[c]);
            if (b.getHeight() > row_heights[r]) {
                height = height - row_heights[r] + b.getHeight();
                row_heights[r] = b.getHeight();
            }
            c = c + 1;
            /* End of row */
            if (c == columns) {
                c = 0;
                r = r + 1;
            }
        }
    }
    
    /** The preferred width of this box. This is the sum of the
     *  preferred widths of the columns. The preferred width of a
     *  column is the greatest of the preferred widths of the cells in
     *  that column.
     *
     *  <p>If the box is resized to it's preferred width, all the
     *  cells in each row would fit on a single line.
     *  @return the preferred width of the box. */
    public int getPreferredWidth() {
        return pref_width;
    }
    
    /** The minimum width of this box. This is the sum of the minimum
     *  widths of the columns. The minimum width of a column is the
     *  greatest of the minimum widths of the cells in that column.
     *  @return the preferred width of the box. */
    public int getMinimumWidth() {
        return min_width;
    }
    
    public void drawAt(int x, int y, DocumentView v) {
        /* We can't draw the table if we couldn't do the layout. */
	if (layout_error) {
            return;
        }

        int c = 0, x_offset = 0;
        int r = 0, y_offset = 0;
	
        ListIterator iterator = boxes.listIterator();
        while (iterator.hasNext()) {
            Box b = (Box)iterator.next();
            b.drawAt(x + x_offset, y + y_offset, v);
            x_offset = x_offset + column_widths[c];
            c = c + 1;
            /* End of row */
            if (c == columns) {
                x_offset = 0;
                c = 0;
                y_offset = y_offset + row_heights[r];
                r = r + 1;
            }
        }
    }

}