/*
 * $Id$
 *
 * Copyright 2009 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
package org.jdesktop.swingx.table;

import static org.jdesktop.swingx.table.TableUtilities.isDataChanged;
import static org.jdesktop.swingx.table.TableUtilities.isInsert;
import static org.jdesktop.swingx.table.TableUtilities.isStructureChanged;
import static org.jdesktop.swingx.table.TableUtilities.isUpdate;
import static org.jdesktop.swingx.table.TableUtilities.setPreferredRowHeight;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.logging.Logger;

import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;

/**
 * A controller to adjust JTable rowHeight based on sizing requirements of its renderers.
 * 
 * @author Jeanette Winzenburg, Berlin
 */
public class TableRowHeightController {

    private JTable table;
    private TableModelListener tableModelListener;
    private PropertyChangeListener tablePropertyListener;

    /**
     * Instantiates an unbound TableRowHeightController.
     */
    public TableRowHeightController() {
        this(null);
    }

    /**
     * Instantiates a TableRowHeightController and installs itself to the given table.
     * The row heights of all visible rows are automatically adjusted on model changes.
     * 
     * @param table the table to control.
     */
    public TableRowHeightController(JTable table) {
        install(table);
    }

    /**
     * Installs this controller on the given table. Releases control from previously
     * installed table, if any. 
     * @param table the table to install upon.
     */
    public void install(JTable table) {
        release();
        if (table != null) {
            this.table = table;
            installListeners();
            updatePreferredRowHeights();
        }
    }

    /**
     * Release this controller from its table. Does nothing if no table installed.
     * 
     */
    public void release() {
        if (table == null)
            return;
        uninstallListeners();
        table = null;
    }

    /**
     * Sets the row heights of the rows in the range of first- to lastRow, inclusive.
     * The coordinates are model indices.
     * 
     * @param firstRow the first row in model coordinates
     * @param lastRow the last row in model coordinates
     */
    protected void updatePreferredRowHeights(int firstRow, int lastRow) {
        for (int row = firstRow; row <= lastRow; row++) {
            int viewRow = table.convertRowIndexToView(row);
            if (viewRow >= 0) {
//                int oldHeight = table.getRowHeight(viewRow);
//                LOG.info("in viewRow/old/new: " + viewRow + " / " + oldHeight + " / " + table.getRowHeight(viewRow));
                setPreferredRowHeight(table, viewRow);
            }
        }
    }

    /**
     * Sets the row heights of all rows.
     */
    protected void updatePreferredRowHeights() {
        if (table.getRowCount() == 0) return;
        updatePreferredRowHeights(0, table.getModel().getRowCount() - 1);
    }
    
    /**
     * @param oldValue
     */
    protected void updateModel(TableModel oldValue) {
        if (oldValue != null) {
            oldValue.removeTableModelListener(getTableModelListener());
        }
        table.getModel().addTableModelListener(getTableModelListener());
        updatePreferredRowHeights();
    }


    /**
     * @return
     */
    protected PropertyChangeListener createTablePropertyListener() {
        PropertyChangeListener l = new PropertyChangeListener() {
            
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                invokedPropertyChanged(evt);
            }

            /**
             * @param evt
             */
            private void invokedPropertyChanged(final PropertyChangeEvent evt) {
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        if ("model".equals(evt.getPropertyName())) {
                            updateModel((TableModel) evt.getOldValue());
                        }
                        
                    }
                });
            }
        };
        return l;
    }

    protected TableModelListener createTableModelListener() {
        TableModelListener l = new TableModelListener() {
            @Override
            public void tableChanged(final TableModelEvent e) {
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        invokedTableChanged(e);
                    }
                });
            }

            private void invokedTableChanged(TableModelEvent e) {
                if (isStructureChanged(e) || isDataChanged(e)) {
                    updatePreferredRowHeights();
                } else  if (isUpdate(e) || isInsert(e)) {
                    updatePreferredRowHeights(e.getFirstRow(), e.getLastRow());
                } 
                // do nothing on delete
            }
        };
        return l;
    }
    /**
     * 
     */
    private void uninstallListeners() {
        table.removePropertyChangeListener(getPropertyChangeListener());
        table.getModel().removeTableModelListener(getTableModelListener());
        // whatever else turns out to be needed
    }

    private void installListeners() {
        table.addPropertyChangeListener(getPropertyChangeListener());
        table.getModel().addTableModelListener(getTableModelListener());
        // whatever else turns out to be needed
    }

    /**
     * @return
     */
    protected TableModelListener getTableModelListener() {
        if (tableModelListener == null) {
            tableModelListener = createTableModelListener();
        }
        return tableModelListener;
    }

    /**
     * @return
     */
    protected PropertyChangeListener getPropertyChangeListener() {
        if (tablePropertyListener == null) {
            tablePropertyListener = createTablePropertyListener();
        }
        return tablePropertyListener;
    }

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger
        .getLogger(TableRowHeightController.class.getName());
}