/*
 * 作成日: 2006/04/02
 */
package net.sourceforge.swingx.jspread;

import java.awt.KeyboardFocusManager;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Date;
import java.util.EventObject;
import java.util.HashMap;

import javax.swing.JComponent;

import net.sourceforge.swingx.Logger;

/**
 * セルの編集を行います。
 * 【手動による編集開始】
 * JSpreadCellListener.mouseClicked(MouseEvent e)から呼ばれる。
 * 【手動による編集終了】
 * フォーカスが失ったとき。
 * JSpreadCellListener.mousePressed(MouseEvent e)
 * @author Asan
 */
class JSpreadControllerCellEditor {
	static Logger logger = Logger.getLogger(JSpreadControllerCellEditor.class);

	final JSpread spread;
	boolean atEdit = false;
	int editColumn = -1;
	int editRow = -1;
	private JComponent editorParent;
	Rectangle editorRect = null;
	JSpreadCellEditor editor = null;

    private PropertyChangeListener editorRemover = null;
	/** HashMap<Class, JSpreadDefaultCellEditor> */
    transient protected HashMap defaultEditorsByClass = null;
	
    
    
	JSpreadControllerCellEditor(JSpread spread) {
		this.spread = spread;
		// デフォルトのセル・エディタの登録
		createDefaultEditors();
	}
	/** デフォルトのセル・レンダラの登録. */
    protected void createDefaultEditors() {
    	defaultEditorsByClass = new HashMap();
        // Objects
    	defaultEditorsByClass.put(Object.class, new JSpreadCellEditor.DefaultCellEditor());
    	// Numbers
    	defaultEditorsByClass.put(Number.class, new JSpreadCellEditor.NumberEditor());
        // Booleans
    	defaultEditorsByClass.put(Boolean.class, new JSpreadCellEditor.BooleanEditor());
    	// Date
    	defaultEditorsByClass.put(Date.class, new JSpreadCellEditor.DateEditor());
    }
    
	/** セルを描画するのに使用されるコンポーネントを返します. */ 
    private JSpreadCellEditor getDefaultCellEditorComponent(Object value,
			int row, int column) {
		if (value != null) {
			for (Class cls = value.getClass(); cls != null; cls = cls.getSuperclass()) {
				JSpreadCellEditor editor = 
					(JSpreadCellEditor) defaultEditorsByClass.get(cls);
				if (editor != null)
					return editor;
			}
		}
		return (JSpreadCellEditor) defaultEditorsByClass.get(Object.class);
	}
    
