package org.limewire.ui.swing.table;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseMotionListener;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;

import org.jdesktop.swingx.decorator.ColorHighlighter;
import org.jdesktop.swingx.decorator.ComponentAdapter;
import org.jdesktop.swingx.decorator.HighlightPredicate;
import org.limewire.ui.swing.util.PropertyUtils;

import ca.odell.glazedlists.gui.AdvancedTableFormat;
import ca.odell.glazedlists.gui.TableFormat;
import ca.odell.glazedlists.swing.DefaultEventTableModel;

public class MouseableTable extends StripedJXTable {

    private TablePopupHandler popupHandler;

    private TableDoubleClickHandler rowDoubleClickHandler;

    private TableColumnDoubleClickHandler columnDoubleClickHandler;

    private TableColors colors = newTableColors();

    private TableCellHeaderRenderer defaultRenderer;

    protected MouseMotionListener mouseOverEditorListener;

    public MouseableTable() {
        initialize();
    }

    protected TableColors newTableColors() {
        return new TableColors();
    }

    public TableColors getTableColors() {
        return colors;
    }

    public MouseableTable(TableModel model) {
        super(model);
        initialize();
    }

    public void setPopupHandler(TablePopupHandler popupHandler) {
        this.popupHandler = popupHandler;
    }

    public void setDoubleClickHandler(TableDoubleClickHandler tableDoubleClickHandler) {
        this.rowDoubleClickHandler = tableDoubleClickHandler;
    }

	public void setColumnDoubleClickHandler(TableColumnDoubleClickHandler columnDoubleClickHandler) {
        this.columnDoubleClickHandler = columnDoubleClickHandler;
    }

    @Override
    public String getToolTipText(MouseEvent event) {
        int row = rowAtPoint(event.getPoint());
        int col = columnAtPoint(event.getPoint());
        if (row > -1 && col > -1) {
            return getToolTipText(row, col);
        }

        return null;
    }

    /**
     * Returns the tooltip text for the item at the given row and column.
     * The default implementation only shows the tooltip text if the text is clipped. 
     */
    protected String getToolTipText(int row, int col) {
        Object value = getValueAt(row, col);
        JComponent renderer = getRendererComponent(row, col, value);

        if (value != null && isClipped(renderer, col)) {
            String toolTip = renderer.getToolTipText();

            if (toolTip != null) {
                return toolTip;
            } else if (renderer instanceof JLabel) {
                // works for DefaultTableCellRenderer
                return ((JLabel) renderer).getText();
            }

            return PropertyUtils.getToolTipText(value);
        }
        
        return null;
    }

    /**
     * Checks if the renderer fits in the column.
     * 
     * @param row the view index of the row
     * @param col the view index of the column
     * @return true if the column width is less than the preferred width of the
     *         renderer
     */
    private boolean isClipped(JComponent renderer, int col) {
        return renderer.getPreferredSize().width > getColumnModel().getColumn(col).getWidth();
    }

    private JComponent getRendererComponent(int row, int col, Object value) {
        TableCellRenderer tcr = getCellRenderer(row, col);
        return (JComponent) tcr.getTableCellRendererComponent(this, value, false, false, row, col);
    }
      


    protected void initialize() {	
        setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);        
        setCellSelectionEnabled(false);
        setRowSelectionAllowed(true);
        setTableHeaderRenderer();
        setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
        setFont(colors.getTableFont());

        //HighlightPredicate.EVEN and HighlightPredicate.ODD are zero based
        setHighlighters(colors.getEvenHighlighter(), 
                colors.getOddHighlighter(),
                new ColorHighlighter(new MenuHighlightPredicate(this), colors.menuRowColor,  colors.menuRowForeground, colors.menuRowColor, colors.menuRowForeground));

        setGridColor(colors.getGridColor());

        //so that mouseovers will work within table		
        mouseOverEditorListener = new MouseMotionAdapter() {
            @Override
            public void mouseMoved(MouseEvent e) {
                // Get the table cell that the mouse is over.
                int row = rowAtPoint(e.getPoint());
                int col = columnAtPoint(e.getPoint());
                
                // If the cell is editable and
                // it's not already being edited ...
                if (isCellEditable(row, col) && (row != getEditingRow() || col != getEditingColumn())) {
                    editCellAt(row, col);
                } else {
                    maybeCancelEditing();
                }
            }
        };
        
