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

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
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.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.FontUIResource;
import javax.swing.text.Caret;

import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.edge.EdgeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.ie.InternetExplorerOptions;

import com.ftinc.si.assist.run.Messages;
import com.ftinc.si.assist.run.VCentral;
import com.ftinc.si.assist.test.Fson;
import com.ftinc.si.assist.test.JSONRecord;
import com.ftinc.si.assist.test.Tool;

public class WebElementGetter extends JDialog {
	private JTextArea m_target;
	protected JComboBox<String> m_params; //正規表現式
	private JLabel m_lblParams;

	private PageActionEditor m_owner;
	private WebDriver m_driver = null;
	private String m_driverName = null;
	private PageAction m_curAction = null;

	private ArrayList<WebElement> m_elms;
	private ArrayList<String> m_xpaths;
	private String[] m_elms_style;
	private int m_focusedPos = -1;

	//initTagでセットされる。：initTagは最初。PageActionEditorから呼ばれる。
	protected Integer m_selectedRow = -1;

	private JTextField elmPosText;

	private JButton prevBtn;
	private JButton nextBtn;

	private static String s_elmByXpath = "function _elementAtXpath(path){return document.evaluate(path,document,null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue;}"; //$NON-NLS-1$

	//パラメータ欄入力の際に、置き換えたかどうか。
	private boolean paramReplaced;

