/*
 *  Copyright (c) 1995-2012, The University of Sheffield. See the file
 *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
 *
 *  This file is part of GATE (see http://gate.ac.uk/), and is free
 *  software, licenced under the GNU Library General Public License,
 *  Version 2, June 1991 (in the distribution as file licence.html,
 *  and also available at http://gate.ac.uk/gate/licence.html).
 *
 *  Valentin Tablan 06/03/2001
 *
 *  $Id: JTreeTable.java 19756 2016-11-19 01:55:44Z markagreenwood $
 *
 */
package gate.swing;

import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import javax.swing.tree.*;


/**
 * A TreeTable component. That is a component that looks like a table apart
 * from the first column that contains a tree.
 */
@SuppressWarnings("serial")
public class JTreeTable extends XJTable {

  /**The tree used to render the first column*/
  protected CustomJTree tree;

  /**The model for this component*/
  protected TreeTableModel treeTableModel;
  
  /**
   * The adapter used internally to convert a tree model into a table model. 
   */
  protected TreeTableModelAdapter modelAdapter;

  /**
   * Constructs a JTreeTable from a model
   */
  public JTreeTable(TreeTableModel model) {
    super();
    this.treeTableModel = model;

    initLocalData();
    initGuiComponents();
    initListeners();

    super.setSortable(false);
  }

  protected void initLocalData(){
  }

  protected void initGuiComponents(){
    // Create the tree. It will be used by the table renderer to draw the cells
    //in the first column
    tree = new CustomJTree();
    tree.setModel(treeTableModel);
    tree.setEditable(false);

    // Install a tableModel representing the visible rows in the tree.
    modelAdapter = new TreeTableModelAdapter(treeTableModel);
    super.setModel(modelAdapter);

    // Force the JTable and JTree to share their row selection models.
    tree.setSelectionModel(new DefaultTreeSelectionModel() {
      //extend the constructor
      {
        setSelectionModel(listSelectionModel);
      }
    });

    setAutoCreateColumnsFromModel(false);
    //Install the renderer and editor
    getColumnModel().getColumn(0).setCellRenderer(new TreeTableCellRenderer());
    getColumnModel().getColumn(0).setCellEditor(new TreeTableCellEditor());

    setShowGrid(false);
    
    setRowMargin(0);
  }

  protected void initListeners(){
    //install the mouse listener that will forward the mouse events to the tree
    addMouseListener(new MouseHandler());

    getColumnModel().getColumn(0).addPropertyChangeListener(new PropertyChangeListener() {
      @Override
      public void propertyChange(PropertyChangeEvent e) {
        if(e.getPropertyName().equals("width")){
          int width = ((Number)e.getNewValue()).intValue();
          int height = tree.getSize().height;
          tree.setSize(width, height);
        }
      }
    });
  }

  /**
   * Overrides the setSortable() method from {@link XJTable} so the table is NOT
   * sortable. In a tree-table component the ordering for the rows is given by
   * the structure of the tree and they cannot be reordered.
   */
  @Override
  public void setSortable(boolean b){
    throw new UnsupportedOperationException(
          "A JTreeTable component cannot be sortable!\n" +
          "The rows order is defined by the tree structure.");
  }

  public JTree getTree(){
    return tree;
  }

  public void expandPath(TreePath path){
    tree.expandPath(path);
  }

  public void expandRow(int row){
    tree.expandRow(row);
  }

  /**
   * The renderer used to display the table cells containing tree nodes.
   * Will use an internal JTree object to paint the nodes.
   */
  public class TreeTableCellRenderer extends DefaultTableCellRenderer {
    @Override
    public Component getTableCellRendererComponent(JTable table,
                     Object value,
                     boolean isSelected,
                     boolean hasFocus,
                     int row, int column) {
      tree.setVisibleRow(row);
      return tree;
    }
  }//public class TreeTableCellRenderer extends DefaultTableCellRenderer

  /**
   * The editor used to edit the nodes in the tree. It only forwards the
   * requests to the tree's editor.
   */
  class TreeTableCellEditor extends DefaultCellEditor {
    TreeTableCellEditor(){
      super(new JTextField());
      //placeHolder = new PlaceHolder();
      editor = tree.getCellEditor();
      setClickCountToStart(0);
    }

