/*FreeMind - A Program for creating and viewing Mindmaps
*Copyright (C) 2000-2006 Joerg Mueller, Daniel Polansky, Christian Foltin, Dimitri Polivaev and others.
*
*See COPYING for Details
*
*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.
*
*You should have received a copy of the GNU General Public License
*along with this program; if not, write to the Free Software
*Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
/*
 * Created on 12.06.2005
 *
 */
package freemind.view.mindmapview.attributeview;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.logging.Logger;

import javax.swing.ComboBoxEditor;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultCellEditor;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;

import freemind.main.Resources;
import freemind.modes.MindMapNode;
import freemind.modes.attributes.AttributeController;
import freemind.modes.attributes.AttributeRegistry;
import freemind.modes.attributes.AttributeTableLayoutModel;
import freemind.modes.attributes.AttributeTableModel;
import freemind.modes.attributes.ColumnWidthChangeEvent;
import freemind.modes.attributes.ColumnWidthChangeListener;
import freemind.view.mindmapview.MapView;
import freemind.view.mindmapview.NodeView;

/**
 * @author dimitri
 * 12.06.2005
 */
public class AttributeTable extends JTable implements ColumnWidthChangeListener{
    private static final int MAX_HEIGTH = 300;
    private static final int MAX_WIDTH = 600;
    static private class MyFocusListener implements FocusListener{
        private AttributeTable focusedTable; 
        /* (non-Javadoc)
         * @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent)
         */
        public void focusGained(final FocusEvent event) { 
            final Component source = (Component)event.getSource();
            final Component oppositeComponent = event.getOppositeComponent();
            if(source instanceof AttributeTable){
                focusedTable = (AttributeTable)source;
            }
            else{
                focusedTable = (AttributeTable)SwingUtilities.getAncestorOfClass(AttributeTable.class, source);
            }
            EventQueue.invokeLater(new Runnable(){
				public void run() {
					if(focusedTable != null){
						final Component newNodeViewInFocus = SwingUtilities.getAncestorOfClass(NodeView.class, focusedTable);
						if(newNodeViewInFocus != null){
							NodeView viewer = (NodeView)newNodeViewInFocus;
							if(viewer != viewer.getMap().getSelected()){
								viewer.getMap().selectAsTheOnlyOneSelected(viewer, false);
							}
						}
					}
				}
            });
        }
        
        /* (non-Javadoc)
         * @see java.awt.event.FocusListener#focusLost(java.awt.event.FocusEvent)
         */
        public void focusLost(FocusEvent event) {
            Component oppositeComponent = event.getOppositeComponent();
            Component newTable = SwingUtilities.getAncestorOfClass(AttributeTable.class, oppositeComponent);
            if(focusedTable != null && focusedTable != newTable){
                if (focusedTable.isEditing()){                
                    focusedTable.getCellEditor().stopCellEditing();
                } 
                if(!focusedTable.attributeView.isPopupShown()){
                    final AttributeView attributeView = focusedTable.getAttributeView();
                    final String currentAttributeViewType = attributeView.getNode().getMap().getRegistry().getAttributes().getAttributeViewType();
                    if(attributeView.getViewType() != currentAttributeViewType){
                        attributeView.stateChanged(null);
                    }
                }
                focusedTable = null;
            }
        }
        
    }
    static private class HeaderMouseListener extends MouseAdapter{
        public void mouseReleased(MouseEvent e) {
            JTableHeader header = (JTableHeader)e.getSource();
            AttributeTable table = (AttributeTable) header.getTable();
            float zoom = table.attributeView.getMapView().getZoom();
            Dimension preferredScrollableViewportSize = table.getPreferredScrollableViewportSize();
            JViewport port = (JViewport)table.getParent();
            Dimension extentSize = port.getExtentSize();
            if(preferredScrollableViewportSize.width !=extentSize.width){
                AttributeTableModel model = (AttributeTableModel)table.getModel();
                for(int col = 0; col < table.getColumnCount(); col++){
                    int modelColumnWidth = model.getColumnWidth(col);
                    int currentColumnWidth = (int) (table.getColumnModel().getColumn(col).getWidth() / zoom);
                    if(modelColumnWidth != currentColumnWidth){
                        model.setColumnWidth(col, currentColumnWidth);
                    }
                }
            }
        }
    }
    