        addMouseMotionListener(mouseOverEditorListener);

        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {//adding this to editor messes up popups

                int col = columnAtPoint(e.getPoint());
                int row = rowAtPoint(e.getPoint());

                if (row >= 0 && col >= 0) {
                    if (rowDoubleClickHandler != null || columnDoubleClickHandler != null) {
                        Component component = e.getComponent();
                        //launch file on double click unless the click is on a button
                        if (e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton(e)
                                && !(component.getComponentAt(e.getPoint()) instanceof JButton)) {
                            if (rowDoubleClickHandler != null) {
                                rowDoubleClickHandler.handleDoubleClick(row);
                            }
                            if (columnDoubleClickHandler != null) {
                                columnDoubleClickHandler.handleDoubleClick(col);
                            }
                        }
                    }
                }
            }
            

            @Override
            public void mouseExited(MouseEvent e) {
                maybeCancelEditing();
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                maybeShowPopup(e);
            }

            @Override
            public void mousePressed(MouseEvent e) {
                int col = columnAtPoint(e.getPoint());
                int row = rowAtPoint(e.getPoint());
                if (isEditing() && isCellEditable(row, col)) { 
                    TableCellEditor editor = getCellEditor(row, col);
                    if (editor != null) {
                        // force update editor colors
                        prepareEditor(editor, row, col);
                    }                        
                }
                maybeShowPopup(e);
            }

