/*
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
 * or http://www.netbeans.org/cddl.txt.
 * 
 * When distributing Covered Code, include this CDDL Header Notice in each file
 * and include the License file at http://www.netbeans.org/cddl.txt.
 * If applicable, add the following below the CDDL Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 * Microsystems, Inc. All Rights Reserved.
 */
package org.openide.explorer.view;

import org.openide.nodes.*;

import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.*;
import javax.swing.tree.TreePath;


/* NodeTableModel synchronizing tree and table model. Used by TreeTable.
 *
 * @author Jan Rojcek
 */
class TreeTableModelAdapter extends NodeTableModel {
    private JTree tree;
    private NodeTableModel nodeTableModel;

    public TreeTableModelAdapter(JTree t, NodeTableModel ntm) {
        this.tree = t;
        this.nodeTableModel = ntm;

        Listener listener = new Listener();
        tree.addTreeExpansionListener(listener);
        tree.getModel().addTreeModelListener(listener);
        nodeTableModel.addTableModelListener(listener);
    }

    // NodeTableModel methods
    public void setNodes(Node[] nodes) {
        nodeTableModel.setNodes(nodes);
    }

    public void setProperties(Node.Property[] props) {
        nodeTableModel.setProperties(props);
    }

    protected Node.Property getPropertyFor(Node node, Node.Property prop) {
        return nodeTableModel.getPropertyFor(node, prop);
    }

    Node nodeForRow(int row) {
        return Visualizer.findNode(tree.getPathForRow(row).getLastPathComponent());
    }

    Node.Property propertyForColumn(int column) {
        return nodeTableModel.propertyForColumn(column - 1);
    }

    // Wrappers, implementing TableModel interface. 
    public int getColumnCount() {
        return nodeTableModel.getColumnCount() + 1;
    }

    public String getColumnName(int column) {
        return (column == 0) ? Visualizer.findNode(tree.getModel().getRoot()).getDisplayName()
                             : nodeTableModel.getColumnName(column - 1);
    }

    public Class getColumnClass(int column) {
        return (column == 0) ? TreeTableModelAdapter.class : nodeTableModel.getColumnClass(column - 1);
    }

    public int getRowCount() {
        return tree.getRowCount();
    }

    public Object getValueAt(int row, int column) {
        return (column == 0) ? tree.getPathForRow(row).getLastPathComponent()
                             : nodeTableModel.getPropertyFor(nodeForRow(row), propertyForColumn(column));
    }

    public boolean isCellEditable(int row, int column) {
        if (column == 0) {
            return true;
        }

        Object o = getValueAt(row, column);

        if (o == null) {
            return false;
        }

        if (o instanceof Node.Property) {
            return ((Node.Property) o).canWrite();
        }

        return false;
    }

    public void setValueAt(Object value, int row, int column) {
    }

    /* Listener for synchronizing tree and table model.
     */
    class Listener implements TreeExpansionListener, TreeModelListener, TableModelListener, Runnable {
        // selection paths stored for restore after update
        TreePath[] tps = null;

        ///////// TreeExpansionListener
        public void treeExpanded(TreeExpansionEvent event) {
            updateNodes();
        }

        public void treeCollapsed(TreeExpansionEvent event) {
            updateNodes();
        }

        ///////////// TreeModelListener
        // Install a TreeModelListener that can update the table when
        // tree changes. We use delayedUpdateNodes as we can
        // not be guaranteed the tree will have finished processing
        // the event before us.
        public void treeNodesChanged(TreeModelEvent e) {
            delayedUpdateNodes(e);
        }

        public void treeNodesInserted(TreeModelEvent e) {
            delayedUpdateNodes(e);
        }

        public void treeNodesRemoved(TreeModelEvent e) {
            delayedUpdateNodes(e);
        }

        public void treeStructureChanged(TreeModelEvent e) {
            // bugfix #23757, store selection paths
            tps = tree.getSelectionPaths();

            // bugfix #30355, don't restore selection when the tree root changed
            // (see javadoc TreeModelListener.treeStructureChanged)
            if ((e.getPath().length == 1) && !e.getTreePath().equals(e.getPath()[0])) {
                tps = null;
            }

            delayedUpdateNodes(e);
        }

        ///////// TableModelListener
        public void tableChanged(TableModelEvent e) {
            int c = e.getColumn();
            int column = (c == TableModelEvent.ALL_COLUMNS) ? TableModelEvent.ALL_COLUMNS : (c + 1);
            fireTableChanged(
                new TableModelEvent(TreeTableModelAdapter.this, e.getFirstRow(), e.getLastRow(), column, e.getType())
            );
        }

        /**
         * Invokes fireTableDataChanged after all the pending events have been processed.
         */
        protected void delayedUpdateNodes(TreeModelEvent e) {
            // Something like this can be used for updating tree column name ?!
            //if (tree.getModel().getRoot().equals(e.getTreePath().getLastPathComponent())) {
            //    fireTableStructureChanged();
            //}
            SwingUtilities.invokeLater(this);
        }

        public void run() {
            updateNodes();
        }

        private void updateNodes() {
            Node[] nodes = new Node[tree.getRowCount()];

            for (int i = 0; i < tree.getRowCount(); i++) {
                nodes[i] = Visualizer.findNode(tree.getPathForRow(i).getLastPathComponent());
            }

            setNodes(nodes);

            // retore selection paths
            if (tps != null) {
                tree.setSelectionPaths(tps);
                tps = null;
            }
        }
    }
}
