package ash.gui.editor.core;
import java.io.File;
import java.util.List;
import java.util.ArrayList;
import java.awt.Component;
import java.awt.Font;
import javax.swing.JFileChooser;
import javax.swing.JMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JScrollBar;
import javax.swing.JOptionPane;
import javax.swing.JTabbedPane;
import java.awt.event.MouseEvent;
import java.awt.event.ActionEvent;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.MenuEvent;
import javax.swing.filechooser.FileFilter;
// VK_*: F, N, O, S, A, R, X, W, E, U, Z, R, Y, T, C, P, V, D, DELETE
import static java.awt.event.KeyEvent.*;
import ash.util.Log;
import ash.util.FileUtil;
import ash.gui.core.AshGUIFrame;
import ash.gui.core.AshMenu;
import ash.gui.core.AshPopupMenu;
import ash.gui.core.GUIAdapter;

/**
 * エディタフレーム
 */
@SuppressWarnings("serial")
abstract public class AshEditorFrame extends AshGUIFrame {
	/**
	 * コンストラクタ：
	 * 　メニューバー、ツールバー、描画領域、ステータスバーを生成する。
	 */
	public AshEditorFrame(String title) {
		super(title);
		showUntitled();
		showCursorPos(1, 0);
	}

	static final String UNTITLED = "Untitled";
	private void showUntitled() { setMessage(UNTITLED); }
	private void showFileName(String filename) {
		setMessage(FileUtil.normalPath(filename));
	}
	/*
	 * メニューの作成
	 */
	protected static final String FILE_NEW    = actionName();
	protected static final String FILE_OPEN   = actionName();
	protected static final String FILE_SAVE   = actionName();
	protected static final String FILE_SAVEAS = actionName();
	protected static final String FILE_REVERT = actionName();
	protected static final String FILE_CLOSE  = actionName();
	protected static final String FILE_EXIT   = actionName();
	protected static final String EDIT_UNDO   = actionName();
	protected static final String EDIT_REDO   = actionName();
	protected static final String EDIT_COPY   = actionName();
	protected static final String EDIT_CUT    = actionName();
	protected static final String EDIT_PASTE  = actionName();
	protected static final String EDIT_DELETE = actionName();
	protected static final String EDIT_SELALL = actionName();
	protected static final String EDIT_SEARCH = actionName();
	protected static final String EDIT_NEXT   = actionName();
	protected static final String EDIT_PREV   = actionName();
	protected static final String EDIT_REGEX  = actionName();
	protected static final String EDIT_REPLACE= actionName();
	protected static final String EDIT_GOTO   = actionName();
	protected static final String VIEW_FONT   = actionName();
	protected static final String VIEW_STATUS = actionName();

	/*
	@Override protected JMenuBar createMenuBar() {
		assert Log.debug(1, "==>AshEditorFrame#createMenuBar()");
		JMenuBar mbar = new JMenuBar();
		mbar.add(new FileMenu());
		mbar.add(new EditMenu());
		mbar.add(new ViewMenu());
		return mbar;
	}
	*/
	protected class FileMenu extends AshMenu {
		public FileMenu() {
			construct();
		}
		protected void construct() {
			setup("ファイル(F)");
			setActionListener(AshEditorFrame.this);
			add("新規(N)", FILE_NEW,  VK_N);
			add("開く(O)", FILE_OPEN, VK_O);
			add("保存(S)", FILE_SAVE, VK_S);
			add("名前を付けて保存(A)",  FILE_SAVEAS);
			add("復帰(R)", FILE_REVERT);
			addSeparator();
			add("終了(X)", FILE_EXIT, VK_W);
		}
	}
	protected class EditMenu extends AshMenu {
		private JMenuItem cut, copy, delete, selAll;
		protected void constructUndo() {
			add("元に戻す(U)",  EDIT_UNDO, VK_Z);
			add("やり直し(R)",  EDIT_REDO, VK_Y);
		}
		protected void constructBasics() {
			cut    = add("切り取り(T)",  EDIT_CUT,    VK_X);
			copy   = add("コピー(C)",    EDIT_COPY,   VK_C);
					 add("貼り付け(P)",  EDIT_PASTE,  VK_V);
			delete = add("削除(D)",      EDIT_DELETE, VK_DELETE, 0);
			selAll = add("すべて選択(A)",EDIT_SELALL, VK_A);
		}
		@Override public void menuSelected(MenuEvent e) {
			//setMessage("編集メニューが選択されました。");
			if(cut == null) return;
			boolean selected = editArea.isSelected();
			cut.setEnabled(selected);
			copy.setEnabled(selected);
			delete.setEnabled(selected);
			selAll.setEnabled(editArea.isSelectable());
		}
	}
	/*
	protected class ViewMenu extends AshMenu {
		ViewMenu() {
			setup("表示(V)");
			setActionListener(AshEditorFrame.this);
			add("フォント(F)",   VIEW_FONT);
			addSeparator();
			add(makeStatusMenuItem());
		}
	}
	*/