    static private MyFocusListener focusListener = new MyFocusListener();
    static private MouseListener componentListener = new HeaderMouseListener();
    private int highRowIndex = 0;
    static private ComboBoxModel defaultComboBoxModel = null; 
    static private AttributeTableCellRenderer dtcr = new AttributeTableCellRenderer();
    private AttributeView attributeView;
    private static final int EXTRA_HEIGHT = 4;
    private static final float TABLE_ROW_HEIGHT = 4;
    private static final Dimension prefHeaderSize = new Dimension(1, 8);
	private static Logger mLogger = null;
    AttributeTable(AttributeView attributeView) {
        super();
        if (mLogger == null) {
			mLogger = Resources.getInstance().getLogger(
					this.getClass().getName());
		}
		this.attributeView = attributeView;
        addFocusListener(focusListener);
        final MindMapNode model = attributeView.getNodeView().getModel();
        final AttributeController attributeController = model.getMap().getRegistry().getModeController().getAttributeController();
        if(attributeController != null){
            getTableHeader().addMouseListener(componentListener);
        }
        else{
            getTableHeader().setResizingAllowed(false);
        }
        setModel(attributeView.getCurrentAttributeTableModel());
        updateFontSize(this, 1F);
        updateColumnWidths();       
        setAutoResizeMode(AUTO_RESIZE_OFF);
        getTableHeader().setReorderingAllowed(false);
        getTableHeader().setPreferredSize(prefHeaderSize);
        int h = getRowHeight();
        updateRowHeights();
        setRowSelectionAllowed(false);
        putClientProperty("JTable.autoStartsEdit", Boolean.FALSE);
    }
    
    
    public TableCellRenderer getCellRenderer(int row, int column) {
        String text = getValueAt(row, column).toString();
        dtcr.setText(text);
        int prefWidth = dtcr.getPreferredSize().width;
        int width = getColumnModel().getColumn(column).getWidth();
        if(prefWidth > width){
            dtcr.setToolTipText(text);
        }
        else{
            dtcr.setToolTipText(null);
        }
        return dtcr;
    }
    public TableCellEditor getCellEditor(int row, int column) {
        JComboBox comboBox = new JComboBox();
        DefaultCellEditor dce = new DefaultCellEditor(comboBox);
        return dce;
    }
    /**
     * 
     */
    public Component prepareEditor(TableCellEditor tce, int row, int col) {
        ComboBoxModel model;
        JComboBox comboBox = (JComboBox) ((DefaultCellEditor) tce).getComponent();
        MindMapNode node = getAttributeTableModel().getNode();
        AttributeRegistry attributes = node.getMap().getRegistry().getAttributes();
        switch (col){
        case 0:        
            model = attributes.getComboBoxModel();
            comboBox.setEditable(! attributes.isRestricted());
            break;
        case 1:
            String attrName = getAttributeTableModel().getValueAt(row, 0).toString();
            model = attributes.getDefaultComboBoxModel(attrName);
            comboBox.setEditable(! attributes.isRestricted(attrName));
            break;
        default:
            model = getDefaultComboBoxModel();
        }
        comboBox.setModel(model);
        model.setSelectedItem(getValueAt(row, col));
        
        comboBox.addFocusListener(focusListener);
        comboBox.getEditor().getEditorComponent().addFocusListener(focusListener);
        Component editor = super.prepareEditor(tce, row, col);
        updateFontSize(editor, getZoom());
        return editor;
    }
    
    public Dimension getPreferredScrollableViewportSize() {
        if(! isValid())
            validate();
        float zoom = getZoom();
        Dimension dimension = super.getPreferredSize();
        dimension.width = Math.min((int)(MAX_WIDTH * zoom) , dimension.width);
        dimension.height = Math.min((int)(MAX_HEIGTH * zoom) - getTableHeaderHeight(), dimension.height);
        return dimension;
    }
    
    static ComboBoxModel getDefaultComboBoxModel() {
        if (defaultComboBoxModel == null)
        {
            defaultComboBoxModel = new DefaultComboBoxModel();
        }
        return defaultComboBoxModel;
    }
    public void changeSelection(int rowIndex, int columnIndex, boolean toggle,
            boolean extend) {
        int rowCount = getRowCount();
        if(rowCount == 0)
            return;        
        if(rowIndex >= rowCount){
            rowIndex = 0;
            columnIndex = 0;
        }
        changeSelectedRowHeight(rowIndex);
        super.changeSelection(rowIndex, columnIndex, toggle, extend);        
    }
    private void changeSelectedRowHeight(int rowIndex) {
        if(highRowIndex != rowIndex){
            if(highRowIndex < getRowCount()){
                int h = getRowHeight(highRowIndex);
                setRowHeight(highRowIndex, h - EXTRA_HEIGHT);
            }
            int h = getRowHeight(rowIndex);
            setRowHeight(rowIndex, h + EXTRA_HEIGHT);
            highRowIndex = rowIndex;
        }
    }
    
    
    /**
     * 
     */
    void updateAttributeTable() {
        updateFontSize(this, 1F);
        updateRowHeights();
        updateColumnWidths();       
    }
    