	WebElementGetter(PageActionEditor owner) {
		super(owner, false);
		setBackground(Color.GRAY);
		setResizable(false);
		m_owner = owner;

		int x = 100;
		int y = 100;
		int h = 205;
		if (owner != null) {
			x = owner.getX() + owner.getWidth();
			y = owner.getY();
			h = 180;
		}
		setBounds(x, y, 590, h);
		if (owner != null) {
			//PageActionEditorから起動されたらタイトルバーを消す。邪魔なので
			setUndecorated(true);
		}

		setTitle(Messages.getString("WebElementGetter.0") + "  (Copyright:Toyoaki Furusawa, 2017-)"); //$NON-NLS-1$
		getContentPane().setLayout(null);

		addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				closing();
			}
		});


		final JComboBox<String> comboBrowsers = new JComboBox<String>();
		comboBrowsers.addItem("Chrome"); //$NON-NLS-1$
		comboBrowsers.addItem("IE"); //$NON-NLS-1$
		comboBrowsers.addItem("Firefox"); //$NON-NLS-1$
		comboBrowsers.addItem("Edge"); //$NON-NLS-1$

		comboBrowsers.setBounds(12, 10, 114, 19);
		getContentPane().add(comboBrowsers);

		final JButton openBrowser = new JButton(Messages.getString("WebElementGetter.5")); //$NON-NLS-1$
		openBrowser.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				if (m_driver == null) {
					int n = comboBrowsers.getSelectedIndex();
					if (n >= 0) {
						m_driverName = comboBrowsers.getItemAt(n).toString();
						m_driver = getDriver(m_driverName);

						//ブラウザを変更できないようにする。
						comboBrowsers.setEditable(false);
						openBrowser.setEnabled(false);

						//必要に応じてブラウザから読み込む
						if (m_selectedRow >= 0 && m_curAction.arg_map.get("url") == null) { //$NON-NLS-1$
							//urlが未設定の時、読み込む。たぶん、空のURL。
							initTags();
						} else {
							//この画面そのものがTable選択から始まるので、ありえない。
						}

						//元の画面を記憶する。
						PageAction.s_win_id = m_driver.getWindowHandle();
					}
				}
			}
		});
		openBrowser.setBounds(131, 9, 91, 21);
		getContentPane().add(openBrowser);

		m_lblParams = new JLabel(Messages.getString("WebElementGetter.6")); //$NON-NLS-1$
		m_lblParams.setBounds(12, 123, 67, 13);
		getContentPane().add(m_lblParams);

		JLabel lblElement = new JLabel(Messages.getString("WebElementGetter.7")); //$NON-NLS-1$
		lblElement.setBounds(12, 36, 67, 13);
		getContentPane().add(lblElement);

		m_params = new JComboBox<String>();
		m_params.addMouseWheelListener(new MouseWheelListener() {
			public void mouseWheelMoved(MouseWheelEvent e) {
				//マウスホイールの操作により、フォントサイズを変える。
				Font t_font = Tool.resizeFont(e, m_params.getFont());
				if (t_font != null) {
					m_params.setFont(t_font);
				}
			}
		});
		m_params.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if ("comboBoxEdited".equals(e.getActionCommand())) { //$NON-NLS-1$
					Object t_str = m_params.getSelectedItem();
					if (t_str != null && t_str.toString().length() > 0) {
						m_params.removeItem(t_str);
						m_params.addItem(t_str.toString());
					}
				}
			}
		});

		//キーイベントの割り込みはJComboboxの部品にリスナー登録する。
		//複合キー割り当て（chord:）の場合、文字列合成する。
		m_params.getEditor().getEditorComponent().addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent e) {
				Object t_str = m_params.getSelectedItem();
				paramReplaced = false;
				if (t_str != null && t_str.toString().startsWith(Messages.getString("WebElementGetter.4"))) { //$NON-NLS-1$
					//Actionによっては特殊なキー、複合打鍵があるので、その場合、置き換える。
					StringBuilder sb = new StringBuilder(t_str.toString());

					if (m_curAction.modifyByKeyInput(e, sb)) {
						//Key入力により、自動的に入力結果を作成する場合。
						m_params.setSelectedItem(sb);
						paramReplaced = true;
						e.consume();
					}
				}
			}
			@Override
			public void keyReleased(KeyEvent e) {
				if (paramReplaced) {
					e.consume();
				}
			}
			@Override
			public void keyTyped(KeyEvent e) {
				if (paramReplaced) {
					e.consume();
				}
			}
		});

		m_params.setBounds(91, 120, 487, 19);
		getContentPane().add(m_params);

		JButton btnSet = new JButton(Messages.getString("WebElementGetter.8")); //$NON-NLS-1$
		btnSet.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				//元のテーブルにセットする
				if (m_selectedRow >= 0) {
					if (m_target.getText().length() > 0) {
						String str = makeActionsJson();
						if (str.length() == 0) {
							Tool.alertMSG(null, Messages.getString("WebElementGetter.2")); //$NON-NLS-1$
						} else {
							String t_reg = Messages.getString("WebElementGetter.16"); //$NON-NLS-1$
							if (m_params.isEditable()) {
								t_reg = m_params.getSelectedItem().toString();
							}
							if (m_owner != null) {
								m_owner.setArgumentAt(m_selectedRow, str, t_reg); //$NON-NLS-1$
							}
						}
					} else {
						Tool.alertMSG(null, Messages.getString("WebElementGetter.1")); //$NON-NLS-1$
					}
				}
			}
		});
		btnSet.setBounds(253, 149, 91, 21);
		getContentPane().add(btnSet);
		if (m_owner == null) {
			btnSet.setEnabled(false);
		}

		JLabel lblMove = new JLabel(Messages.getString("WebElementGetter.9")); //$NON-NLS-1$
		lblMove.setBounds(253, 13, 91, 13);
		getContentPane().add(lblMove);

		elmPosText = new JTextField();
		prevBtn = new JButton(Messages.getString("WebElementGetter.10")); //$NON-NLS-1$
		nextBtn = new JButton(Messages.getString("WebElementGetter.11")); //$NON-NLS-1$

		elmPosText.addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent e) {
				//数字を入力し、リターンで要素の位置を決めたとみなす。
				int t_key = e.getKeyCode();
				if (t_key == KeyEvent.VK_ENTER){
					if (!m_curAction.hasPath()) {
						//要素選択とは無関係。path情報は不要なアクションである。
						return;
					}

					String t_str = VCentral.pickNumber(elmPosText.getText());
					if (t_str != null && t_str.length() > 0) { //$NON-NLS-1$
						Integer n = Integer.valueOf(t_str);
						if (n < m_elms.size()) {
							int prev_focus = m_focusedPos;

							//ブラウザの選択状態を変える
							moveFocus(prev_focus, n);
						}
					} else {
						elmPosText.setText(Integer.toString(m_elms.size() - 1));
					}
				}
			}
		});
		elmPosText.setHorizontalAlignment(SwingConstants.CENTER);
		elmPosText.setText(Integer.toString(m_focusedPos));
		elmPosText.setBounds(425, 10, 74, 19);
		getContentPane().add(elmPosText);
		elmPosText.setColumns(10);

		prevBtn.setBounds(340, 9, 83, 21);
		getContentPane().add(prevBtn);

		prevBtn.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				if (m_selectedRow >= 0) {
					if (!m_curAction.hasPath()) {
						//要素選択とは無関係。path情報は不要なアクションである。
						return;
					}
					if (m_focusedPos >= 0) {
						m_focusedPos--;

						moveFocus(m_focusedPos + 1, m_focusedPos);
					}
				}
			}
		});

		nextBtn.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				if (!m_curAction.hasPath()) {
					//要素選択とは無関係。path情報は不要なアクションである。
					return;
				}
				if (m_xpaths.size() > m_focusedPos) {
					m_focusedPos++;

					moveFocus(m_focusedPos - 1, m_focusedPos);
				}
			}
		});
		nextBtn.setBounds(504, 9, 74, 21);
		getContentPane().add(nextBtn);

		//走査ボタン
		JButton btnReadagain = new JButton(Messages.getString("WebElementGetter.22")); //$NON-NLS-1$
		btnReadagain.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				//フォーカスを戻す
				moveFocus(m_focusedPos, - 1);

				//もう一度、ブラウザから読み込む。
				initTags();
				Object t_arg = m_params.getSelectedItem();
				if (m_xpaths.size() == 0 && t_arg != null && t_arg.toString().length() > 0) {
					//m_regExprsに値が入っていたためNGだったかも知れないので警告。
					Tool.alertMSG(null, Messages.getString("WebElementGetter.12") + m_params.getSelectedItem().toString() + "."); //$NON-NLS-1$ //$NON-NLS-2$
				}
			}
		});
		btnReadagain.setBounds(131, 149, 111, 21);
		getContentPane().add(btnReadagain);

		JButton btnPreview = new JButton(Messages.getString("WebElementGetter.23")); //$NON-NLS-1$
		btnPreview.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				//操作のプレビュー
				//ツールチップのリセット。
				m_target.setToolTipText(""); //$NON-NLS-1$

				PageAction t_act = (PageAction)Tool.getObjectfromJSON(null, m_owner.makeJsonAt(m_selectedRow, makeActionsJson()));
				t_act.arg_map.put(Messages.getString("WebElementGetter.17"), Messages.getString("WebElementGetter.18")); //待ち時間を個別に設定。あまり待たない。 //$NON-NLS-1$ //$NON-NLS-2$
				try {
					ArrayList<String> res = t_act.doAction(m_driver);
					if (t_act.assertable() && res != null && res.size() > 0) {
						//Tooltipにしたのは、必ず見たい訳でもないため。
						m_target.setToolTipText("RESULT = " + res.get(0)); //$NON-NLS-1$
					}
				} catch (Exception e1) {
					String t_msg;
					t_msg = Messages.getString("WebElementGetter.13") + t_act.getClass().getSimpleName() + "\n"; //$NON-NLS-1$ //$NON-NLS-2$
					if (e1.getCause() != null) {
						t_msg += e1.getCause().getClass().getName() + " = " + e1.getCause().getMessage(); //$NON-NLS-1$
					} else {
						t_msg += e1.getClass().getName() + " = " + e1.getMessage(); //$NON-NLS-1$
					}
					t_msg = t_msg.replaceAll("_cssByXPath", Matcher.quoteReplacement("$x")); //$NON-NLS-1$ //$NON-NLS-2$
					Tool.alertMSG(null, t_msg);
				}
			}
		});
		btnPreview.setBounds(356, 149, 91, 21);
		getContentPane().add(btnPreview);
		if (m_owner == null) {
			btnPreview.setEnabled(false);
		}

		JScrollPane scrollPane = new JScrollPane();
		scrollPane.setBounds(90, 36, 488, 74);
		getContentPane().add(scrollPane);

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

		m_params.setEditable(true);
		m_target.setEditable(true);
		m_target.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				if(SwingUtilities.isRightMouseButton(e)){
					//右クリックならポップアップを開く。
					goPopup(e);
				}
			}
		});

		//入力支援用
		m_target.addKeyListener(new KeyAdapter() {
			public void keyTyped(KeyEvent e) {
				if (!openBrowser.isEnabled()) {
					//WebDriverがある時にだけ、入力支援が可能。
					if (e.getKeyChar() == '=' || e.getKeyChar() == '@') {
						//入力支援可能なActionであり、かつ=か@が入力されたとき。
						popupCandidate(e);
					}
				}
			}
		});

		scrollPane.setViewportView(m_target);
		m_target.setColumns(10);

		//m_targetのtooltipのフォントを標準より大きくする。
		Font t_f = m_target.getFont();
		FontUIResource t_fres = new FontUIResource(t_f);
		UIManager.getLookAndFeelDefaults().put(Messages.getString("WebElementGetter.24"), t_fres); //$NON-NLS-1$
	}


	private void moveFocus(int prev, int next) {
		//フォーカスを変化させるかどうかの判定：変化がなければ何もしない。
		if (prev != next && m_driver != null) {
			//ボタンの初期状態を決める
			if (m_elms.size() == 1) {
				//移動する必要なし。
				nextBtn.setEnabled(false);
				prevBtn.setEnabled(false);
			} else {
				nextBtn.setEnabled(true);
				prevBtn.setEnabled(true);
			}

			if (m_elms.size() == 0) {
				//WindowはWebElementではないので。Windowを切り替える。
				moveWindowFocus(next);
			} else {
				//要素間でフォーカスを移動する
				moveElementFocus(prev, next);
			}
		} else  {
			//リセットする。
			moveElementFocus(prev, -1);
			elmPosText.setText(""); //$NON-NLS-1$
			nextBtn.setEnabled(false);
			prevBtn.setEnabled(false);
		}
	}

	//要素のフォーカスをprevからnextに移す
	//このメソッドだけ特別扱い。改善の余地あり。★★
	private void moveWindowFocus(int next) {
		if (m_xpaths == null) {
			return;
		}

		if (next == 0) {
			prevBtn.setEnabled(false);
		} else if (next < 0) {
			//領域外のため抜ける
			m_focusedPos = -1;
			prevBtn.setEnabled(false);
			return;
		}
		if (m_xpaths.size() == next + 1) {
			nextBtn.setEnabled(false);
		} else if (next >= m_xpaths.size()) {
			//領域外のため抜ける
			m_focusedPos = m_xpaths.size();
			nextBtn.setEnabled(false);
			return;
		}
		m_focusedPos = next;
		m_driver.switchTo().window(m_xpaths.get(m_focusedPos));

		//位置の反映
		elmPosText.setText(Integer.toString(m_focusedPos + 1) + " / " + Integer.toString(m_xpaths.size())); //$NON-NLS-1$

		//テキスト表示更新
		m_target.setText("\"title\":\"" + Fson.escape(m_driver.getTitle()) + "\""); //$NON-NLS-1$ //$NON-NLS-2$

		if (!m_curAction.assertable()) {
			//WindowIDをm_curAxctionに書き込む。
			m_params.setSelectedItem(m_xpaths.get(m_focusedPos));
		}

		//ボタンの有効無効制御
		if (m_focusedPos < m_xpaths.size() - 1) {
			nextBtn.setEnabled(true);
		}
		if (m_focusedPos > 0) {
			prevBtn.setEnabled(true);
		}
	}

	//要素のフォーカスをprevからnextに移す
	private void moveElementFocus(int prev, int next) {
		JavascriptExecutor jsexe = (JavascriptExecutor)m_driver;

		if (next == 0) {
			prevBtn.setEnabled(false);
		} else if (next < 0) {
			//領域外のため抜ける
			m_focusedPos = -1;
			if (prev >= 0) {
				//現在のフォーカス色を元に戻す
				setElementStyle(jsexe, prev, m_elms_style[prev]);
			}
			prevBtn.setEnabled(false);
			return;
		}
		if (m_elms.size() == next + 1) {
			nextBtn.setEnabled(false);
		} else if (next >= m_elms.size()) {
			//領域外のため抜ける
			m_focusedPos = m_elms.size();
			nextBtn.setEnabled(false);
			if (prev < m_elms.size()) {
				//現在のフォーカス色を元に戻す
				setElementStyle(jsexe, prev, m_elms_style[prev]);
			}

			return;
		}
		m_focusedPos = next;

		//位置の反映
		elmPosText.setText(Integer.toString(next + 1) + " / " + Integer.toString(m_elms.size())); //$NON-NLS-1$

		WebElement t_elm2 = m_elms.get(next);

		//フォーカス位置の情報をm_targetもしくはm_regExprsに反映する。
		setCurrentStringtoTarget(t_elm2);


		//現状の色の保存  ※arguments[0]にWebElementを使用する方式を試みたが、ブラウザ依存大のため以下に変更。
		String t_src0 = "var _elm=_elementAtXpath(\"" + m_xpaths.get(next) + "\");if(_elm==null)return;"; //$NON-NLS-1$ //$NON-NLS-2$
		String t_src1 = "return _elm.style.border;"; //$NON-NLS-1$ //$NON-NLS-2$
		String t_src2 = "return _elm.style.backgroundColor;"; //$NON-NLS-1$ //$NON-NLS-2$

		//その要素を画面の下端に持ってくる。
		String t_src3 = "_elementAtXpath(\"" + m_xpaths.get(next) + "\").scrollIntoView(false);"; //$NON-NLS-1$ //$NON-NLS-2$
		try {
			String t_style1 = (String)jsexe.executeScript(s_elmByXpath + t_src0 + t_src1);
			String t_style2 = (String)jsexe.executeScript(s_elmByXpath + t_src0 + t_src2);
			if (!(t_style1 != null && t_style1.length() > 0)) {
				//何もないときは透明
				t_style1 = "none"; //$NON-NLS-1$
			}
			if (!(t_style2 != null && t_style2.length() > 0)) {
				//何もないときは透明
				t_style2 = "transparent"; //$NON-NLS-1$
			}
			m_elms_style[next] = t_style1 + "\t" + t_style2; //$NON-NLS-1$

			//フォーカスを解除する要素の背景色の現状復帰
			if (prev >= 0 && prev < m_elms_style.length) {
				setElementStyle(jsexe, prev, m_elms_style[prev]);
			}

			//フォーカスを移した要素の背景色を赤にする。
			setElementStyle(jsexe, next, "thick solid red\tred"); //$NON-NLS-1$

			//その要素を画面の下端に持ってくる。
			jsexe.executeScript(s_elmByXpath + t_src3);
		} catch (Exception e) {
			String t_msg;
			if (e.getCause() != null) {
				t_msg = e.getCause().getClass().getName() + " = " + e.getCause().getMessage(); //$NON-NLS-1$
			} else {
				t_msg = e.getClass().getName() + " = " + e.getMessage(); //$NON-NLS-1$
			}
			if (e.getMessage() != null && e.getMessage().startsWith("_elementAtXpath")) { //$NON-NLS-1$
				t_msg = Messages.getString("WebElementGetter.15");  //$NON-NLS-1$
			}
			Tool.alertMSG(null, "Exception>> " + t_msg); //$NON-NLS-1$
		}
	}

	//n番目の要素の色を指定色に変える
	private void setElementStyle(JavascriptExecutor jsexe, int n, String style) {
		if (style == null) return;

		String[] t_style = style.split("\t"); //$NON-NLS-1$

		//書き換えられない場合に備えてreadonlyを外す。
		String t_src0 = "var _elm=_elementAtXpath(\"" + m_xpaths.get(n) + "\");if(_elm==null)return;_elm.removeAttribute('readonly');"; //$NON-NLS-1$ //$NON-NLS-2$
		String t_src1 = "_elm.style.border=\""; //$NON-NLS-1$ //$NON-NLS-2$

		//枠線の色を変える
		jsexe.executeScript(s_elmByXpath + t_src0 + t_src1 + t_style[0] + "\";"); //$NON-NLS-1$
		//背景色を変える
		String t_src2 = "_elm.style.backgroundColor=\""; //$NON-NLS-1$ //$NON-NLS-2$
		jsexe.executeScript(s_elmByXpath + t_src0 + t_src2 + t_style[1] + "\";"); //$NON-NLS-1$
	}

	//ブラウザの要素が移った時、m_targetやm_regExprsに必要な文字列を移す。
	private void setCurrentStringtoTarget(WebElement elm) {
		if (m_curAction.hasPath()) {
			//現在、target欄にある文字列。
			String t_target = m_target.getText();

			//置き換え対象文字列がある時、現在のxpathと置き換える。
			String next_str = m_xpaths.get(m_focusedPos);//現在フォーカスしているxpath

			final Pattern t_pat1 = Pattern.compile("([,\\(\\s]xpath=\")([^\"]*)\""); //$NON-NLS-1$
			Matcher t_m = t_pat1.matcher(t_target);
			if (t_m.find()) {
				//ラムダ式の中のxpath
				String tstr1 = t_m.group(1);
				t_target = t_m.replaceFirst(Matcher.quoteReplacement(tstr1 + next_str + "\"")); //$NON-NLS-1$
				m_target.setText(t_target);
			} else {
				final Pattern t_pat2 = Pattern.compile("\"xpath\":\"([^\"]*)\""); //$NON-NLS-1$
				t_m = t_pat2.matcher(t_target);
				if (t_m.find()) {
					//Jsonの中のxpathを置き変える。
					t_target = t_m.replaceFirst(Matcher.quoteReplacement("\"xpath\":\"" + next_str + "\"")); //$NON-NLS-1$ //$NON-NLS-2$
					m_target.setText(t_target);
				} else {
					//まだJsonになっていないのでJson化する。
					//要素の指定方法(xpath)をJSON形式でm_targetに書き込む。
					String t_path = getFocussingPath(elm);  //Json形式で焦点を移したPathでのm_targetを表示する。
					m_target.setText(t_path);
				}
			}
		}
	}

	//アクション作成支援のために、ブラウザが指定された時、対応するドライバを取得する。
	private WebDriver getDriver(String bname) {
		WebDriver drv = null;

		try {
			//単独
			if (bname.equals("Firefox")) { //$NON-NLS-1$
				drv = new FirefoxDriver();
			} else if (bname.equals("IE")) { //$NON-NLS-1$
				InternetExplorerOptions ieCap = new InternetExplorerOptions();
				ieCap.setCapability(InternetExplorerDriver.INTRODUCE_FLAKINESS_BY_IGNORING_SECURITY_DOMAINS, true);
				drv = new InternetExplorerDriver(ieCap);
			} else if (bname.equals("Chrome")) { //$NON-NLS-1$
				drv = new ChromeDriver();
			} else if (bname.equals("Edge")) { //$NON-NLS-1$
				drv = new EdgeDriver();
			}
		} catch (Throwable e) {
			Tool.alertMSG(null, e.getMessage());
			if (drv != null) {
				drv.quit();
			}
			return null;
		}

		return drv;
	}

	//PageActionEditorでダブルクリック時に呼ばれる。
	//初期化のため、第二列の内容を必要に応じて、m_target,m_regExprsに転記する。
	protected void initParams(int n, PageAction act, String assertStr) {
		//PageActionEditorでアクションがリセットされたのでフォーカスを外す。
		moveFocus(m_focusedPos, -1);

		//ツールチップのリセット。
		m_target.setToolTipText(""); //$NON-NLS-1$

		m_selectedRow = n;
		//現在のアクションを保持する。
		m_curAction = act;

		//引数欄をセットする。
		m_params.removeAllItems();//二重登録を防ぐため


		//判定するActionだけ有効化する。
		ArrayList<String> t_candidates = act.getLabelAndParams();
		if (t_candidates != null && t_candidates.size() > 0) {
			m_params.setEditable(true);

			m_lblParams.setText(t_candidates.get(0));
			for (int i = 1; i < t_candidates.size(); i++) {
				m_params.removeItem(t_candidates.get(i));//重複を避ける。
				m_params.addItem(t_candidates.get(i));
			}
		} else {
			m_lblParams.setText(""); //$NON-NLS-1$
			m_params.setEditable(false);
		}

		//正規表現を表示する
		if (assertStr != null && assertStr.length() > 0) {
			String t_val = (String)act.arg_map.get(Messages.getString("WebElementGetter.25")); //$NON-NLS-1$
			m_params.removeItem(t_val);
			m_params.addItem(t_val);
			m_params.setSelectedItem(t_val);
		}

		//actの内容を出力する。
		String t_target = act.getTarget();
		m_target.setText(t_target);

		//初期化
		 m_focusedPos = -1;
	}

	//PageActionEditorの第二列に書き込む内容を合成する。
	private String makeActionsJson() {
		String t_param = Messages.getString("WebElementGetter.26"); //$NON-NLS-1$
		if (m_params.isEditable()) {
			t_param = m_params.getSelectedItem().toString();
		}
		String t_result = "";
		String t_json = m_target.getText();
		final Pattern t_pat = Pattern.compile("^([^\"]+)\":\"([^\"]+)");
		Matcher t_m = t_pat.matcher(t_json);
		while (t_m.find()) {
			if (t_result.length() > 0) {
				t_result += ",";
				t_result += "\"" + t_m.group(1) + "\":\"" + Fson.escape(t_m.group(2)) + "\"";
			}
		}
		return m_curAction.getJsonElement(t_result, t_param);
	}


	//現在フォーカスしている要素のxpathを返す。
	private String getFocussingPath(WebElement elm) {
		String t_json = "";
		m_curAction.arg_map.put("xpath", m_xpaths.get(m_focusedPos)); //$NON-NLS-1$
		for (Entry<String, Object> entry: m_curAction.arg_map.entrySet()) {
			if (t_json.length() > 0) {
				t_json += ",";
			}
			t_json += "\"" + entry.getKey() + "\":\"" + entry.getValue() + "\"";
		}
		return t_json;
	}


	//XPath検索用の手がかりを得る
	private String getKeyWordForPath() {
		String t_str = m_target.getText();
		if (t_str.length() > 0) {
			//xpathを持つもしくはラムダ式の場合
			final Pattern t_pat = Pattern.compile("\\(.*xpath=\"([^\"]+)\".*\\)\\s*\\->"); //$NON-NLS-1$
			Matcher t_m = t_pat.matcher(t_str);
			if (t_m.find()) {
				//XPATHを含むラムダ式からxpath部分を切り出す。
				String t_path = t_m.group(1);
				return t_path;
			} else {
				final Pattern t_pat2 = Pattern.compile("^\\s*(\\(|/).*$");
				t_m = t_pat2.matcher(t_str);
				if (t_m.find()) { //$NON-NLS-1$
					//xpathの新規または再検索用。m_targetがタグ名（JSONではない）を指定しているなら検索用と判定する。
					return t_str;
				} else {
					Pattern t_pat3 = Pattern.compile("^\\s*\"xpath\":\"([^\"]+)\".*$"); //$NON-NLS-1$
					t_m = t_pat3.matcher(t_str);
					if (t_m.find()) { //$NON-NLS-1$
						//xpathの新規または再検索用。m_targetがタグ名（JSONではない）を指定しているなら検索用と判定する。
						return t_m.group(1);
					}
				}
			}
		}
		return null;
	}


	//caretのある文字列を返却する。本ツールで生成するxpathは"を使わない。'を使う。
	private String currentPointedWord() {
		String xstr = m_target.getText();
		int pos = m_target.getCaretPosition();
		String part1_str = xstr.substring(0, pos);//posより前の文字列。置き換え対象。

		final Pattern t_pat1 = Pattern.compile("^[\\s\\S]+\"([^\"]*)$");//macthしなければ行頭からの文字列全部が返る。 //$NON-NLS-1$
		Matcher t_m = t_pat1.matcher(part1_str);
		if (t_m.find()) {
			String t_key = t_m.group(1);
			return t_key;
		}
		return part1_str;
	}

	//初期設定。現在選択中(rowで指定)のアクションに関係するタグやURLを、開いているブラウザから取得する。
	//返却値がfalseなら、WebElementGetterは開かない。
	protected boolean initTags() {
		ArrayList<WebElement> el_list = new ArrayList<WebElement>();//検索したWebElementを格納する。
		ArrayList<String> xp_list = new ArrayList<String>();//検索した要素のXPathを格納する。

		//前後ボタンは一旦無効化。後で要素数を見て有効化する。
		nextBtn.setEnabled(false);
		prevBtn.setEnabled(false);

		//m_targetなどで検索キーワードが指定されていたら、まとめて配列にする。
		String xpath = getKeyWordForPath();

		//返却値：このアクションがWebElementGetterを使用するかどうか。
		//WebDriverが存在するなら、指定の要素を検索して、ArrayListに格納する。
		boolean t_result = false;
		try {
			if (m_curAction== null) {
				//単独でWebElementGetterを開いた場合、無色のPageActionをあてがう。
				m_curAction = new PageAction();
				m_selectedRow = 0;
			}
			t_result = m_curAction.getFocussingElements(m_driver, el_list, xp_list, xpath);
		} catch (Throwable e) {
			Tool.alertMSG(null, e.getMessage());
		}

		if (!t_result) {
			return false;//WebElementGetterを使用しない。
		}

		if (xp_list.size() > 0) {
			m_elms = el_list;
			m_xpaths = xp_list;
			m_elms_style = new String[el_list.size()];
		} else {
			m_elms = null;
			m_xpaths = null;
			m_elms_style =null;
		}

		if (m_curAction.hasPath()) {
			if (m_xpaths != null) {
				//フォーカス位置の初期化
				moveFocus(m_focusedPos, m_xpaths.size() - 1);
			} else {
				//フォーカスを戻す
				//この時点で、m_xpaths=null
				moveFocus(m_focusedPos, - 1);
			}
		} else  {
			//位置を持たない操作：LoadURLなど
			if (m_xpaths != null) {
				//Alertが見つかった場合。
				elmPosText.setText(Integer.toString(m_xpaths.size())); //$NON-NLS-1$
			} else {
				elmPosText.setText(""); //$NON-NLS-1$
			}
			m_target.setText(m_curAction.getTarget());
		}
		return true;//使用する意思表明
	}


	//終了処理
	protected void closing() {
		if (m_driver != null) {
			m_driver.quit();
			m_driver = null;
		}
	}

	//"@="を受けて、
	//①現在の入力テキストを解析し、文字列とクラスのマップを作る。
	//②直前の文字列を解析し、クラスを特定する。必要な候補を表示する。$*.xxxまではフィールド、そこから先はpublicメソッド。
	//③選択された文字列をcaret位置に挿入する。
	private void popupCandidate(KeyEvent e) {
		//カレット位置の計算
		Caret tc = m_target.getCaret();
		Point op = m_target.getLocationOnScreen();
		Point pt = tc.getMagicCaretPosition();
		Point pos = new Point(op.x + pt.x, op.y + pt.y);
		String t_src = currentPointedWord();
		//popup表示フォーカス委譲（タイマーで）
		final PageAssistMenu _popup = new PageAssistMenu(this, m_driver, e.getKeyChar(), t_src, pos);

		//候補がある場合のみポップアップする。
		if (_popup.countCandidate() > 0) {
			_popup.setVisible(true);

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


	//caretの位置にテキストを差し込む。
	public void insertAtCaret(String str_answer) {
		if (str_answer != null) {
			int pos = m_target.getCaretPosition();

			String t_result = m_target.getText();
			String part1_str = t_result.substring(0, pos);//posより前の文字列。置き換え対象。
			String part2_str = t_result.substring(pos);//pos後の文字列。変換後にくっつけるため。
			int len = part1_str.length();

			if (str_answer.matches("^.*\t.+")) { //$NON-NLS-1$
				//複数の属性を持つので置き換える。@以降を置き換える必要がある。
				String t_attr = str_answer.replaceFirst("^.+\t.+:(.+)$", "$1");//属性名の取り出し //$NON-NLS-1$ //$NON-NLS-2$
				str_answer = str_answer.replaceFirst("^(.+)\t.+$", "$1");//属性値の取り出し //$NON-NLS-1$ //$NON-NLS-2$

				if (!str_answer.contains(":")) { //$NON-NLS-1$
					//style属性の場合、両端の空白は不要。
					str_answer = " " + str_answer + " "; //$NON-NLS-1$ //$NON-NLS-2$
				}
				String match_str = Matcher.quoteReplacement("contains(concat(' ',normalize-space(@" + t_attr + "),' '), '" + str_answer + "')]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				part1_str = part1_str.replaceFirst("^([\\s\\S]+)@" + t_attr + "=$", "$1" + match_str); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				len = part1_str.length() - str_answer.length();
				t_result = part1_str  + part2_str; //$NON-NLS-1$ //$NON-NLS-2$
			} else {
				//一つしかない場合は、属性名が付随していない。要するに複数属性値を持たないので単純な追加。
				if (part1_str.endsWith("=")) { //$NON-NLS-1$
					t_result = part1_str + "'" + str_answer + "']" + part2_str; //$NON-NLS-1$ //$NON-NLS-2$
					len += 3;
				} else {
					//@の後
					t_result = part1_str + str_answer + part2_str; //$NON-NLS-1$ //$NON-NLS-2$
				}
			}

			m_target.setText(t_result);
			m_target.setCaretPosition(len + str_answer.length());
		}
	}


	////////////////////////////////////////////////////
	//ポップアップメニューを開く
	private void goPopup(MouseEvent e) {
		JPopupMenu t_pop = m_curAction.getPopup(this, m_target.getText());
		if (t_pop != null) {
			t_pop.show(e.getComponent(), e.getX(), e.getY());
		}
	}


	//ポップアップで選択したテンプレートを入れる。
	public void setTargetFromPopup(String str) {
		if (str != null) {
			//テンプレートが与えられたらm_targetに埋める。
			m_target.setText(str);
		}
	}

	//JSONを登録するエディタを開く。
	public void registerTargetFromPopup(String cname, String templateString) {
		String str = JOptionPane.showInputDialog(this, Messages.getString("WebElementGetter.3"), "js_");  //$NON-NLS-1$ //$NON-NLS-2$
		if (str != null) {
			if (str.matches("^[\\s\\S]*[\\(\\)][\\s\\S]*$")) { //$NON-NLS-1$
				Tool.alertMSG(null, Messages.getString("WebElementGetter.14")); //$NON-NLS-1$
				return;
			}

			JSONRecord t_j = JSONRecord.getJSONRec(cname, str); //$NON-NLS-1$
			if (t_j == null) {
				//insert
				t_j = new JSONRecord(2);
				t_j.name = str;
				t_j.className = cname;
				t_j.content = templateString;
				t_j.fields = ""; //$NON-NLS-1$
			} else {
				//update
				t_j.content = templateString;
				t_j.fields = Messages.getString("WebElementGetter.27"); //$NON-NLS-1$
				t_j.changed();
			}

			if (t_j != null) {
				int t_res = JOptionPane.showConfirmDialog(this, Messages.getString("WebElementGetter.19") + str + Messages.getString("WebElementGetter.20")); //$NON-NLS-1$ //$NON-NLS-2$
				if(t_res == JOptionPane.YES_OPTION) {
					try {
						//ここでDB更新
						Tool._db().updateRecordBySQL(t_j.getUpdateSQL(), true);
						JSONRecord.updatingJSONRec(t_j);

						JOptionPane.showMessageDialog(null, Messages.getString("WebElementGetter.21")); //$NON-NLS-1$
					} catch (SQLException e) {
						Tool.alertMSG(null, Messages.getString("JSONEditor.9") + e.getMessage()); //$NON-NLS-1$
					}
				}
			}

		}
	}


}