    @Override
    public Component getTableCellEditorComponent(JTable table,
                                                 Object value,
                                                 boolean isSelected,
                                                 int row,
                                                 int column) {

      editor = tree.getCellEditor();

      editor.addCellEditorListener(new CellEditorListener() {
        @Override
        public void editingStopped(ChangeEvent e) {
          fireEditingStopped();
        }

        @Override
        public void editingCanceled(ChangeEvent e) {
          fireEditingCanceled();
        }
      });

      editorComponent = editor.getTreeCellEditorComponent(
                    tree, tree.getPathForRow(row).getLastPathComponent(),
                    isSelected, tree.isExpanded(row),
                    tree.getModel().isLeaf(
                      tree.getPathForRow(row).getLastPathComponent()
                    ),
                    row);
      Box box = Box.createHorizontalBox();
      box.add(Box.createHorizontalStrut(tree.getRowBounds(row).x));
      box.add(editorComponent);
      return box;
//      return editorComponent;
    }

    @Override
    public Object getCellEditorValue() {
      return editor == null ? null : editor.getCellEditorValue();
    }

    @Override
    public boolean stopCellEditing(){
      return editor == null ? true : editor.stopCellEditing();
    }

    @Override
    public void cancelCellEditing(){
      if(editor != null) editor.cancelCellEditing();
    }

    TreeCellEditor editor;
    Component editorComponent;
  }

  /**
   * Class used to convert the mouse events from the JTreeTable component space
   * into the JTree space. It is used to forward the mouse events to the tree
   * if they occured in the space used by the tree.
   */
  class MouseHandler extends MouseAdapter {
    @Override
    public void mousePressed(MouseEvent e) {
      if(columnAtPoint(e.getPoint()) == 0){
        tree.dispatchEvent(convertEvent(e));
      }
    }

    @Override
    public void mouseReleased(MouseEvent e) {
      if(columnAtPoint(e.getPoint()) == 0){
        tree.dispatchEvent(convertEvent(e));
      }
    }

    @Override
    public void mouseClicked(MouseEvent e) {
      if(columnAtPoint(e.getPoint()) == 0){
        tree.dispatchEvent(convertEvent(e));
      }
    }


    @Override
    public void mouseEntered(MouseEvent e) {
      if(columnAtPoint(e.getPoint()) == 0){
        tree.dispatchEvent(convertEvent(e));
      }
    }

    @Override
    public void mouseExited(MouseEvent e) {
      if(columnAtPoint(e.getPoint()) == 0){
        tree.dispatchEvent(convertEvent(e));
      }
    }

    protected MouseEvent convertEvent(MouseEvent e){
      int column = 0;
      int row = rowAtPoint(e.getPoint());

      //move the event from table to tree coordinates
      Rectangle tableCellRect = getCellRect(row, column, false);
      Rectangle treeCellRect = tree.getRowBounds(row);
      int dx = 0;
      if(tableCellRect != null) dx = -tableCellRect.x;
      int dy = 0;
      if(tableCellRect !=null && treeCellRect != null)
        dy = treeCellRect.y -tableCellRect.y;
      e.translatePoint(dx, dy);


      return new MouseEvent(
        tree, e.getID(), e.getWhen(), e.getModifiers(),
        e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger()
      );
    }
  }

  /**
   * A wrapper that reads a TreeTableModel and behaves as a TableModel
   */
  class TreeTableModelAdapter extends AbstractTableModel{
    public TreeTableModelAdapter(TreeTableModel treeTableModel) {
      tree.addTreeExpansionListener(new TreeExpansionListener() {
        // Don't use fireTableRowsInserted() here;
        // the selection model would get  updated twice.
        @Override
        public void treeExpanded(TreeExpansionEvent event) {
          fireTableDataChanged();
        }
        @Override
        public void treeCollapsed(TreeExpansionEvent event) {
          fireTableDataChanged();
        }
      });
      tree.getModel().addTreeModelListener(new TreeModelListener() {
        @Override
        public void treeNodesChanged(TreeModelEvent e) {
          fireTableDataChanged();
        }
        @Override
        public void treeNodesInserted(TreeModelEvent e) {
          fireTableDataChanged();
        }
        @Override
        public void treeNodesRemoved(TreeModelEvent e) {
          fireTableDataChanged();
        }
        @Override
        public void treeStructureChanged(TreeModelEvent e) {
          fireTableDataChanged();
        }
      });
    }

