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

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map.Entry;

import org.openqa.selenium.NotFoundException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

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

public class MacroActions extends PageAction {
	//JSONテンプレートを元に、操作を再生するマクロである。★★key4actionsを指定すると、操作を再生できる。★★
	//※VBAから、画面テスト仕様から、TestCommandRecord（画面ID#モード）と同時にJSONTemplate（事前事後、入力、submit、チェック処理）も格納される。
	//※PageActionEditorで編集はできない。
	//※可能な限り、定業用Excelに構造を合わせた。別の方法として、ケースごとにActionを生成し組をつくることもできるが、作りとしては、本実装の方が容易。

	//このマクロで実行するアクションを決めるキー：画面ID＋モード \t パラメータ組名 \ ボタン。poslistにキーがあれば、その位置のPageActionを実行する。。
	//※マクロのJsonTemplateを取得するためのキーは、このクラス名、および、画面ID＋モード。
	private String key4actions = "\t\t"; //$NON-NLS-1$

	//defaultコンソトラクタ。Template用。
	public MacroActions() {
		//"panelLayer"は設定しない。設定してJsonTemplateに保存すると、実行時のdoActionで上書きされるため。
	}

	//最初に呼ばれるMacroActions。最上位。TestCaseから直接生成される。Templateではない。
	public MacroActions(String key, MacroActions template, String xoptions) {
		key4actions = key;
		arg_map.put("panelLayer", ExecLayer.TARGET); //$NON-NLS-1$
		if (xoptions != null && xoptions.length() > 0) {
			options = xoptions;

			if (xoptions.contains("_submit") && template.checkpoints_map != null) { //$NON-NLS-1$
				//submit型の場合に、historyは、DBに登録時に検索する。登録時に必要なアラームを上げるためである。

				ArrayList<String> ng_list = new ArrayList<String>();//堂々巡りの防止用
				ng_list.add(getKey4MacroName());

				//checkpointsを辿る。
				template.key4actions = key;
				template.options = xoptions;

				ArrayList<String> chk_list = template.checkpoints_map.get(getKey4Input());
				if (chk_list != null && chk_list.size() > 0) {
					ArrayList<ArrayList<String>> mlist = template.getHistory(template.checkpoints_map.get(getKey4Input()), ng_list);

					//ルートに成功したら、最初のルートを採用する。
					if (mlist != null && mlist.size() > 0) {
						history = mlist.get(0);
					} else {
						Tool.alertMSG(null, key + Messages.getString("MacroActions.1") + Tool.getJSONfromObject(chk_list)); //$NON-NLS-1$
					}
				}
			}
		}
	}

	//マクロ取得用のキーを返す。画面ID＋モード
	protected String getKey4MacroName() {
		String[] t_strs = key4actions.split("\t", -1); //$NON-NLS-1$
		return t_strs[0];
	}

	//*_poslistやmile_stones用のキーを返す。:パラメータ組名
	private String getKey4Input() {
		String[] t_strs = key4actions.split("\t", -1); //$NON-NLS-1$
		return t_strs[1];
	}

	//submit_action_map用のキーを返す。:パラメータ組名 \t ボタン名
	private String getKey4Submit() {
		String[] t_strs = key4actions.split("\t", -1); //$NON-NLS-1$
		return t_strs[1] + "\t" + t_strs[2]; //$NON-NLS-1$
	}

	//チェック対象の画面か、その前段かポップアップか。要するにチェックするかどうか。
	enum ExecLayer {
		TARGET,
		NOT_TARGET,
	}

	//キー：このアクションのパラメータ組名		値：キーに対応するListは、パラメータ組名で指定された経由状態(画面ID＋モード \t パラメータ組名 \t ボタン名)のリスト。
	//※経由状態にパラメータ組を含むのは、特定の値の設定をセッション情報として持っている場合があるため。必須ではない。
	protected HashMap<String, ArrayList<String>> checkpoints_map;

	//この画面を含む遷移ルート。TestCommandの中のMacroActionとして作っておく。
	protected ArrayList<String> history = null;

	//この画面の遷移元リスト：画面ID＋モードのリスト。
	//※リスト先頭要素がデフォルト遷移元。袋小路や、ここから出て行って戻るパターンは含まない。
	private ArrayList<String> trans_from = null;

	//この画面の遷移先マップ：「パラメータ組 \t ボタン」をキーに、遷移先画面ID＋モードを得る。
	private HashMap<String, String> trans_to;

	//キー：パラメータ組名	値：キーに対応するListは順番のリスト。input_actionsのコントロール操作の位置を指定している。
	private HashMap<String, ArrayList<Integer>> input_poslist = null;