	/*
	 * ポップアップメニューの作成と実行
	 */
	protected AshPopupMenu createPopupMenu(Component popupArea) {
		return new EditAreaPopup(popupArea);
	}
	protected class EditAreaPopup extends AshPopupMenu {
		private JMenuItem cut, copy, delete, selAll;
		protected EditAreaPopup(Component popupArea) { super(popupArea); }
		protected void construct() {
			setActionListener(AshEditorFrame.this);
			cut    = add("切り取り", EDIT_CUT);
			copy   = add("コピー",   EDIT_COPY);
					 add("貼り付け", EDIT_PASTE);
			delete = add("削除",     EDIT_DELETE);
					 addSeparator();
			selAll = add("すべて選択", EDIT_SELALL);
					 addSeparator();
					 add("閉じる",   FILE_CLOSE);
		}
		@Override protected void popupSelected(MouseEvent me) {
			boolean selected = editArea.isSelected();
			cut.setEnabled(selected);
			copy.setEnabled(selected);
			delete.setEnabled(selected);
			selAll.setEnabled(editArea.isSelectable());
		}
	}

	/*
	 * アクションの実行
	 *   ActionListener インタフェースの実装
	 */
	public void actionPerformed(ActionEvent ae) {
		String action = ae.getActionCommand();
		if(action.equals(FILE_NEW)) {			// 新規作成
			newFile();
		} else if(action.equals(FILE_OPEN)) {	// 開く
			openFile();
		} else if(action.equals(FILE_SAVE)) {	// 上書き保存
			saveFile(editArea);
		} else if(action.equals(FILE_SAVEAS)) {	// 名前を付けて保存
			saveAsFile(editArea);
		} else if(action.equals(FILE_REVERT)) {	// 復帰する
			revertFile(editArea);
		} else if(action.equals(FILE_CLOSE)) {	// クローズ
			closeFile();
		} else if(action.equals(FILE_EXIT)) {	// 終了
			disposeFrame();
		} else if(action.equals(EDIT_UNDO)) {	// アンドゥ
			editArea.undo();
		} else if(action.equals(EDIT_REDO)) {	// リドゥ
			editArea.redo();
		} else if(action.equals(EDIT_COPY)) {	// コピー
			editArea.copy();
		} else if(action.equals(EDIT_CUT)) {	// カット
			editArea.cut();
		} else if(action.equals(EDIT_PASTE)) {	// ペースト
			editArea.paste();
		} else if(action.equals(EDIT_DELETE)) {	// 削除
			editArea.delete();
		} else if(action.equals(EDIT_GOTO)) {	// 行へ移動
			editArea.gotoLine();
		} else if(action.equals(EDIT_SELALL)) {	// 全てを選択
			editArea.selectAll();
		} else if(action.equals(VIEW_FONT)) {	// フォント
			editArea.setFont();
		} else {
			super.actionPerformed(ae);
		}
	}

