package com.basilv.core.richui;

import java.awt.*;

/**
 * A layout manager that lays out components in a table with variable-width
 * columns based on each component's preferred size.
 */
public class TableLayout implements LayoutManager {

    private int numColumns;
    private int numRows;
    private int xSpacing;
    private int ySpacing;

    /**
     * Create an instance with the specified number of columns and rows 
     * and with a default x and y spacing of 5.
     * @param numColumns  Must be greater than zero.
     * @param numRows  Must be greater than zero.
     */
    public TableLayout(int numColumns, int numRows) {
        this(numColumns, numRows, 5, 5);
    }

    /**
     * Create an instance. 
     * @param numColumns  Must be greater than zero.
     * @param numRows  Must be greater than zero.
     * @param xSpacing  Must be greater than or equal to zero.
     * @param ySpacing  Must be greater than or equal to zero.
     */
    public TableLayout(int numColumns, int numRows, int xSpacing, int ySpacing) {
        assert numColumns > 0;
        assert numRows > 0;
        assert xSpacing >= 0;
        assert ySpacing >= 0;
        
        this.numColumns = numColumns;
        this.numRows = numRows;
        this.xSpacing = xSpacing;
        this.ySpacing = ySpacing;
    }
    
    /** 
     * Lays out the specified container.
     * @param parent the container to be laid out 
     */
    public void layoutContainer(Container parent) {
        assert parent != null;
        
        int[] rowHeights = new int[numRows];
        int[] columnWidths = new int[numColumns];

        for (int row = 0; row < numRows; row++) {
            rowHeights[row] = calculateRowHeight(parent, row);
        }

        for (int column = 0; column < numColumns; column++) {
            columnWidths[column] = calculateColumnWidth(parent, column);
        }

        Insets insets = parent.getInsets();

        int x = insets.left;
        for (int column = 0; column < numColumns; column++) {
            int y = insets.top;

            for (int row = 0; row < numRows; row++) {
                Component component = getComponent(parent, column, row);

                // Subtract spacing because it isn't to be used for the components size
                component.setBounds(x, y, columnWidths[column]-xSpacing, rowHeights[row]-ySpacing);
                y += rowHeights[row];
            }
            x += columnWidths[column];
        }

    }
    
    /**
     * Calculates the preferred size dimensions for the specified 
     * container, given the components it contains.
     * @param parent the container to be laid out
     *  
     * @see #minimumLayoutSize
     */
    public Dimension preferredLayoutSize(Container parent) {
        assert parent != null;
        
        Insets insets = parent.getInsets();

        int height = insets.top + insets.bottom;
        int width = insets.left + insets.right;

        for (int row = 0; row < numRows; row++) {
            height += calculateRowHeight(parent, row);
        }

        for (int column = 0; column < numColumns; column++) {
            width += calculateColumnWidth(parent, column);
        }

        // Subtract spacing after last row / cell
        height -= ySpacing;
        width -= xSpacing;
        
        return new Dimension(width, height);

    }

    /** 
     * Calculates the minimum size dimensions for the specified 
     * container, given the components it contains.
     * @param parent the component to be laid out
     * @see #preferredLayoutSize
     */
    public Dimension minimumLayoutSize(Container parent) {
        return preferredLayoutSize(parent);
    }


    /**
     * If the layout manager uses a per-component string,
     * adds the component <code>comp</code> to the layout,
     * associating it 
     * with the string specified by <code>name</code>.
     * 
     * @param name the string to be associated with the component
     * @param comp the component to be added
     */
    public void addLayoutComponent(String name, Component comp) {
        // Do nothing.
    }

    /**
     * Removes the specified component from the layout.
     * @param comp the component to be removed
     */
    public void removeLayoutComponent(Component comp) {
        // Do nothing
    }

    private Component getComponent(Container parent, int column, int row) {
        int componentIndex = row * numColumns + column;
        return parent.getComponent(componentIndex);
    }
    
    private int calculateColumnWidth(Container parent, int column) {
        int columnWidth = 1;
        for (int row = 0; row < numRows; row++) {
            Component component = getComponent(parent, column, row);
            int componentWidth = component.getPreferredSize().width;
            if (componentWidth > columnWidth) {
                columnWidth = componentWidth;
            }
        }
        return columnWidth + xSpacing;
    }
    
    private int calculateRowHeight(Container parent, int row) {
        int rowHeight = 1;
        for (int column = 0; column < numColumns; column++) {
            Component component = getComponent(parent, column, row);
            int componentHeight = component.getPreferredSize().height;
            if (componentHeight > rowHeight) {
                rowHeight = componentHeight;
            }
        }
        return rowHeight + ySpacing;
    }
    
}