	//キー：パラメータ組名	値：キーに対応するListは順番のリスト。input_actionsの中のOuterSetの位置を指定している。
	//OuterSetを分離するのは、history操作の場合に除外するため。
	private HashMap<String, ArrayList<Integer>> outerset_poslist = null;

	//パラメータの入力。この画面でテストするすべてのパターンが記載されている。
	private ArrayList<PageAction> input_actions;

	//パラメータ組名 \t ボタン名をキーに、ボタン押下と遷移先のチェックアクションを得る。
	private HashMap<String, ArrayList<PageAction>> submit_action_map;

	//統合したactionリスト。
	private ArrayList<PageAction> united_actions = null;//Jsonによる生成（単純チェック）、もしくはキーと*_actionsから生成（遷移型）。

	//WebTestCreatorから呼ばれる。
	protected void setInputActions(ArrayList<PageAction> actions) {
		input_actions = actions;
	}

	//WebTestCreatorから呼ばれる。
	protected void setSubmitMap(HashMap<String, ArrayList<PageAction>> map) {
		submit_action_map = map;
	}

	//WebTestCreatorから呼ばれる。
	protected void setUnitedActions(ArrayList<PageAction> actions) {
		united_actions = actions;
	}

	//例外時に箇所を特定するための手がかり。
	private HashMap<PageAction, String> _infos = null;

	//errメッセージの保持
	private String err_msg = ""; //$NON-NLS-1$
	public String getErrMsg() {
		return err_msg;
	}


	//必要なものだけが、初期化する。
	public void initAction() {
		//マクロIDと個別パラメータにより、再生すべきMacroActionsを構成する。
		//最初からJsonで与えられている場合は、単純チェックである。
		try {
			pushed(key4actions);//無限ループ防止用。

			if (united_actions == null) {
				err_msg = "getting json template @ initAction"; //$NON-NLS-1$
				JSONRecord t_jdata = JSONRecord.getJSONRec(this.getClass().getName(), getKey4MacroName() + options);
				if (t_jdata != null) {
					String bk_key4actions = key4actions;
					String bk_options = options;

					Tool.logIfDebug(null, "MacroActions: key4action=" + key4actions + "  type=" + options); //$NON-NLS-1$ //$NON-NLS-2$
					Fson.updateFromJson(this, t_jdata.content);

					//JsonTemplateで上書きされたのを回復する。
					key4actions = bk_key4actions;
					options = bk_options;

					united_actions = getActions();
					initSharedLink();//action間の共有リンク（$KEYなど）
				}
			} else {
				initSharedLink();//action間の共有リンク
			}
			ExecLayer t_layer = (ExecLayer)getArg("panelLayer"); //$NON-NLS-1$
			if (t_layer == null) {
				t_layer = ExecLayer.NOT_TARGET;
			}

			//パラメータ名をarg_mapに登録する。
			arg_map.put("$PARAMETER_NAME", getKey4Input()); //$NON-NLS-1$

			//最上位のマクロ以外はassertableを除去する。および初期化する。
			ArrayList<PageAction> t_list = new ArrayList<PageAction>();
			for (int i = 0; i < united_actions.size(); i++) {
				PageAction t_act = united_actions.get(i);
				t_act.initAction();

				if (!t_act.assertable() || t_layer.equals(ExecLayer.TARGET)) {
					t_list.add(t_act);
				}
			}
			united_actions = t_list;
			popped();
		} catch (Exception e) {
			throw new NotFoundException(e.getMessage() + "\n" + err_msg); //$NON-NLS-1$
		}
		if (united_actions == null || united_actions.size() == 0) {
			throw new NotFoundException(Messages.getString("MacroActions.0") + getKey4MacroName() + options + Messages.getString("MacroActions.9")); //$NON-NLS-1$ //$NON-NLS-2$
		}
	}

	//通常のPageActionは全自動。
	public boolean isAutomatic() {
		for (int i = 0; i < united_actions.size(); i++) {
			if (!united_actions.get(i).isAutomatic()) {
				return false;
			}
		}
		return true;
	}