    // Wrappers, implementing TableModel interface.
    @Override
    public int getColumnCount() {
      return treeTableModel.getColumnCount();
    }

    @Override
    public String getColumnName(int column) {
      return treeTableModel.getColumnName(column);
    }

    @Override
    public Class<?> getColumnClass(int column) {
      if(column == 0) return TreeTableModel.class;
      else return treeTableModel.getColumnClass(column);
    }

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

    protected Object nodeForRow(int row) {
      TreePath treePath = tree.getPathForRow(row);
      return treePath.getLastPathComponent();
    }

    @Override
    public Object getValueAt(int row, int column) {
      if(column == 0) return treeTableModel;
      else return treeTableModel.getValueAt(nodeForRow(row), column);
    }

    @Override
    public boolean isCellEditable(int row, int column) {
      return treeTableModel.isCellEditable(nodeForRow(row), column);
    }

    @Override
    public void setValueAt(Object value, int row, int column) {
      Object node = nodeForRow(row);
      treeTableModel.setValueAt(value, node, column);
    }
  }//class TreeTableModelAdapter extends AbstractTableModel

  /**
   * The JTree used for rendering the first column.
   */
  class CustomJTree extends JTree {

    @Override
    public void updateUI(){
      super.updateUI();
      setRowHeight(0);
    }


    public void setVisibleRow(int row){
      visibleRow = row;
    }

    /**
     * Paints only the current cell in the table
     */
    @Override
    public void paint(Graphics g){
      Rectangle rowBounds = getRowBounds(visibleRow);
      g.translate(0, -rowBounds.y);
      Rectangle oldClip = g.getClipBounds();
//      Rectangle newClip = oldClip.intersection(
//              new Rectangle(oldClip.x, rowBounds.y, oldClip.width, 
//                      rowBounds.height));
//      g.setClip(newClip);
      //re-implemented more efficiently below:
      int newY = Math.max(oldClip.y, rowBounds.y);
      int newHeight = Math.min(rowBounds.height - (rowBounds.y - newY), 
              oldClip.height);
      g.setClip(oldClip.x, newY, oldClip.width, newHeight);
      super.paint(g);
    }


    @Override
    public Dimension getPreferredSize(){
      return new Dimension(super.getPreferredSize().width,
                           getRowBounds(visibleRow).height);
    }


    @Override
    public void validate(){}
    @Override
    public void revalidate(){}
    @Override
    public void repaint(long tm, int x, int y, int width, int height){}
    @Override
    public void repaint(Rectangle r){}

    protected int visibleRow;

    /* (non-Javadoc)
     * @see javax.swing.JTree#setRootVisible(boolean)
     */
    @Override
    public void setRootVisible(boolean rootVisible) {
      boolean oldValue = isRootVisible();
      if(oldValue != rootVisible){
        super.setRootVisible(rootVisible);
        modelAdapter.fireTableDataChanged();
      }
    }
  }

/*
  class SmartTreeCellRenderer implements TreeCellRenderer{

    SmartTreeCellRenderer(TreeCellRenderer renderer){
      originalRenderer = renderer;
    }

    public Component getTreeCellRendererComponent(JTree tree,
                                              Object value,
                                              boolean selected,
                                              boolean expanded,
                                              boolean leaf,
                                              int row,
                                              boolean hasFocus){
      Component comp = originalRenderer.getTreeCellRendererComponent(
                       tree, value, selected, expanded, leaf, row, hasFocus);
      if(comp instanceof JComponent &&
         comp.getPreferredSize().height < getRowHeight(row)){
        ((JComponent)comp).setPreferredSize(
            new Dimension(comp.getPreferredSize().width,
            getRowHeight(row))
        );
      }
      return comp;
    }

    public TreeCellRenderer getOriginalRenderer(){
      return originalRenderer;
    }

    TreeCellRenderer originalRenderer;
  }
*/
}//public class JTreeTable extends XJTable