package org.das2.components.treetable;

import javax.swing.JTree;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;

public class TreeTableModel extends AbstractTableModel implements TableModel {
    
    private TreeTableNode root;
    
    private JTree tree;
    
    public TreeTableModel(TreeTableNode root, JTree tree) {
        this.root = root;
        this.tree = tree;
        tree.addTreeExpansionListener(new TreeTableTreeListener());
        tree.getModel().addTreeModelListener(new TreeTableTreeModelListener());
    }
    
    @Override
    public Class getColumnClass(int columnIndex) {
        return root.getColumnClass(columnIndex);
    }
    
    public int getColumnCount() {
        return root.getColumnCount();
    }
    
    @Override
    public String getColumnName(int columnIndex) {
        return root.getColumnName(columnIndex);
    }
    
    public int getRowCount() {
        return tree.getRowCount();
    }
    
    public Object getValueAt(int rowIndex, int columnIndex) {
        return getNodeForRow(rowIndex).getValueAt(columnIndex);
    }
    
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return getNodeForRow(rowIndex).isCellEditable(columnIndex);
    }
    
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        getNodeForRow(rowIndex).setValueAt(aValue, columnIndex);
    }
    
    public void toggleExpanded(int rowIndex) {
        if (tree.isExpanded(rowIndex)) {
            tree.collapseRow(rowIndex);
        }
        else {
            tree.expandRow(rowIndex);
        }
    }
    
    public void expand( int rowIndex ) {
        if ( tree.isCollapsed(rowIndex) ) tree.expandRow(rowIndex);
    }
    
    public void collapse( int rowIndex ) {
        if ( tree.isExpanded(rowIndex) ) tree.collapseRow(rowIndex);
    }
    
    public TreeTableNode getNodeForRow(int rowIndex) {
        TreePath path = tree.getPathForRow(rowIndex);
        return (TreeTableNode)path.getLastPathComponent();
    }
    
    public TreeTableNode getRoot() {
        return root;
    }
    
    public void setRoot(TreeTableNode node) {
        if (node == null) {
            throw new NullPointerException("null root node not allowed");
        }
        tree.setModel(new DefaultTreeModel(node, true));
    }
    
    private class TreeTableTreeModelListener implements TreeModelListener {
        
        public void treeNodesChanged(TreeModelEvent e) {
            TreePath path = e.getTreePath();
            int minRow = Integer.MAX_VALUE;
            int maxRow = Integer.MIN_VALUE;
            for (Object child : e.getChildren()) {
                TreePath childPath = path.pathByAddingChild(child);
                int row = tree.getRowForPath(childPath);
                if ( row>-1 && row>maxRow ) maxRow= row;
                if ( row>-1 && row<minRow ) minRow= row;
            }
            if ( minRow<=maxRow ) {
                TreeTableModel.this.fireTableRowsUpdated( minRow, maxRow );
            }
        }
        
        public void treeNodesInserted(TreeModelEvent e) {
            TreePath path = e.getTreePath();
            int row = tree.getRowForPath(path);
            if (row != -1 && tree.isExpanded(row)) {
                int[] indices = e.getChildIndices();
                java.util.Arrays.sort(indices);
                for (int i = 0; i < indices.length; i++) {
                    TreeTableModel.this.fireTableRowsInserted(indices[i], indices[i]);
                }
            }
        }
        
        public void treeNodesRemoved(TreeModelEvent e) {
            TreePath path = e.getTreePath();
            int row = tree.getRowForPath(path);
            if (row != -1 && tree.isExpanded(row)) {
                int[] indices = e.getChildIndices();
                java.util.Arrays.sort(indices);
                for (int i = indices.length - 1; i >= 0; i++) {
                    TreeTableModel.this.fireTableRowsDeleted(indices[i], indices[i]);
                }
            }
        }
        
        public void treeStructureChanged(TreeModelEvent e) {
            TreeTableModel.this.fireTableStructureChanged();
        }
        
    }
    
    private class TreeTableTreeListener implements TreeExpansionListener {
        
        public void treeCollapsed(TreeExpansionEvent event) {
            TreePath path = event.getPath();
            int row = tree.getRowForPath(path);
            TreeTableNode node = (TreeTableNode)path.getLastPathComponent();
            int count = node.getChildCount();
            if (count != 0) {
                TreeTableModel.this.fireTableRowsDeleted(row + 1, row + count);
            }
        }
        
        public void treeExpanded(TreeExpansionEvent event) {
            TreePath path = event.getPath();
            int row = tree.getRowForPath(path);
            TreeTableNode node = (TreeTableNode)path.getLastPathComponent();
            int count = node.getChildCount();
            if (count != 0) {
                TreeTableModel.this.fireTableRowsInserted(row + 1, row + count);
            }
        }
        
    }
    
}