    private void updateColumnWidths() {
        float zoom = getZoom();
        for(int i = 0; i < 2; i++){
            int width = (int) (getAttributeTableModel().getColumnWidth(i) * zoom);
            getColumnModel().getColumn(i).setPreferredWidth(width);            
        }
    }
    
    private void updateRowHeights() {
        int rowCount = getRowCount();
        if(rowCount == 0)
            return;
        
        int constHeight = getTableHeaderHeight() + EXTRA_HEIGHT;        
        float zoom = getZoom();
        float fontSize = getFontSize();
        float tableRowHeight = fontSize + zoom * TABLE_ROW_HEIGHT;
        int newHeight = (int)((tableRowHeight * rowCount + (zoom - 1)* constHeight) / rowCount);
        if(newHeight < 1){
            newHeight = 1;
        }
        int highRowsNumber = (int)((tableRowHeight - newHeight)* rowCount);
        for (int i = 0; i < highRowsNumber; i++)
        {
            setRowHeight(i, 1 + newHeight + (i == highRowIndex ? EXTRA_HEIGHT : 0));
        }
        for (int i = highRowsNumber; i < rowCount; i++)
        {
            setRowHeight(i, newHeight + (i == highRowIndex ? EXTRA_HEIGHT : 0));
        }
    }


    int getTableHeaderHeight() {
        final JTableHeader tableHeader = getTableHeader();
        return tableHeader != null ? tableHeader.getPreferredSize().height : 0;
    }
    
    private void updateFontSize(Component c, float zoom) {
        // 1) Determine font
        Font font = c.getFont();
        if (font != null) {
            float oldFontSize = font.getSize2D();
            float newFontSize = getFontSize() * zoom;
            if(oldFontSize != newFontSize)
            {
                font = font.deriveFont(newFontSize);
                c.setFont(font); 
            }
        }
    }
    float getZoom() {
        return attributeView.getMapView().getZoom();
    }
    
    public void tableChanged(TableModelEvent e) {
        super.tableChanged(e);
        if(getParent() == null)
            return;
        
        if(e.getType() == TableModelEvent.DELETE
                && e.getFirstRow() == highRowIndex
                && e.getFirstRow() == getRowCount()
                && e.getFirstRow() != 0){
            changeSelection(e.getFirstRow()-1, 0, false, false);
        }
        else{
            updateRowHeights();
        }
        
        MapView map = getAttributeView().getNodeView().getMap();
        getParent().getParent().invalidate();
        map.getModel().nodeChanged(getAttributeView().getNode());
    }
    
    public void viewRemoved() {
        getModel().removeTableModelListener(this);
    }
    
    
    /* (non-Javadoc)
     * @see javax.swing.JTable#removeNotify()
     */
    public void removeNotify() {
        // TODO Auto-generated method stub
        super.removeNotify();
    }


    private float getFontSize() {
        return (attributeView.getNodeView().getModel().getMap().getRegistry().getAttributes().getFontSize());
    }
    public void setModel(TableModel dataModel) {
        super.setModel(dataModel);
    }
    
    private void removeListenerFromEditor() {
        JComboBox comboBox = (JComboBox) getEditorComponent();
        comboBox.removeFocusListener(focusListener);
        comboBox.getEditor().getEditorComponent().removeFocusListener(focusListener);
        comboBox.setModel(new DefaultComboBoxModel());
    }
    
