/* GraphTableComponent.java
 * =========================================================================
 * This file is part of the GrInvIn project - http://www.grinvin.org
 * 
 * Copyright (C) 2005-2008 Universiteit Gent
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 * 
 * This program 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
 * General Public License for more details.
 * 
 * A copy of the GNU General Public License can be found in the file
 * LICENSE.txt provided with the source distribution of this program (see
 * the META-INF directory in the source jar). This license can also be
 * found on the GNU website at http://www.gnu.org/licenses/gpl.html.
 * 
 * If you did not receive a copy of the GNU General Public License along
 * with this program, contact the lead developer, or write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

package org.grinvin.gui.components;

import be.ugent.caagt.swirl.tables.EnhancedTable;
import be.ugent.caagt.swirl.tables.TableColumnButton;

import java.awt.Container;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ResourceBundle;
import javax.swing.AbstractAction;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.event.TableModelEvent;
import javax.swing.table.TableColumn;

import org.grinvin.gui.dnd.CommonGraphListTransferHandler;
import org.grinvin.worksheet.HasRequestedCell;
import org.grinvin.worksheet.HasSelectableGraphList;
import org.grinvin.invariants.InvariantValue;
import org.grinvin.invariants.InvariantValueDelegate;
import org.grinvin.list.graphs.DefaultGraphList;
import org.grinvin.list.graphs.GraphList;
import org.grinvin.list.graphs.GraphListElement;
import org.grinvin.list.GraphInvariantTableModel;
import org.grinvin.gui.components.render.InvariantValueTableCellRenderer;
import org.grinvin.list.graphs.GraphListModel;
import org.grinvin.list.invariants.InvariantListModel;
import org.grinvin.list.actions.InvariantValuePopupMenu;
import org.grinvin.list.actions.GraphRenamePanel;

/**
 * Table component which displays a list of graphs as rows, invariants as columns
 * and the corresponding values in the table cells.<p>
 * A table of this type is drag-and-drop enabled:
 * <ul>
 * <li>The current selection can be exported as elements of type {@link GraphListElement}.</li>
 * <li>This component can handle drops of {@link GraphListElement}
 * and {@link org.grinvin.factories.graphs.GraphFactory}, which add a corresponding row.</li>
 * </ul>
 *
 *
 */
public class GraphTableComponent extends EnhancedTable implements HasSelectableGraphList, HasRequestedCell, MouseListener {
    
    //
    private GraphInvariantTableModel model;
    
    //
    private static final Class INVARIANT_VALUE_ELEMENT_CLASS = InvariantValue.class;
    
    //
    private static final ResourceBundle BUNDLE = ResourceBundle.getBundle ("org.grinvin.worksheet.resources");

    //
    private final InvariantValuePopupMenu popupMenu
            = new InvariantValuePopupMenu();

    /**
     * Creates a new instance of GraphTableComponent
     */
    public GraphTableComponent(GraphListModel graphListModel, InvariantListModel invariantListModel, ListSelectionModel newSelectionModel) {
        super();
        setSelectionModel(newSelectionModel);
        TableColumnButton tcb = TableColumnButton.createCloseButton();
        tcb.addActionListener(new CloseButtonListener());
        addPrototypeDecoration(InvariantValue.class, tcb);
        this.model = new GraphInvariantTableModel(graphListModel, invariantListModel);
        setModel(this.model);
        setTransferHandler(CommonGraphListTransferHandler.getInstance());
        setDragEnabled(true);
        
        setDefaultRenderer(INVARIANT_VALUE_ELEMENT_CLASS, InvariantValueTableCellRenderer.getInstance());
        
        setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        
        setShowGrid(false); //hide grid
        setRowMargin(0);
        getColumnModel().setColumnMargin(0);
        
        // Keyboard interaction
        getInputMap(WHEN_FOCUSED).put(KeyStroke.getKeyStroke("DELETE"), "deleteSelectedRows");
        getActionMap().put("deleteSelectedRows", new AbstractAction() {
            public void actionPerformed(ActionEvent ev) {
                deleteSelectedRows();
            }
        });
        getInputMap(WHEN_FOCUSED).put(KeyStroke.getKeyStroke("F2"), "startEditing");
        getActionMap().put("startEditing", new AbstractAction() {
            public void actionPerformed(ActionEvent ev) {
                int index = selectionModel.getMinSelectionIndex();
                if (index < 0)
                    return; // none selected
                if (index != selectionModel.getMaxSelectionIndex())
                    return; // more than one selection
                GraphListElement gle = getGraphList().get(index);
                
                if (gle.isNameEditable()) {
                    new GraphRenamePanel().showDialog(gle, GraphTableComponent.this);
                }
            }
        });
        
        // mouse interaction
        addMouseListener(this);

    }
    
    //
    private class CloseButtonListener implements ActionListener {
        
        public void actionPerformed(ActionEvent e) {
            TableColumnButton tcb = (TableColumnButton)e.getSource();
            model.removeColumn(tcb.getColumnIndex());
        }
        
    }
    