	//画面の準備ができるまで待つ。Actionごとに待ち方が異なる。
	public ArrayList<String> doAction(WebDriver drv) {
		//統合したアクションを実行する。
		if (united_actions != null && united_actions.size() > 0) {
			PagePlayer t_play = new PagePlayer();
			t_play.snapmode = false; //入れ子のplayerではスナップショットは取らない。
			t_play.actions = united_actions;

			try {
				return t_play.play(null);
			} catch (Exception e) {
				PageAction t_act = t_play.cur_action; //実行中のアクション
				String t_msg = e.getMessage();
				if (getArg("panelLayer").equals(ExecLayer.TARGET) ) { //$NON-NLS-1$
					if (key4actions != null) {
						t_msg += "\t\t" + key4actions.replaceAll("\t", " "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					}
				} else {
					t_msg += "\t\tunited actions size=" + Integer.toString(united_actions.size()); //$NON-NLS-1$
				}
				if (_infos != null && _infos.containsKey(t_act)) {
					t_msg += " err_info=" + _infos.get(t_act).toString(); //$NON-NLS-1$
				}
				if (arg_map.containsKey("itemID")) { //$NON-NLS-1$
					t_msg += " itemID=" + arg_map.get("itemID").toString(); //$NON-NLS-1$ //$NON-NLS-2$
				}
				t_msg += " class=" + t_act.getClass().getSimpleName(); //$NON-NLS-1$
				if (e instanceof SnapshotException) {
					//snapshotのエラーの場合、さらなるスナップショットを防ぐため、Exceptionクラスを変えない。
					throw new SnapshotException(t_msg + " [\n " + Tool.makeIndent(e) + "]"); //$NON-NLS-1$ //$NON-NLS-2$
				}
				throw new NotFoundException(t_msg + " [\n " + Tool.makeIndent(e) + "]"); //$NON-NLS-1$ //$NON-NLS-2$
			}
		}
		return null;
	}

	//path情報を持つアクションかどうか。falseならgetTargetはpath以外のものを返すべきなので上書きすること。
	protected boolean hasPath() {
		return false;
	}


	//チェックが設定されているかどうかを返す。doActionの後に呼ばれる。
	protected boolean assertable() {
		//delegationにより、targetかどうかがわかる。
		ExecLayer t_layer = (ExecLayer)getArg("panelLayer"); //$NON-NLS-1$
		if (t_layer != null && t_layer.equals(ExecLayer.TARGET)) {
			//直下もしくは、配下。Historyは除く。
			if (united_actions != null && united_actions.size() > 0) {
				//アクションだけでなく確認アクションもある。
				for (int i = 0; i < united_actions.size(); i++) {
					err_msg = Integer.toString(i) + "th action @ assertable"; //$NON-NLS-1$
					PageAction t_act = united_actions.get(i);
					if (t_act.assertable()) {
						return true;
					}
				}
			}
		}
		//targetのMacroActionでなければAssertしない。
		return false;
	}


	//正規表現があれば返す。doActionの後に呼ばれることが前提。
	protected ArrayList<String> getRegs() {
		ArrayList<String> t_regs = new ArrayList<String>();
		if (assertable()) {
			ExecLayer t_layer = (ExecLayer)getArg("panelLayer"); //$NON-NLS-1$
			if (t_layer != null && t_layer.equals(ExecLayer.TARGET)) {
				//このMacroActionsがターゲット画面の場合のみ正規表現を有効化する。
				//※historyはデフォルトコンストラクタで生成するので、ExecLayer.NOT_TARGET

				if (united_actions != null && united_actions.size() > 0) {
					//Check型の場合、united_actionsに直接入れる。
					//アクションだけでなく確認アクションもある。
					for (int i = 0; i < united_actions.size(); i++) {
						err_msg = Integer.toString(i) + "th action @ getReg"; //$NON-NLS-1$
						if (united_actions.get(i).assertable()) {
							t_regs.addAll(united_actions.get(i).getRegs());
						}
					}
				}
				return t_regs;
			}
		}
		return null;
	}


	//情報の共有関係をセットする。主に$KEYのため。
	//※_infosは関係ないが、ここで作るのが都合がよいので入れている。
	private void initSharedLink() {
		//united_actionsのdelegation設定をこのオブジクトにする。
		if (_infos == null) {
			//例外時の情報を格納するリストを作成する。
			_infos = new HashMap<PageAction, String>();
		}
		if (united_actions != null) {
			for (int i = 0; i < united_actions.size(); i ++) {
				err_msg = Integer.toString(i) + "th action @ initSharedLink"; //$NON-NLS-1$

				PageAction t_act = united_actions.get(i);
				if (t_act != null) {
					//情報共有リンクの設定
					t_act.setReferTo(this);
					if (key4actions == null) {
						//最初からunited_actionsが定められているので_infoは未設定。
						if (_infos != null) {
							_infos.put(t_act, "position>>" + Integer.toString(i)); //$NON-NLS-1$
						}
					}
				}
			}
		}
	}

	//配下のPageActionに参照リンクを伝える。
	protected void setReferTo(PageAction act) {
		refer_to = act;
		if (united_actions != null) {
			for (int i = 0; i < united_actions.size(); i ++) {
				err_msg = Integer.toString(i) + "th action @ initSharedLink"; //$NON-NLS-1$

				PageAction t_act = united_actions.get(i);

				//情報共有リンクの設定
				t_act.setReferTo(this);
			}
		}
	}

	//PageActionは、4段階で順を追って実行する。①前段階、②入力、③submit、④check。⑤事後。
	private ArrayList<PageAction> getActions() {
		ExecLayer t_layer = (ExecLayer)getArg("panelLayer");//マクロ階層のLayer情報。デフォルトではnull。 //$NON-NLS-1$

		ArrayList<PageAction> t_acts = new ArrayList<PageAction>();
		if (key4actions != null && key4actions.length() > 0) {
			boolean b_loaded = false;

			if (t_layer != null && t_layer.equals(ExecLayer.TARGET)) {
				//第一階層なので事前処理を登録する。

				//例外時の情報を格納するリストを作成する。
				_infos = new HashMap<PageAction, String>();

				//OuterSetの中から、必要な設定アクションを抜き出しセットする。
				if (outerset_poslist != null && outerset_poslist.size() > 0) {
					//Keyと「パラメータ組名」が一致する。
					ArrayList<Integer> i_list = outerset_poslist.get(getKey4Input());
					if (i_list != null) {
						for (int i = 0; i < i_list.size(); i++) {
							PageAction t_p =input_actions.get(i_list.get(i));
							err_msg = Integer.toString(i) + "th action of outerset @ getActions"; //$NON-NLS-1$
							t_acts.add(t_p);

							if (_infos != null) {
								_infos.put(t_p, "outerset_action position>>" + i_list.get(i).toString()); //$NON-NLS-1$
							}
						}
					}
				}

				//ここに至るまでの遷移を実行する。
				//historyを取得し、それまでのアクションを全部作る。SUBMIT型以外にhistoryは作られないことに留意。
				//それらのアクションには NOT_TARGETをっ指定する。
				if (history != null && history.size() > 0) {
					ArrayList<String> t_list = history;//遷移ルートを実行する。
					for (int i = 0; i < t_list.size(); i++) {
						err_msg = Integer.toString(i) + "th action of history @ getActions"; //$NON-NLS-1$

						//最上層ではないので判定はしない。
						MacroActions t_macro = new MacroActions(); //ExecLayer.NOT_TARGET
						String[] temp = t_list.get(i).split("\t"); //temp[0]が画面名。t_list.get(i)は前画面のkey4action //$NON-NLS-1$
						JSONRecord t_jdata = JSONRecord.getJSONRec(this.getClass().getName(), temp[0] + options);
						if (t_jdata != null) {
							Fson.updateFromJson(t_macro, t_jdata.content);

							//key4actionsを上書きする。
							t_macro.key4actions = t_list.get(i);
							t_macro.options = options;

							//念のため、最上層ではないと否定しておく。
							t_macro.arg_map.put("panelLayer", ExecLayer.NOT_TARGET); //$NON-NLS-1$
							if (i == 0) {
								if (t_macro.arg_map.get("url") != null) { //$NON-NLS-1$
									//初画面でアクションがない場合、少なくともURLはJsonに直接セットされているだろう。
									LoadURL t_url = new LoadURL();
									t_url.arg_map.put("url", t_macro.arg_map.get("url")); //$NON-NLS-1$ //$NON-NLS-2$
									t_acts.add(t_url);
									if (_infos != null) {
										_infos.put(t_url, "history position>>" + Integer.toString(i)); //$NON-NLS-1$
									}
									b_loaded = true;
								} else {
									//警告を発する。画面を開けない。
									Tool.alertMSG(null, t_macro.key4actions + Messages.getString("MacroActions.19")); //$NON-NLS-1$
								}
							}

							t_macro.united_actions = t_macro.getActions();
							t_acts.add(t_macro);
							if (_infos != null) {
								_infos.put(t_macro, "history position>>" + Integer.toString(i)); //$NON-NLS-1$
							}
						} else {
							//警告を発する。遷移元として設定されているのに、DBに未登録の場合。
							Tool.alertMSG(null, temp[0] + Messages.getString("MacroActions.20")); //$NON-NLS-1$
						}
					}
				}
				//LoadURLは最上層で処理する。
				if (!b_loaded) {
					//遷移実行がない場合かつ初画面でアクションがない場合、このアクションには、少なくともURLはセットされているだろう。
					if (arg_map.containsKey("url")) { //$NON-NLS-1$
						LoadURL t_url = new LoadURL();
						t_url.arg_map.put("url", arg_map.get("url")); //$NON-NLS-1$ //$NON-NLS-2$
						t_acts.add(t_url);
						if (_infos != null) {
							_infos.put(t_url, "url>>" + arg_map.get("url")); //$NON-NLS-1$ //$NON-NLS-2$
						}
					}
				}
			}

			//WindowやFrameにフォーカスを改めて与える。
			if (arg_map.get("title") != null) { //$NON-NLS-1$
				CheckFor t_chk = new CheckFor();
				t_chk.options = "ToWindow"; //$NON-NLS-1$
				t_chk.arg_map.put("title", arg_map.get("title").toString()); //$NON-NLS-1$ //$NON-NLS-2$
				t_acts.add(t_chk);
				if (_infos != null) {
					_infos.put(t_chk, "title>>" + arg_map.get("title").toString()); //$NON-NLS-1$ //$NON-NLS-2$
				}
			}

			//ToFrameは、設定されていれば指定のフレームにフォーカスを移す。指定されていなければ、（先の画面が特定フレーム内の場合があるので）TOPに移す。
			ToFrame t_fr = new ToFrame();
			if (arg_map.get("frame") != null) { //$NON-NLS-1$
				err_msg = "toframe @ getActions"; //$NON-NLS-1$
				String str_id = arg_map.get("frame").toString();
				if (str_id.startsWith("src=")) {
					str_id = str_id.substring(4);
					t_fr.arg_map.put("src", str_id); //$NON-NLS-1$
				} else if (str_id.startsWith("title=")) {
					str_id = str_id.substring(6);
					t_fr.arg_map.put("title", str_id); //$NON-NLS-1$
				} else if (str_id.startsWith("xpath=")) {
					str_id = str_id.substring(6);
					t_fr.arg_map.put("xpath", str_id); //$NON-NLS-1$
				} else if (str_id.startsWith("name=")) {
					str_id = str_id.substring(5);
					t_fr.arg_map.put("name", str_id); //$NON-NLS-1$
				}
			}
			t_acts.add(t_fr);
			if (_infos != null) {
				_infos.put(t_fr, "frame_xpath>>" + arg_map.get("frame_xpath")); //$NON-NLS-1$ //$NON-NLS-2$
			}

			//入力操作リストからパラメータ組名で指定した操作の組を抽出しセットする。
			if (input_poslist != null && input_poslist.size() > 0) {
				//Keyと「パラメータ組名」が一致する。
				ArrayList<Integer> i_list = input_poslist.get(getKey4Input());
				if (i_list != null) {
					for (int i = 0; i < i_list.size(); i++) {
						err_msg = Integer.toString(i) + "th action of inputposlist @ getActions"; //$NON-NLS-1$

						PageAction t_p =input_actions.get(i_list.get(i));
						t_acts.add(t_p);

						if (_infos != null) {
							_infos.put(t_p, "input_action position>>" + i_list.get(i).toString()); //$NON-NLS-1$
						}
					}
				}
			}
			if (submit_action_map != null) {
				//キーと「パラメータ組名 \t ボタン名」が一致する遷移操作と確認事項を抽出し登録する。
				ArrayList<PageAction> t_actions = submit_action_map.get(getKey4Submit());
				if (t_actions != null) {
					for (int i = 0; i < t_actions.size(); i ++) {
						err_msg = Integer.toString(i) + "th action of submitmap @ getActions"; //$NON-NLS-1$

						//最上層ならすべてアクションを実行する。
						//historyの画面であれば、ボタン押下だけ実行する。
						if (t_layer.equals(ExecLayer.TARGET) || i == 0) {
							t_acts.add(t_actions.get(i));
							if (_infos != null) {
								_infos.put(t_actions.get(i), "submit_actions position>>" + Integer.toString(i)); //$NON-NLS-1$
							}
						}
					}
				}
			}

			if (t_layer != null && t_layer.equals(ExecLayer.TARGET)) {
				//ブラウザをリセットする。
				ResetBrowser t_reset = new ResetBrowser();
				t_acts.add(t_reset);
			}
		} else {
			//key4actionsが指定されていない時。Historyの中の画面でもない。Targetでもない。単なる操作の一コマ。
			//素の場合、united_actionsが呼ばれるので、ここには来ない。
		}
		if (t_layer != null && t_layer.equals(ExecLayer.TARGET)) {
			//デバッグのためのlog処理
			if (Tool.debug_mode) {
				String t_data = Tool.getJSONfromObject(t_acts, "(arg_map|united)"); //$NON-NLS-1$
				Tool.logIfDebug(null, t_data);
			}
		}
		return t_acts;
	}

	//WebElementGetterではMacroActionは処理できないので、エディタで開くためにfalseを返す。
	protected boolean getFocussingElements(WebDriver drv, ArrayList<WebElement> el_list, ArrayList<String> xp_list, String xpath) {
		return false;
	}


	////////////高速化の試み///////////////////////////
	private static HashMap<String, MacroActions> s_macros = new HashMap<String, MacroActions>();
	private MacroActions getMacroActions(String name) {
		if (s_macros.containsKey(name)) {
			return s_macros.get(name);
		}
		//更新するためのJsonを取得(optionsはつけているが、実体はsubmit用)
		JSONRecord t_jdata = JSONRecord.getJSONRec(this.getClass().getName(), name);
		if (t_jdata != null) {
			MacroActions result_panel = new MacroActions(); //ExecLayer.NOT_TARGET

			//Jsonにより属性を更新
			Fson.updateFromJson(result_panel, t_jdata.content);
			s_macros.put(name, result_panel);
			return result_panel;
		}
		return null;
	}

	/////////////Status表示////////////////////////////////////
	private static ArrayList<String> s_stack = new ArrayList<String>();
	private static String pushed(String str) throws EndlessException {
		String key = str;
		if ("\t\t".equals(key)) {
			//名無しのMacroActionsの場合、とりあえず深さを登録する。
			key = Integer.toString(s_stack.size());
		}

		if (s_stack.contains(str)) {
			throw new EndlessException(str);
		}

		s_stack.add(str);

		String t_msg = ""; //$NON-NLS-1$
		for (int i = 0; i < s_stack.size(); i++) {
			if (t_msg.length() > 0) {
				t_msg += " > "; //$NON-NLS-1$
			}
			t_msg += s_stack.get(i);
		}
		return t_msg;
	}

	private static void popped() {
		//最後を取る。
		s_stack.remove(s_stack.size() - 1);
	}

	/////////////重複メッセージを出力しない////////////////////////////////////
	//getHistoryは繰り返し同じメッセージを出力するので、重複はカットする。
	private static ArrayList<String> s_msglist = new ArrayList<String>();
	private static void sendLog(String msg) {
		if (s_msglist.contains(msg)) {
			Tool.logIfDebug(null, msg);
			s_msglist.add(msg);
		}
	}


	//////////////////////////////////////////////////////////////////////////////
	//この画面から遡るルートを探る。
	//返却値は、この画面を起点としたルートのリスト。注意！thisをリストに含めてはならない。
	//checkpointsの要素：画面ID#モード \t パラメータ組名 \t ボタン名     ※パラメータ組名、ボタン名は省略可能。
	//※　セッション情報を同じ値で更新するケースが複数の組み合わせにまたがる場合がある。
	//※　このため、画面ID#モード　と　ボタン名の組が同じでも、パラメータ組名が異なれば、別要素として扱う。
	protected ArrayList<ArrayList<String>> getHistory(ArrayList<String> checkpoints, ArrayList<String> ng_list) {
		if (!"_submit".equals(options)) { //$NON-NLS-1$
			//submit型でない場合には、遷移を含めたテストは対象外なので、nullを返す。
			return null;
		}

		//返却値：検索結果としてありうるルートをリストにして返す。
		ArrayList<ArrayList<String>> t_list = new ArrayList<ArrayList<String>>();

		if (checkpoints != null) {
			String t_msg = null;
			try {
				t_msg = pushed(getKey4MacroName());
				WebTestCreator.setStatus(Messages.getString("MacroActions.41") + t_msg); //$NON-NLS-1$
			} catch (EndlessException e) {
				//無限ループに入ったので、これ以上深入りしない。
				return null;
			}
			if (trans_from == null || trans_from.size() == 0) {
				//うまく行った時の準備
				ArrayList<String> t_result = new ArrayList<String>();
				t_list.add(t_result);

				if (arg_map.containsKey("url") && checkpoints.size() == 0) { //$NON-NLS-1$
					popped();
					//ルートは途絶えたが、全てのcheckpointは通過済みで、prev_panelにurlが指定されている場合。
					//その先がないが、動作するという意味で、答えに空を設定してルートを返す。
					return t_list;
				} else if (arg_map.containsKey("url") && checkpoints.size() == 1) { //$NON-NLS-1$
					String[] k4as = key4actions.split("\t", -1);//比較用 //$NON-NLS-1$

					//panel, paramName, buttunName
					String[] checkpoint_info = checkpoints.get(checkpoints.size() - 1).split("\t", -1);//最後のチェックポイントを確認する。 //$NON-NLS-1$
					if (checkpoint_info[0].length() > 0 && checkpoint_info[0].equals(k4as[0])) {
						//画面がチェックポイントと一致する。
						if (checkpoint_info[1].length() > 0 && checkpoint_info[1].equals(k4as[1])) {
							//パラメータ組名が指定されていて一致。
							if (checkpoint_info[2].length() > 0) {
								if (checkpoint_info[2].equals(k4as[2])) {
									popped();

									//ボタンがも指定されている。
									//パラメータ組名、ボタン名も一致する。
									return t_list;
								}
							} else {
								popped();

								//パラメータ組名が一致
								//checkpointsから、最後の要素を除く。
								return t_list;
							}
						} else if (checkpoint_info[1].length() == 0 && checkpoint_info[2].length() > 0 && checkpoint_info[2].equals(k4as[2])) {
							popped();

							//パラメータ組名は指定がなく、ボタンの指定があり。一致。
							//checkpointsから、最後の要素を除く。
							return t_list;
						} else if (checkpoint_info[1].length() == 0 && checkpoint_info[2].length() == 0) {
							popped();

							//パラメータ組名、ボタンが指定されていないので、画面だけで判断する。結果、チェックポイントである。
							//checkpointsから、最後の要素を除く。
							return t_list;
						}
					}
				}
				if (!arg_map.containsKey("url")) { //$NON-NLS-1$
					sendLog(Messages.getString("MacroActions.45") + getKey4MacroName() + Messages.getString("MacroActions.46")); //$NON-NLS-1$ //$NON-NLS-2$
				}

				popped();

				//trans_fromsがnullの場合、終点である。
				//checkpointsが残っていたら、ルートではなかったということである。
				//histroryに自分は含まれない。
				return null;
			} else {
				//遡る。
				//遷移元画面＋モードのリストを調べる。
				String cur_panel = getKey4MacroName();
				//panel, paramName, buttunName
				String[] checkpoint_info = null;
				if (checkpoints.size() > 0) {
					checkpoint_info = checkpoints.get(checkpoints.size() - 1).split("\t", -1);//最後のチェックポイントを確認する。 //$NON-NLS-1$
					if (trans_from.contains(checkpoint_info[0])) {
						//checkpointsの最後がtrans_fromに含まれていたら、それが解になる可能性が最も高いので順序を変える。
						trans_from.remove(checkpoint_info[0]);
						trans_from.add(0, checkpoint_info[0]);
					}
				}
				if (trans_from.contains(getKey4MacroName())) {
					//念のため。
					trans_from.remove(getKey4MacroName());
				}

				for (int i = 0; i < trans_from.size(); i++) {
					//遷移先ではない遷移元のみ考慮する。（要するにポップアップや、行って戻るルートは除外）
					if (!ng_list.contains(trans_from.get(i))) {
					//　checkpointsの末尾に合致する、もしくは、nglistにない場合、調べる。
						//堂々巡りではない。
						MacroActions prev_panel = getMacroActions(trans_from.get(i) + options); //ExecLayer.NOT_TARGET

						//更新するためのJsonを取得(optionsはつけているが、実体はsubmit用)
						if (prev_panel != null) {
							//遷移型ではtrans_toがnullなのはあり得ない。
							if (prev_panel.trans_to == null || prev_panel.trans_to.size() == 0) {
								sendLog(Messages.getString("MacroActions.47") + prev_panel.getKey4MacroName() + Messages.getString("MacroActions.48")); //$NON-NLS-1$ //$NON-NLS-2$
							} else {
								//元の画面に遷移するパターンがあるか？登録漏れを検出するため。
								boolean b_hit = false;

								//変更するためにチェックポイントのコピーを用意する。
								ArrayList<String> t_checkpoints = new ArrayList<String>();
								if (checkpoint_info != null && checkpoints.size() > 0) {
									t_checkpoints.addAll(checkpoints);
								}

								for (Entry<String, String>entry: prev_panel.trans_to.entrySet()) {
									if (entry.getValue().equals(cur_panel)) {
										b_hit = true;
										//遷移先が、この画面と一致するなら調べる。entry.getKey()=パラメータ組 \t ボタン
										//trans_from.get(i): 前画面ID#モード
										//key4actions:画面ID＋モード \t パラメータ組名 \ ボタン
										prev_panel.key4actions = trans_from.get(i) + "\t" + entry.getKey(); //$NON-NLS-1$

										String[] k4as = prev_panel.key4actions.split("\t", -1);//比較用 //$NON-NLS-1$

										if (t_checkpoints.size() > 0) {
											if (checkpoint_info[0].length() > 0 && checkpoint_info[0].equals(k4as[0])) {
												//前画面がチェックポイントと一致する。
												if (checkpoint_info[1].length() > 0 && checkpoint_info[1].equals(k4as[1])) {
													//パラメータ組名が指定されていて一致。
													if (checkpoint_info[2].length() > 0) {
														//ボタンも指定されている。
														if (checkpoint_info[2].equals(k4as[2])) {
															//パラメータ組名、ボタン名も一致する。
															//checkpointsから、最後の要素を除く。
															t_checkpoints.remove(t_checkpoints.size() - 1);
															break;
														}
													} else {
														//パラメータ組名が一致
														//checkpointsから、最後の要素を除く。
														t_checkpoints.remove(t_checkpoints.size() - 1);
														break;
													}
												} else if (checkpoint_info[1].length() == 0 && checkpoint_info[2].length() > 0 && checkpoint_info[2].equals(k4as[2])) {
													//パラメータ組名は指定がなく、ボタンの指定があり。一致。
													//checkpointsから、最後の要素を除く。
													t_checkpoints.remove(t_checkpoints.size() - 1);
													break;
												} else if (checkpoint_info[1].length() == 0 && checkpoint_info[2].length() == 0) {
													//パラメータ組名、ボタンが指定されていないので、画面だけで判断する。結果、チェックポイントである。
													//checkpointsから、最後の要素を除く。
													t_checkpoints.remove(t_checkpoints.size() - 1);
													break;
												}
											} else {
												//チェックポイントではないので何もしない。
											}
										}
									}
								}
								//ng_listの作成
								ArrayList<String> next_ngs = new ArrayList<String>();
								next_ngs.addAll(ng_list);
								next_ngs.add(prev_panel.getKey4MacroName());

								//ルート探索
								ArrayList<ArrayList<String>> p_list = prev_panel.getHistory(t_checkpoints, next_ngs);
								if (p_list != null) {
									//ルートが見つかった!
									for (int j = 0; j < p_list.size(); j++) {
										//答えに自分を追加してルートが完成する。
										ArrayList<String> t_result = new ArrayList<String>();
										t_result.addAll(p_list.get(j));
										t_result.add(prev_panel.key4actions);
										t_list.add(t_result);

										//お掃除
										p_list.clear();
									}
									if (checkpoints.size() == 0) {
										//checkpointsは全て消化しているので見つかった解が最適に決まっている。
										t_checkpoints.clear();
										t_checkpoints = null;
										p_list = null;

										popped();
										return t_list;
									}
								} else if (t_checkpoints.size() > 0) {
									//辿るべきチェックポイントがまだあるが、解(前の画面)がない。
									//どこで途切れたかを残す。
									if (err_msg.length() > 0) {
										err_msg += ","; //$NON-NLS-1$
									}
									t_msg = prev_panel.getErrMsg();
									if (t_msg.length() == 0) {
										t_msg = prev_panel.getKey4MacroName();
									}
								} else {
									sendLog(Messages.getString("MacroActions.51") + prev_panel.getKey4MacroName() + Messages.getString("MacroActions.52")); //$NON-NLS-1$ //$NON-NLS-2$
								}
								if (!b_hit) {
									//登録漏れ。
									err_msg = cur_panel;
									sendLog(Messages.getString("MacroActions.53") + trans_from.get(i) + Messages.getString("MacroActions.54") + cur_panel + Messages.getString("MacroActions.55")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
								}
								t_checkpoints.clear();
								t_checkpoints = null;
								p_list = null;
							}
						} else {
							err_msg = trans_from.get(i);
							sendLog(Messages.getString("MacroActions.56") + trans_from.get(i) + Messages.getString("MacroActions.57")); //$NON-NLS-1$ //$NON-NLS-2$
						}
					}
				}
			}
			popped();

			if (t_list.size() == 0) {
				return null;
			}
			Collections.sort(t_list, new HistoryComparator());
			return t_list;
		} else {
			err_msg = getKey4MacroName();
			sendLog(Messages.getString("MacroActions.58") + getKey4MacroName() + Messages.getString("MacroActions.59")); //$NON-NLS-1$ //$NON-NLS-2$
			return null;
		}
	}


	class HistoryComparator implements Comparator<ArrayList<String>> {
		@Override
		public int compare(ArrayList<String> o1, ArrayList<String> o2) {
			if (o1.size() == o2.size()) {
				return 0;
			} else if (o1.size() < o2.size()) {
				return -1;
			}
			return 1;
		}
	}
}