	boolean isCellEditable(int rowDataIndex, int colDataIndex) { 
		return false;
	}
	/** セルのエディタを返します。isCellEditable()が常にfalseを返す場合は、呼ばれません。 */
	JSpreadCellEditor getCellEditor(Object value, 
			int rowDataIndex, int colDataIndex) {
		JSpreadCellEditor editor = getDefaultCellEditorComponent(value, rowDataIndex, colDataIndex);
		logger.debug("getCellEditor(row="+rowDataIndex+"col="+colDataIndex+
				") editor="+editor.getClass());
		return editor;
	}	
	/**
	 * 指定したセルの編集を開始します。
	 * 編集対象のシートは現在選択されているシートです。
	 * @param	rowViewIndex	列表示番号(viewIndex)(0..)
	 * @param	colViewIndex	行表示番号(viewIndex)(0..)
	 * @param event	イベント。編集を開始するかどうかの判断に使われる。nullのとき、イベントのチェックは行わない。
	 * @return	true:編集を開始した / false:編集を開始しなかった
	 */
	boolean editCellAt(int rowViewIndex, int colViewIndex, EventObject event) {
		logger.debug("editCellAt(row="+rowViewIndex+", column="+colViewIndex+", event="+event+")");

		// すでに編集中で、編集を終了できなかったら、新たな編集は行えない。
		if (editor != null && ! spread.stopCellEditing()) {
            return false;
        }
		
		// 編集を行うセルを調べる
        JSpreadSheet sheet = spread.getSheet();
		assert 0 <= rowViewIndex && rowViewIndex < sheet.rowHeader.getViewCount(): rowViewIndex;
		assert 0 <= colViewIndex && colViewIndex < sheet.colHeader.getViewCount(): colViewIndex;
		int colDataIndex = sheet.colHeader.getDataIndex(colViewIndex);
		int rowDataIndex = sheet.rowHeader.getDataIndex(rowViewIndex);

		// そのセルが編集可能でないならば何もしない。
		if (! spread.isCellEditable(rowDataIndex, colDataIndex)) {
			return false;
		}
		
		// TODO:フォーカスがなくなったら編集をやめるように、エディタを削除するクラスを作成
        if (editorRemover == null) {
            KeyboardFocusManager fm =
                KeyboardFocusManager.getCurrentKeyboardFocusManager();
            editorRemover = new CellEditorRemover(fm);
            fm.addPropertyChangeListener("permanentFocusOwner", editorRemover);
        }
		
		// 編集前の値を取得
		Object value = spread.getValueAt(rowDataIndex, colDataIndex);

		// セルの編集を行うコンポーネントを取得
		JSpreadCellEditor editor = spread.getCellEditor(
				value, rowDataIndex, colDataIndex);
		if (editor == null) {
			return false;
		}
		editor.setSpread(spread);
		editor.setCellValue(value);
		
		// 編集開始のイベントか？
		if (! editor.isCellEditable(event)) {
			return false;
		}
       
		// 編集を行う矩形、列、行、編集中かどうかを退避
		this.editor = editor;
		this.editorRect = new Rectangle(
				sheet.colHeader.getPoint(colViewIndex),
				sheet.rowHeader.getPoint(rowViewIndex),
				sheet.colHeader.getWidth(colDataIndex),
				sheet.rowHeader.getWidth(rowDataIndex)
		);
		this.editRow = rowViewIndex;
		this.editColumn = colViewIndex;
		this.atEdit = true;
		
		// どの窓で編集を行うか？分割が行われていた場合、フォーカスのある窓で編集を行う。
		//logger.debug("source="+event.getSource());
		JSpreadCellPane pane = (JSpreadCellPane) spread.getComponent("CellPane");//spread.getCellPane();
//		FIND_PANE:
//		for (int r=0; r<sheet.rowPaneNum; r++) {
//			for (int c=0; c<sheet.colPaneNum; c++) {
//				JSpreadCellPane p = spread.getCellPane(r, c);
//				logger.debug("pane["+r+"]["+c+"].hasFocus="+p.hasFocus());
//				if (p.hasFocus()) {
//					pane = p;
//					break FIND_PANE;
//				}
//			}
//		}
		
		JComponent comp = editor.getCellEditorComponent();
		pane.add(comp);
		this.editorParent = pane;
		comp.setBounds(this.editorRect);
		comp.validate();
		return true;
	}
	// TODO:セルエディタがまだ堅牢でない。あやしい。
	boolean stopCellEditing() {
		assert editor != null: "editor";
		logger.debug("stopCellEditing()");
		Object value = editor.getCellValue();
		spread.setValueAt(value, editRow, editColumn);
		removeEditor();
		return true;
	}
	void cancelCellEditing() {
		logger.debug("cancelCellEditing()");
		removeEditor();
	}
	private void removeEditor() {
		logger.debug("removeEditor() editor="+editor+" rect="+editorRect);
		assert editor != null: editor;
		atEdit = false;
		JComponent comp = editor.getCellEditorComponent();
		editorParent.repaint(editorRect.x, editorRect.y, editorRect.width, editorRect.height);
		editorParent.remove(comp);
		editor = null;
		editColumn = -1;
		editRow = -1;
		editorRect = null;
	}
//	Object getCellEditorValue() {
//		return editor.getCellValue();
//	}


	boolean isEditing() {
		return atEdit;
	}
	public JComponent getEditorComponent() {
		return editor != null ? editor.getCellEditorComponent() : null;
	}
	
    class CellEditorRemover implements PropertyChangeListener {
        KeyboardFocusManager focusManager;

        public CellEditorRemover(KeyboardFocusManager fm) {
            this.focusManager = fm;
        }

        public void propertyChange(PropertyChangeEvent ev) {
//        	logger.debug("@@@"+ev.getPropertyName()+
//        			" old="+ev.getOldValue()+
//        			" new="+ev.getNewValue());
        	if ( spread.isEditing() && ev.getPropertyName().equals("permanentFocusOwner") && ev.getOldValue()== editor) {
        		logger.debug("@@@ フォーカスがどこかにいったので編集終了");
        		// 編集を終了する。ダメならキャンセルする。
        		if (! spread.stopCellEditing()) {
					spread.cancelCellEditing();
				}
        	}
//            if (!isEditing() || spread.getClientProperty("terminateEditOnFocusLost") != Boolean.TRUE) {
//                return;
//            }
//
//            Component c = focusManager.getPermanentFocusOwner();
//            while (c != null) {
//                if (c == spread) {
//                    // focus remains inside the table
//                    return;
//                } else if ((c instanceof Window) ||
//                           (c instanceof Applet && c.getParent() == null)) {
//                    if (c == SwingUtilities.getRoot(spread)) {
//                        if (! spread.stopCellEditing()) {
//                            spread.cancelCellEditing();
//                        }
//                    }
//                    break;
//                }
//                c = c.getParent();
//            }

        }
    }
}