    /**
     * Returns an array of graph list elements for the selected rows of the table.
     */
    public Object[] getSelectedValues() {
        int[] rows = getSelectedRows();
        Object[] result = new GraphListElement[rows.length];
        GraphList graphList = getGraphList();
        for (int i=0; i < rows.length; i++)
            result[i] = graphList.get(rows[i]);
        return result;
    }

    public Object getSelectedValue() {
        Object[] values = getSelectedValues();
        if (values.length > 0)
            return values[0];
        else
            return null;
    }

    /**
     * Delete currently selected rows.
     */
    public void deleteSelectedRows() {
        int[] rows = getSelectedRows();
        
        GraphList graphList = getGraphList();
        GraphList list = new DefaultGraphList();
        
        for(int i : rows)
            list.add(graphList.get(i));
        
        graphList.removeAll(list);
    }
    
    //
    public GraphList getGraphList() {
        return model.getGraphList();
    }
    
    // allow dropping below the table,
    // solution from http://weblogs.java.net/blog/shan_man/archive/2006/01/enable_dropping.html
    @Override
    public boolean getScrollableTracksViewportHeight() {
        // fetch the table's parent
        Container viewport = getParent();
        
        // if the parent is not a viewport, calling this isn't useful
        if (!(viewport instanceof JViewport)) {
            return false;
        }
        
        // return true if the table's preferred height is smaller
        // than the viewport height, else false
        return getPreferredSize().height < viewport.getHeight();
    }
    
    //
    private InvariantValue getInvariantValueAtPoint(Point point) {
        
        int row = rowAtPoint(point);
        int column = columnAtPoint(point);
        if (row < 0 || column < 0)
            return null;
        else {
            column = convertColumnIndexToModel(column);
            return (InvariantValue)model.getValueAt(row, column);
        }
    }
 
    //
    private GraphListElement getElementAtPoint(Point point) {
        
        int row = rowAtPoint(point);
        if (row < 0)
            return null;
        else
            return model.getGraphList().get(row);
    }

    public void mouseClicked(MouseEvent e) {
        
        if (e.getClickCount() != 2 || !isEnabled())
            return;
        
        int row = this.rowAtPoint(e.getPoint());
        InvariantValue ivalue = getInvariantValueAtPoint(e.getPoint());
        
        if(ivalue != null){
            InvariantValueDelegate delegate = ivalue.getType().getDelegate();
            
            if (delegate.hasDetails(ivalue))
                delegate.showDetails(ivalue,
                    model.getGraphList().get(row),
                    this, e.getX(), e.getY() );
        }
        
    }

    
    public void mousePressed(MouseEvent e) {
        if(!isEnabled())
            return;
        
        //set requested cell
        setRequestedCell(rowAtPoint(e.getPoint()), columnAtPoint(e.getPoint()));
        
        if (e.isPopupTrigger()) {
            InvariantValue iv = getInvariantValueAtPoint(e.getPoint());
            GraphListElement gle = getElementAtPoint(e.getPoint());
            if (gle != null)
                popupMenu.show(gle, iv, this, e.getX(), e.getY());
        }
    }

    public void mouseReleased(MouseEvent e) {
        if (isEnabled() && e.isPopupTrigger()) {
            InvariantValue iv = getInvariantValueAtPoint(e.getPoint());
            GraphListElement gle = getElementAtPoint(e.getPoint());
            if (gle != null)
                popupMenu.show(gle, iv, this, e.getX(), e.getY());
        }
    }

    public void mouseEntered(MouseEvent e) {
        // intentionally empty
    }

    public void mouseExited(MouseEvent e) {
        // intentionally empty
    }
    
    @Override
    public void tableChanged(TableModelEvent e){
        super.tableChanged(e);
        rescaleColumns();
    }
    
    public void rescaleColumns(){
        if(getResizingColumn()!=null)
            return;
        int minimumWidth;
        Container viewport = getParent();
        if (viewport instanceof JViewport)
            minimumWidth = viewport.getWidth();
        else
            minimumWidth = getWidth();
        if(getColumnModel().getTotalColumnWidth() < minimumWidth)
            for(int i=0; i<getColumnCount(); i++)
                getColumnModel().getColumn(i).setPreferredWidth(minimumWidth/getColumnCount());
    }
    
    private TableColumn getResizingColumn() {
        return (tableHeader == null) ? null : tableHeader.getResizingColumn();
    }
    
    private int requestedRow = -1;
    private int requestedColumn = -1;
    
    public boolean isRequestedCell(int row, int column){
        return row == requestedRow && column == requestedColumn;
    }
    
    public void setRequestedCell(int row, int column){
        Rectangle oldRect = getCellRect(requestedRow, requestedColumn, false);
        requestedRow = row;
        requestedColumn = column;
        Rectangle newRect = getCellRect(row, column, false);
        repaint(oldRect);
        repaint(newRect);
    }
}