	/**
	 * 編集対象のドキュメントに対するエディタ領域を生成する。
	 * インスタンス生成を拡張クラスで実装しなければならない。
	 * ◆Factory Method パターンを適用
	 */
	abstract protected AshEditorArea createEditArea();
	protected void setEditArea(AshEditorArea editArea) {	// CA,20111006
		this.editArea = editArea;
	}
	private AshEditorArea editArea;							// CA,20111006
	private JTabbedPane tabbedPane;		// 複数ファイル編集の場合に使用する。
	private boolean isMultiEditor() { return tabbedPane != null; }
	private AshScrollPane scrollPane;
	private int findTabIndex(AshEditorArea ea) {
		int n = tabbedPane.getTabCount();
		for(int i = 0; i < n; i++) {
			AshScrollPane sp = (AshScrollPane)tabbedPane.getComponentAt(i);
			if(sp.editArea == ea) return i;
		}
		return -1;
	}
	private String getTabTitle(AshEditorArea ea) {
		if(tabbedPane == null) return UNTITLED;
		return tabbedPane.getTitleAt(findTabIndex(ea));
	}
	private void setTabTitle(AshEditorArea ea, String title) {
		tabbedPane.setTitleAt(findTabIndex(ea), title);
	}


	static int scrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED;
	private static class AshScrollPane extends JScrollPane {
		AshEditorArea editArea;
		AshScrollPane(AshEditorArea editArea) {
			this.editArea = editArea;
			setViewportView(editArea.getEditArea());
			setEnabled(true);
			setHorizontalScrollBarPolicy(scrollBarPolicy);
		}
		JScrollBar getVScrollBar() { return verticalScrollBar; }
	}
	public final int getViewHeight() {
		return scrollPane.getHeight();
	}
	public final int getTopPixel() {
		return scrollPane.getVScrollBar().getValue();
	}
	public final void setTopPixel(int value) {
		scrollPane.getVScrollBar().setValue(value);
	}

	/**
	 * 編集領域の変更を告知する。
	 * @param ea 編集領域
	 * @param text 告知内容
	 */
	void notifyUpdate(AshEditorArea ea, String text) {
		if(isMultiEditor()) {
			Log.debug(55, text);
			int index = findTabIndex(ea);
			if(ea != editArea) {
				resetEditArea(ea);
				tabbedPane.setSelectedIndex(index);
				scrollPane = (AshScrollPane)tabbedPane.getComponentAt(index);
			}
			if(ea.isModified()) {
				String title = getTabTitle(ea);
				if(!title.endsWith("*"))
					setTabTitle(ea, title + "*");
			}
		} else {
			super.setTitle(text);
		}
	}
	/**
	 * 描画領域の取得：スクロール機能をもつ編集領域(editArea)を生成する。
	 */
	@Override protected Component createWorkArea() {
  		return createEditor();
  	}
	/**
	 * スクロール機能をもつ編集領域(editArea)を生成する。
	 */
	private AshScrollPane createEditor() {
		Log.debug(-55, "==> createEditor(): ");
		editArea = createEditArea();
  		createPopupMenu(editArea.getEditArea());
		scrollPane = new AshScrollPane(editArea);
		return scrollPane;
	}
	/**
	 * 描画領域の取得：複数ファイル編集機能をもつタブ付きペインを返す。
	 */
	protected Component createMultiWorkArea() {
		tabbedPane = new JTabbedPane();
		tabbedPane.addChangeListener(new TabChangeListener());
		addEditorTab("*scratch*");
		return tabbedPane;
	}
	private void addEditorTab(String title) {
		AshScrollPane sp = createEditor();
		tabbedPane.addTab(title, sp);
		tabbedPane.setSelectedComponent(sp);
	}
	private class TabChangeListener implements ChangeListener {
		public void stateChanged(ChangeEvent e) {
			scrollPane = (AshScrollPane)tabbedPane.getSelectedComponent();
			resetEditArea(scrollPane.editArea);
		}
	}
	private void resetEditArea(AshEditorArea ea) {
		setEditArea(ea);
		File file = ea.getCurrentFile();
		if(file != null) showFileName(file.getPath());
	}
	/**
	 * 指定されたファイルをオープンする（編集可能状態にする）。
	 * すでにオープンしている場合はそのタブを選択する。
	 */
	public void findAndOpenFile(File file) {
		AshScrollPane sp = findEditorTab(file);
		if(sp == null) {
			Log.debug(55, "open: " + file.getPath());
			addEditorTab(file.getName());
			openFileSub(file);
		} else {
			Log.debug(55, "already opened: " + file.getPath());
			resetEditArea(sp.editArea);
			tabbedPane.setSelectedComponent(sp); 
			if(!editArea.isModified() || confirmCancel(file)) {
				openFileSub(file);
				updateTab(editArea);
			}
		}
	}
	private AshScrollPane findEditorTab(File file) {
		int n = tabbedPane.getTabCount();
		for(int i = 0; i < n; i++) {
			AshScrollPane sp = (AshScrollPane)tabbedPane.getComponentAt(i);
			File f = sp.editArea.getCurrentFile();
			if(file.equals(f)) return sp;
		}
		return null;
	}

