package com.ftinc.si.assist.test.gui;

import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.text.Caret;

import com.ftinc.si.assist.run.Messages;
import com.ftinc.si.assist.test.AssertRecord;
import com.ftinc.si.assist.test.TestCommandRecord;
import com.ftinc.si.assist.test.Tool;

public class AssertEditor extends JDialog {
	private JTextField idText;
	private JComboBox<String> typeCombo;
	private JTextArea regText;
	private JTextPane targetText;

	boolean is_OK = false;
	AssertRecord m_assert;
	TestCommandRecord m_cmd;

	private HashMap<String, Class<?>> m_cmap = null;

	public AssertEditor(JDialog owner, AssertRecord rec, TestCommandRecord cmd) {
		super(owner, true);

		m_assert = rec;
		m_cmd = cmd;

		setResizable(false);
		setTitle(Messages.getString("AssertEditor.0")); //$NON-NLS-1$
		getContentPane().setLayout(null);
		setBounds(owner.getX() + 100,owner.getY() + 100, 600, 500);

		JLabel lblId = new JLabel(Messages.getString("AssertEditor.1")); //$NON-NLS-1$
		lblId.setBounds(12, 10, 50, 13);
		getContentPane().add(lblId);

		idText = new JTextField();
		idText.setEnabled(false);
		idText.setEditable(false);
		idText.setBounds(74, 7, 200, 19);
		idText.setText(Integer.valueOf(rec.id).toString());
		getContentPane().add(idText);
		idText.setColumns(10);

		JLabel lblType = new JLabel(Messages.getString("AssertEditor.2")); //$NON-NLS-1$
		lblType.setBounds(324, 10, 50, 13);
		getContentPane().add(lblType);

		typeCombo = new JComboBox<String>();
		typeCombo.setBounds(382, 7, 200, 19);
		typeCombo.addItem("Exception"); //$NON-NLS-1$
		typeCombo.addItem("Inspect"); //$NON-NLS-1$
		typeCombo.setSelectedItem(rec.criteriaType);;

		getContentPane().add(typeCombo);

		JLabel lblCriteria = new JLabel(Messages.getString("AssertEditor.8")); //$NON-NLS-1$
		lblCriteria.setBounds(12, 33, 50, 13);
		getContentPane().add(lblCriteria);


		JLabel lblRegularex = new JLabel(Messages.getString("AssertEditor.11")); //$NON-NLS-1$
		lblRegularex.setBounds(12, 311, 72, 13);
		getContentPane().add(lblRegularex);

		final JButton btnOk = new JButton(Messages.getString("AssertEditor.13")); //$NON-NLS-1$
		//OKボタン
		btnOk.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				if (!btnOk.isEnabled()) {
					return;
				}

				//targetObjの構文の返却型チェック。
				if (isSyntaxOK()) {
					updating();
				} else {
					int option = JOptionPane.showConfirmDialog(null, Messages.getString("AssertEditor.19"), Messages.getString("AssertEditor.20"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); //$NON-NLS-1$ //$NON-NLS-2$
					if (option == JOptionPane.YES_OPTION){
						//内容に多少不備があっても更新する。
						//targetTextの最初にわざと「!」を入れておく。不備有の意味。
						targetText.setText("!" + targetText.getText());; //$NON-NLS-1$
						updating();
					}
				}
			}
		});
		btnOk.setBounds(168, 440, 91, 21);
		btnOk.setEnabled(!TestCaseEditor.unableMode);
		getContentPane().add(btnOk);

		JButton btnCancel = new JButton(Messages.getString("AssertEditor.14")); //$NON-NLS-1$
		btnCancel.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				setVisible(false);
			}
		});
		btnCancel.setBounds(291, 440, 91, 21);
		getContentPane().add(btnCancel);

		JScrollPane scrollPane_1 = new JScrollPane();
		scrollPane_1.setBounds(22, 334, 560, 96);
		getContentPane().add(scrollPane_1);

		regText = new JTextArea(m_assert.criteriaRex);
		regText.addMouseWheelListener(new MouseWheelListener() {
			public void mouseWheelMoved(MouseWheelEvent e) {
				//マウスホイールの操作により、フォントサイズを変える。
				Font t_font = Tool.resizeFont(e, regText.getFont());
				if (t_font != null) {
					regText.setFont(t_font);
				}

			}
		});
		regText.setFont(new Font("MS UI Gothic", Font.PLAIN, 14));
		regText.setEditable(!TestCaseEditor.unableMode);
		scrollPane_1.setViewportView(regText);

		JScrollPane scrollPane_2 = new JScrollPane();
		scrollPane_2.setBounds(25, 56, 557, 245);
		getContentPane().add(scrollPane_2);

		targetText = new JTextPane();
		targetText.addMouseWheelListener(new MouseWheelListener() {
			public void mouseWheelMoved(MouseWheelEvent e) {
				//マウスホイールの操作により、フォントサイズを変える。
				Font t_font = Tool.resizeFont(e, targetText.getFont());
				if (t_font != null) {
					targetText.setFont(t_font);
				}
			}
		});
		targetText.setFont(new Font("MS UI Gothic", Font.PLAIN, 14));
		scrollPane_2.setViewportView(targetText);
		targetText.setToolTipText(""); //$NON-NLS-1$
		targetText.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				//右クリックならポップアップを開く。Exceptionのときだけ。
				if(SwingUtilities.isRightMouseButton(e)){
					popupTargetField(e);
				}
			}
		});
		targetText.addKeyListener(new KeyAdapter() {
			@Override
			public void keyTyped(KeyEvent e) {
				if (e.getKeyChar() == '.') {
					//.入力した時、直前の文字列を切り取って、次の候補を出す。
					popupCandidate(e);
				}
			}

		});
		targetText.setEditable(!TestCaseEditor.unableMode);

		//先頭が「!」の場合、未完成の印。編集には邪魔なので抜く。
		if (m_assert.targetObj != null && m_assert.targetObj.startsWith("!")) { //$NON-NLS-1$
			targetText.setText(m_assert.targetObj.substring(1));
		} else {
			targetText.setText(m_assert.targetObj);
		}

		typeCombo.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent e) {
				if (!typeCombo.isEnabled()) {
					return;
				}

				String t_str = (String)typeCombo.getSelectedItem();
				if (t_str != null) {
					if (t_str.length() > 0) {
						targetText.setText(""); //$NON-NLS-1$

						if (t_str.equals("Exception")) { //$NON-NLS-1$
							targetText.setEnabled(false);
							regText.setText("");
						} else if (t_str.equals("Inspect")) { //$NON-NLS-1$
							targetText.setEnabled(true);
							regText.setText("[]");
						}
					}
				}
				m_assert.criteriaType = (String)typeCombo.getSelectedItem();
			}
		});
		typeCombo.setEnabled(!TestCaseEditor.unableMode);
		initTarget();
	}

	//共通処理、および、Objectの場合にinspectするターゲットを決める。
	private void initTarget() {

		//クラスのマップファイルm_cmapを作成する。
		if (m_cmap == null) {
			//入力支援の元ネタ
			try {
				m_cmap = new HashMap<String, Class<?>>();
				if (!m_cmd.returnType.matches("^[a-z]+$")) { //$NON-NLS-1$
					m_cmap.put("$_", Tool.forName(m_cmd.returnType)); //$NON-NLS-1$
				}
				m_cmap.put("$0", Tool.forName(m_cmd.className)); //$NON-NLS-1$

				for (int i = 0; i < m_cmd.numArgs(); i++) {
					String arg_cname = m_cmd.getArgClass(i);
					if (!arg_cname.endsWith("[]")) {	//配列はクラスではない。メソッドもない。 //$NON-NLS-1$
						m_cmap.put("$" + Integer.toString(i + 1), Tool.forName(arg_cname)); //$NON-NLS-1$
					}
				}
			} catch (ClassNotFoundException e) {
				Tool.alertMSG(null, Messages.getString("AssertEditor.31") + e.getMessage() + "."); //$NON-NLS-1$ //$NON-NLS-2$
				return;
			}
		}
	}

	//画面を閉じた後、更新するための処理:m_assertに値を反映して画面を閉じる。
	private void updating() {
		is_OK = true;

		DefaultComboBoxModel<String> t_model = (DefaultComboBoxModel<String>)(typeCombo.getModel());
		String i_name = (String)t_model.getSelectedItem();
		if (i_name != null) {
			m_assert.criteriaType = i_name;;
		}

		m_assert.targetObj = targetText.getText();
		m_assert.criteriaRex = regText.getText();
		m_assert.changed();

		setVisible(false);
	}


	//判定対象のターゲット候補をポップアップ表示して選択させる。
	private void popupTargetField(MouseEvent e) {
		ArrayList<String> t_list = new ArrayList<String>();

		//ターゲットはException。例外のクラス名を選べるようにする。
		if ("Exception".equals(typeCombo.getSelectedItem())) { //$NON-NLS-1$

			//TestCommandの対象メソッドが発生させる例外を取得し、リスト表示する。
			Class<?>[] arg_classes = Tool.getArgClasses(m_cmd);
			try {
				Method t_verb = Tool.forName(m_cmd.className).getDeclaredMethod(m_cmd.methodName, arg_classes);
				Class<?>[] exs = t_verb.getExceptionTypes();
				for (int k = 0; k < exs.length; k++) {
					t_list.add(exs[k].getName());
				}
			} catch (ClassNotFoundException|SecurityException|NoSuchMethodException e1) {
				Tool.alertMSG(null, e1.getMessage());
			}
		} else if ("Inspect".equals(typeCombo.getSelectedItem())) { //$NON-NLS-1$
			final Pattern t_pat = Pattern.compile("ASSERTING\\(\\s*([0-9]+)\\s*,");
			Matcher t_m = t_pat.matcher(targetText.getText());

			int n = 0;
			while (t_m.find()) {
				String num = t_m.group(1);
				Integer t_i = Integer.valueOf(num);
				if (t_i >= n) {
					n = t_i + 1;
				}
			}
			t_list.add("ASSERTING(" + Integer.toString(n) + ", );\n");
			t_list.add("XROOT();\n");
			t_list.add("XELM(\"//*[@id='']\")");
		}
		JPopupMenu t_pop = new JPopupMenu();
		for (int i = 0; i < t_list.size(); i++) {
			t_pop.add(new JMenuItem(new InsertAction(t_list.get(i), this)));
		}

		t_pop.show(e.getComponent(), e.getX(), e.getY());
	}


	//"."を受けて、
	//①現在の入力テキストを解析し、文字列とクラスのマップを作る。
	//②直前の文字列を解析し、クラスを特定する。必要な候補を表示する。$*.xxxまではフィールド、そこから先はpublicメソッド。
	//③選択された文字列をcaret位置に挿入する。
	private void popupCandidate(KeyEvent e) {
		if (!"Inspect".equals(m_assert.criteriaType)) { //$NON-NLS-1$
			return;
		}

		Caret tc = targetText.getCaret();
		Point op = targetText.getLocationOnScreen();
		Point pt = tc.getMagicCaretPosition();
		Point pos = new Point(op.x + pt.x, op.y + pt.y);

		//popup表示フォーカス委譲（タイマーで）
		final CodingAssistMenu _popup = new CodingAssistMenu(this, null, targetText.getText(), targetText.getCaretPosition(), (HashMap<String,Class<?>>)m_cmap, pos, null);

		//候補がある場合のみポップアップする。
		if (_popup.countCandidate() > 0) {
			//このスレッドが終わるまではフォーカスされないのでタイマーでやる。（表示後でないとフォーカスされないため）
			Thread t_thread = new Thread() {
				@Override
				public void run() {
					try {
						sleep(1000);
						SwingUtilities.getWindowAncestor(_popup).toFront();
						_popup.requestFocusInWindow();
					} catch (InterruptedException e) {
						Tool.logIfDebug(e, "@AssertEditor#popupCandidate"); //$NON-NLS-1$
					}
				}
			};
			t_thread.start();

			_popup.setVisible(true);

			EventQueue.invokeLater(new Runnable() {
				@Override
				public void run() {
					insertAtCaret(_popup.m_answer, _popup.m_target, _popup.m_catches);
				}
			});
		}
	}


	//caretの位置にテキストを差し込む。
	public void insertAtCaret(String str, String tgt, String ctch) {
		if (str != null) {
			int pos = targetText.getCaretPosition();
			String str_h = targetText.getText();
			String str_tail = str_h.substring(pos);

			str_h = str_h.substring(0, pos);

			if (ctch != null) {
				//例外がある場合。
				str_h = str_h.replaceFirst(Pattern.quote(tgt + ".")+ "$", Matcher.quoteReplacement("try{\n" + tgt + ".")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
				str_h += ctch + str_tail;
			} else {
				str_h += str + str_tail;
			}
			targetText.setText(str_h);
			targetText.setCaretPosition(pos + str.length());
		}
	}

	//AssertRecordの形式チェックと、返却値の構文チェック
	private boolean isSyntaxOK() {
		AssertRecord t_ast = (AssertRecord) m_assert._dup(false);
		t_ast.targetObj = targetText.getText();
		t_ast.criteriaRex = regText.getText();

		if (!t_ast.isCompleted()) {
			//AssertのRecordの形式チェックに失敗。正規表現とdecisionの数が合わないも含む。
			Tool.alertMSG(null, Messages.getString("AssertEditor.27")); //$NON-NLS-1$
			return false;
		}

		if ("Exception".equals(typeCombo.getSelectedItem())) { //$NON-NLS-1$
			if ("$null".equals(regText.getText())) { //$NON-NLS-1$
				//VarDictionar, CodeFairyの場合、これだけで完成。
				return true;
			} else if (!regText.getText().matches(".*[ExceptionROr]+$")) {
				//例外の型が指定されていない場合。
				return false;
			}
		}
		return true;
	}
	//popupのコマンド文字列を受けて、このメソッドに中継する。
	//中継だけなので、アクションはこのクラスのみ。
	class InsertAction extends AbstractAction {
		String m_string;
		AssertEditor m_master;

		InsertAction(String str, AssertEditor master) {
			putValue(Action.NAME, str);
			m_string = str;
			m_master = master;
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			m_master.insertAtCaret(m_string, null, null);
		}
	}

}