    public void removeEditor() {
        removeListenerFromEditor();
        getAttributeTableModel().editingCanceled();
        super.removeEditor();
    }
    
    
    /**
     * @return Returns the currentModel.
     */
    public AttributeTableModelDecoratorAdapter getAttributeTableModel() {
        return (AttributeTableModelDecoratorAdapter)getModel();
    }
    public AttributeView getAttributeView() {
        return attributeView;
    }
    
    
    /**
     * 
     */
    public void setOptimalColumnWidths() {
        Component comp = null;
        int cellWidth = 0;
        int maxCellWidth = 2 * (int)(Math.ceil(getFontSize() + TABLE_ROW_HEIGHT));
        for (int col = 0; col < 2; col++) {
            for(int row = 0; row < getRowCount(); row++){
                comp = dtcr.getTableCellRendererComponent(
                        this, getValueAt(row, col),
                        false, false, row, col);
                cellWidth = comp.getPreferredSize().width;
                maxCellWidth = Math.max(cellWidth, maxCellWidth);
            }
            getAttributeTableModel().setColumnWidth(col, maxCellWidth + 1);
        }
    }
    
    
    /**
     */
    public void insertRow(int row) {
        if(getModel() instanceof ExtendedAttributeTableModelDecorator){
            ExtendedAttributeTableModelDecorator model = (ExtendedAttributeTableModelDecorator)getModel();
            if (isEditing() && getCellEditor() != null 
                    && !getCellEditor().stopCellEditing()) {
                return;
            }
            model.insertRow(row);
            changeSelection(row, 0, false, false);
            if(editCellAt(row, 0))
                getEditorComponent().requestFocus();
        }
    }
    
    
    /**
     */
    public void removeRow(int row) {
        if(getModel() instanceof ExtendedAttributeTableModelDecorator){
            ExtendedAttributeTableModelDecorator model = (ExtendedAttributeTableModelDecorator)getModel();
            model.removeRow(row);
        }
    }
    
    
    /**
     */
    public void moveRowUp(int row) {
        if(getModel() instanceof ExtendedAttributeTableModelDecorator){
            ExtendedAttributeTableModelDecorator model = (ExtendedAttributeTableModelDecorator)getModel();
            model.moveRowUp(row);
        }
    }
    
    
    /**
     */
    public void moveRowDown(int row) {
        if(getModel() instanceof ExtendedAttributeTableModelDecorator){
            ExtendedAttributeTableModelDecorator model = (ExtendedAttributeTableModelDecorator)getModel();
            model.moveRowDown(row);
        }
    }
    public void columnWidthChanged(ColumnWidthChangeEvent event) {
    	float zoom = getZoom();
        int col = event.getColumnNumber();
        AttributeTableLayoutModel layoutModel = (AttributeTableLayoutModel) event.getSource();
        int width = layoutModel.getColumnWidth(col);
        getColumnModel().getColumn(col).setPreferredWidth((int)(width * zoom));
        getAttributeView().getNode().getMap().nodeChanged(getAttributeView().getNode());
    }
    
    protected boolean processKeyBinding(KeyStroke ks, KeyEvent e,
            int condition, boolean pressed) {
    	mLogger.info("AttributeTable.processKeyBinding");
        if( ks.getKeyCode() == KeyEvent.VK_TAB
                && e.getModifiers() == 0
                && pressed
                && getSelectedColumn() == 1
                && getSelectedRow() == getRowCount()-1
                && getModel() instanceof ExtendedAttributeTableModelDecorator){
            insertRow(getRowCount());
            return true;            
        }
        if( ks.getKeyCode() == KeyEvent.VK_ESCAPE
                && e.getModifiers() == 0
                && pressed){
            attributeView.getNodeView().requestFocus();
            return true;            
        }
        boolean retValue =  super.processKeyBinding(ks, e, condition, pressed);
        // Start editing when a key is typed. UI classes can disable this behavior
        // by setting the client property JTable.autoStartsEdit to Boolean.FALSE.
        if (!retValue && condition == WHEN_FOCUSED &&
                isFocusOwner() && ks.getKeyCode() != KeyEvent.VK_TAB &&
                e != null && e.getID() == KeyEvent.KEY_PRESSED && !e.isActionKey() 
                && e.getKeyChar() != KeyEvent.CHAR_UNDEFINED
                && 0 == (e.getModifiers()& (KeyEvent.CTRL_MASK | KeyEvent.ALT_MASK))) {
            // We do not have a binding for the event.
            // Try to install the editor
            int leadRow = getSelectionModel().getLeadSelectionIndex();
            int leadColumn = getColumnModel().getSelectionModel().
            getLeadSelectionIndex();
            if (leadRow != -1 && leadColumn != -1 && !isEditing()) {
                if (!editCellAt(leadRow, leadColumn)) {
                    return false;
                }
            }
            Component editorComponent = getEditorComponent();
            // If the editorComponent is a JComboBox, pass the event to it.
            if (editorComponent instanceof JComboBox) {
                JComboBox comboBox = (JComboBox)editorComponent;
                if(comboBox.isEditable()){
                    ComboBoxEditor editor = comboBox.getEditor();
                    editor.selectAll(); // to enable overwrite
                    KeyEvent keyEv;
                    keyEv =
                        new KeyEvent(
                                editor.getEditorComponent(),
                                KeyEvent.KEY_TYPED,
                                e.getWhen(),
                                e.getModifiers(),
                                KeyEvent.VK_UNDEFINED,
                                e.getKeyChar(),
                                KeyEvent.KEY_LOCATION_UNKNOWN);
                    retValue = SwingUtilities.processKeyBindings(keyEv);
                }
                else{
                    editorComponent.requestFocus();
                    retValue = true;
                }                
            }
        }
        if(ks.getKeyCode() == KeyEvent.VK_SPACE){
            return true;
        }
        return retValue;
    }
    public boolean isVisible(){
        return attributeView.areAttributesVisible();
    }
}