            private void maybeShowPopup(MouseEvent e) {
                if (e.isPopupTrigger() && popupHandler != null) {
                    int col = columnAtPoint(e.getPoint());
                    int row = rowAtPoint(e.getPoint());
                    if (row >= 0 && col >= 0) {
                        popupHandler.maybeShowPopup(
                            e.getComponent(), e.getX(), e.getY());
                        TableCellEditor editor = getCellEditor();
                        if (editor != null) {
                            editor.cancelCellEditing();
                        }
                    }
                }
            }

        });
    }

    //Don't set the cell value when editing is cancelled
    @Override
    public void editingStopped(ChangeEvent e) {
        TableCellEditor editor = getCellEditor();
        if (editor != null) {          
            removeEditor();
        }
    }

    public void setStripeHighlighterEnabled(boolean striped){
        if (striped) {
            // HighlightPredicate.EVEN and HighlightPredicate.ODD are zero based
            setHighlighters(
                    colors.getEvenHighlighter(),
                    colors.getOddHighlighter());

        } else {
            setHighlighters(
                    new ColorHighlighter(HighlightPredicate.EVEN, colors.evenColor,
                            colors.evenForeground, colors.evenColor,
                            colors.evenForeground),
                    new ColorHighlighter(HighlightPredicate.ODD, colors.evenColor,
                            colors.evenForeground, colors.evenColor,
                            colors.evenForeground),
                    new ColorHighlighter(new MenuHighlightPredicate(this), colors.menuRowColor,
                            colors.menuRowForeground, colors.menuRowColor, colors.menuRowForeground));

        }
    }

    // gets rid of default editor color so that editors are colored by highlighters and selection color is shown
    @Override
    public Component prepareEditor(TableCellEditor editor, int row, int column) {
        Component comp = super.prepareEditor(editor, row, column);
        
        if (compoundHighlighter != null) {
            ComponentAdapter adapter = getComponentAdapter(row, column);
            comp = compoundHighlighter.highlight(comp, adapter);
        }
        
        return comp;
    }
    
    protected void setTableHeaderRenderer() {
        JTableHeader th = getTableHeader();
        th.setDefaultRenderer(getTableCellHeaderRenderer());
    }
    
    private TableCellRenderer getTableCellHeaderRenderer() {
        if(defaultRenderer == null)
            defaultRenderer = new TableCellHeaderRenderer();
        return defaultRenderer;
    }
    
    /**
     * Fills in the top right corner if a scrollbar appears
     * with an empty table header.
     */
    @Override
    protected void configureEnclosingScrollPane() {
        super.configureEnclosingScrollPane();
        
        Container p = getParent();
        if (p instanceof JViewport) {
            Container gp = p.getParent();
            if (gp instanceof JScrollPane) {
                JScrollPane scrollPane = (JScrollPane)gp;
                // Make certain we are the viewPort's view and not, for
                // example, the rowHeaderView of the scrollPane -
                // an implementor of fixed columns might do this.
                JViewport viewport = scrollPane.getViewport();
                if (viewport == null || viewport.getView() != this) {
                    return;
                }
                JTableHeader th = new JTableHeader();
                th.setDefaultRenderer(getTableCellHeaderRenderer());
                // Put a dummy header in the upper-right corner.
                final Component renderer = th.getDefaultRenderer().getTableCellRendererComponent(null, "", false, false, -1, -1);
                JPanel cornerComponent = new JPanel(new BorderLayout());
                cornerComponent.add(renderer, BorderLayout.CENTER);
                scrollPane.setCorner(ScrollPaneConstants.UPPER_RIGHT_CORNER, cornerComponent);
            }
        }
    }
          
    /**
     * @return whether or not a popup menu is showing on the row
     */
    public boolean isMenuShowing(int row) {
    	if(popupHandler != null) {
    		return popupHandler.isPopupShowing(row);
    	}
    	return false;
    }
    
    @Override
    public boolean isCellEditable(int row, int col) {
        if (row >= getRowCount() || col >= getColumnCount() || row < 0 || col < 0) {
            return false;
        }
        return getColumnModel().getColumn(col).getCellEditor() != null;
    }

    /**
     * Does this row have a popup menu showing?
     */
    private static class MenuHighlightPredicate implements HighlightPredicate {

        private MouseableTable table;

        public MenuHighlightPredicate(MouseableTable table) {
            this.table = table;
        }

        @Override
		public boolean isHighlighted(Component renderer,
                ComponentAdapter adapter) {
            if (!adapter.getComponent().isEnabled())
                return false;

            return table.isMenuShowing(adapter.row);
        }
    }

    @Override
    public void setDefaultEditor(Class clazz, TableCellEditor editor) {
        boolean usesEventTableModel = getModel() instanceof DefaultEventTableModel;
        boolean usesAdvancedTableFormat = false;
        TableFormat tableFormat = null;

        if (usesEventTableModel) {
            tableFormat = ((DefaultEventTableModel)getModel()).getTableFormat();
            usesAdvancedTableFormat =
                tableFormat instanceof AdvancedTableFormat;
        }

        if (usesEventTableModel && usesAdvancedTableFormat) {
            AdvancedTableFormat format = (AdvancedTableFormat) tableFormat;
            for (int i = 0; i < getModel().getColumnCount(); i++) {
                Class columnClass = format.getColumnClass(i);
                if (columnClass == clazz) {
                    getColumnModel().getColumn(i).setCellEditor(editor);
                }
            }
        } else {
            super.setDefaultEditor(clazz, editor);
        }
    }

    @Override
    public void setDefaultRenderer(Class clazz, TableCellRenderer renderer) {
        boolean usesEventTableModel = getModel() instanceof DefaultEventTableModel;
        boolean usesAdvancedTableFormat = false;
        TableFormat tableFormat = null;

        if (usesEventTableModel) {
            tableFormat = ((DefaultEventTableModel)getModel()).getTableFormat();
            usesAdvancedTableFormat =
                tableFormat instanceof AdvancedTableFormat;
        }

        if (usesEventTableModel && usesAdvancedTableFormat) {
            AdvancedTableFormat format = (AdvancedTableFormat) tableFormat;
            for (int i = 0; i < getModel().getColumnCount(); i++) {
                Class columnClass = format.getColumnClass(i);
                if (columnClass == clazz) {
                    getColumnModel().getColumn(i).setCellRenderer(renderer);
                    break;
                }
            }
        } else {
            super.setDefaultRenderer(clazz, renderer);
        }
    }
    
    /**
     * Ensures the given row is visible.
     */
    public void ensureRowVisible(int row) {
        if(row != -1) {
            Rectangle cellRect = getCellRect(row, 0, false);
            Rectangle visibleRect = getVisibleRect();
            if( !visibleRect.intersects(cellRect) )
                scrollRectToVisible(cellRect);
        }

    }
    
    public boolean isColumnVisible(int column) {
        Rectangle cellRect = getCellRect(0, column, false);
        Rectangle visibleRect = getVisibleRect();
        return visibleRect.intersects(cellRect);
    }
    
    //clears mouseover color
    private void maybeCancelEditing() {
        Point mousePosition = getMousePosition();
        if (getCellEditor() != null && 
                (mousePosition == null || rowAtPoint(mousePosition) == -1 || columnAtPoint(mousePosition) == -1)){
            getCellEditor().cancelCellEditing();
        } 
    }

}