	/*
	 *  ステータスバーのカスタマイズ
	 */
	@Override protected String cursorPosDisplay(int row, int column) {
		int max = editArea.getLineCount();
		int ratio = (max == 0) ? 0 : (row*100 + max/2)/max;
		return ("--L" + row + "--C" + column + "--" + ratio + "%--");
	}

	/*
	 * ファイル関連コマンドの実装
	 */
	public void newFile() {			// ファイルを新規に作成する。
		if(isMultiEditor()) {
			addEditorTab(UNTITLED);
			editArea.newFile();
			showCursorPos(1, 0);
		} else if(confirmWriteBack(editArea)) {
			editArea.newFile();
			showUntitled();
			showCursorPos(1, 0);
		}
	}
	private void openFile() {		// ファイルを開く。
		if(isMultiEditor() || confirmWriteBack(editArea)) {
			JFileChooser fc = getFileChooser(editArea);
			int ans = fc.showOpenDialog(getContentPane());
			if(ans == JFileChooser.APPROVE_OPTION)
				openFile(fc.getSelectedFile());
		}
	}
	private boolean saveFile(AshEditorArea ea) {	// ファイルを上書き保存
		if(ea.getCurrentFile() == null) return saveAsFile(ea);
		if(ea.isEditable()) {
			ea.writeFile();
			updateTab(ea);
			return true;
		}
		showMessage("読み込み専用です。保存できません。");
		return false;
	}
	private boolean saveAsFile(AshEditorArea ea) {	// ファイルを名前を付けて保存
		JFileChooser fc = getFileChooser(ea);
		while(true) {
			int ans = fc.showSaveDialog(getContentPane());
			if(ans != JFileChooser.APPROVE_OPTION) return false;
			File file = fc.getSelectedFile();
			if(!file.exists() || canOverride(file)) {
				if(ea.writeFile(file)) {
					showFileName(file.getPath());
					updateTab(ea);
				}
				break;
			}
		}
		return true;
	}
	private void revertFile(AshEditorArea ea) {	// ファイルを編集前の状態に復帰
		if(ea.isModified()) {
			File file = ea.getCurrentFile();
			if(file != null && confirmCancel(file)) {
				openFileSub(file);
				updateTab(ea);
			}
		}
	}

	private boolean canOverride(File file) {
		int ans = showConfirm("下記ファイルは既に存在しています。" +
							  "上書きして良いですか?\n" +
							  findFileName(file), 
							  "上書き保存の確認");
		return (ans == JOptionPane.YES_OPTION);
	}
	private boolean confirmCancel(File file) {
		int ans = showConfirm("下記ファイルのバッファは変更されています。"+
							  "変更を取り消して良いですか?\n" +
							  findFileName(file),
							  "変更取消しの確認");
		return (ans == JOptionPane.YES_OPTION);
	}
	private String findFileName(File file) {
		return (file != null) ? FileUtil.getPath(file) : "****";
	}
	private void updateTab(AshEditorArea ea) {
		File file = ea.getCurrentFile();
		if(isMultiEditor())
			setTabTitle(ea, file.getName());
	}

	/**
	 * エディタフレームを終了し、リソースを解放する。
	 */
	@Override protected void disposeFrame() {
		if(confirmDispose()) {
			Log.debug(1, "==> AshEditorFrame#disposeFrame()");
			disposeEditorResource();
			dispose();
		}
	}
	@Override protected boolean confirmDispose() {
		if(!isMultiEditor()) return confirmWriteBack(editArea);
		int n = tabbedPane.getTabCount();
		for(int i = 0; i < n; i++) {
			AshScrollPane sp = (AshScrollPane)tabbedPane.getComponentAt(i);
			if(!confirmWriteBack(sp.editArea)) return false;
		}
		return true;
	}
	protected boolean confirmWriteBack(AshEditorArea ea) {
		if(ea.isModified()) {
			File file = ea.getCurrentFile();
			String fname = (file != null) ? findFileName(file) : getTabTitle(ea);
			int ans = showConfirm("下記のバッファは変更されています。" +
								  "変更を保存しますか?\n" + fname);
			switch(ans) {
			case JOptionPane.YES_OPTION:	return saveFile(ea);
			case JOptionPane.NO_OPTION:		return true;
			case JOptionPane.CANCEL_OPTION:	return false;
			}
		}
		return true;
	}
	protected void disposeEditorResource() {}

	/**
	 * フィルター関係
	 */
	private JFileChooser getFileChooser(AshEditorArea ea) {
		JFileChooser fc = new JFileChooser();
		fc.setCurrentDirectory(ea.getCurrentDirectory());
		File file = ea.getCurrentFile();
		if(file == null) file = new File(getTabTitle(ea));
		fc.setSelectedFile(file);
		if(fileFilter != null) fc.setFileFilter(fileFilter);
		return fc;
	}
	private AshFileFilter fileFilter = null;
	/**
	 * フィルターを設定する。
	 * フィルターの設定は、AshEditor クラスの拡張クラスが行う。
	 */
	public void setFileFilter(String description) {
		fileFilter = new AshFileFilter();
		fileFilter.description = description;
	}
	public void setFileFilter(String description, String[] suffixes) {
		setFileFilter(description);
		for(String suffix : suffixes) fileFilter.addSuffix(suffix);
	}
	public void addFileFilterSuffix(String suffix) {
		if(fileFilter == null) fileFilter = new AshFileFilter();
		fileFilter.addSuffix(suffix);
	}
	private static class AshFileFilter extends FileFilter {
		private List<String> suffixArray = new ArrayList<String>();
		String description = "Ash file";
		void addSuffix(String suffix) {
			suffixArray.add(suffix);
		}
		public boolean accept(File f) {
			if(suffixArray == null) return true;
			if(f.isDirectory()) return true;
			String name = f.getName();
			int index = name.lastIndexOf('.');
			String fileSuffix = (index < 0) ? "" : name.substring(index+1);
			for(String suffix : suffixArray)
				if(fileSuffix.equalsIgnoreCase(suffix)) return true;
			return false;
		}
		public String getDescription() { return description; }
	}

	public void openFile(String filename) {
		openFile(GUIAdapter.findFile(filename));
	}
	protected void closeFile() {
		if(isMultiEditor()) {
			if(tabbedPane.getSelectedIndex() > 0 && confirmWriteBack(editArea)) {
				tabbedPane.remove(scrollPane);
			}
		} else {
			disposeFrame();
		}
	}

	/*
	 * AshEditor から呼ばれるメソッド
	 */
	/**
	 * 指定されたファイルをオープンする（編集可能状態にする）。
	 * 複数ファイル対応ですでにオープンしている場合はそのタブを選択する。
	 */
	public void openFile(File file) {
		if(isMultiEditor()) {
			findAndOpenFile(file);
		} else {
			openFileSub(file);
		}
	}
	private void openFileSub(File file) {
		editArea.openFile(file);
		showCursorPos(1, 0);
		showFileName(file.getPath());
	}
	public void setTextData(String title, String text) {
		if(title == null) title = UNTITLED;
		if(isMultiEditor()) addEditorTab(title);	// ★CA,090903 BUGGY?
		Log.debug(-55, "==> setTextData: " + text);
		editArea.setTextData(text);
	}
	public void setFileName(String filename) {
		showFileName(filename);
		editArea.setFileName(filename);
		if(isMultiEditor())
			setTabTitle(editArea, filename);
	}
	public void setEditable(boolean editable) {
		editArea.setEditable(editable);
	}
	public void setEditFont(Font font) {
		editArea.setFont(font);
	}
	public void setRowColumn(int rows, int columns) {
		editArea.setRowColumn(rows, columns);
	}
	public void showLine(int lineNum) {
		editArea.showLine(lineNum - 1);
	}
}
