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

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.JPopupMenu;

import org.openqa.selenium.NoAlertPresentException;
import org.openqa.selenium.NotFoundException;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.Wait;
import org.openqa.selenium.support.ui.WebDriverWait;

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

public class CheckFor extends PageAction {

	//返却値を保持するメンバ
	private String temp_result;
	//一時的に判定条件を保持する。
	private String cur_reg;

	//指定された要素の内部文字列を返す
	public final ArrayList<String> doAction(WebDriver drv) {
		Wait<WebDriver> wait = new WebDriverWait(drv, getTimeout());

		//正規表現でスキップ指定
		ArrayList<String> result = new ArrayList<String>();

		cur_reg = convertReg((String)arg_map.get("value")); //$NON-NLS-1$
		if (":SKIP".equals(cur_reg)) { //$NON-NLS-1$
			result.add(""); //$NON-NLS-1$
			return result;
		}

		temp_result = null;

		if ("Alert".equals(options)) { //$NON-NLS-1$
			//Alertモードの時
			try {
				if ("(?!.+)".equals(cur_reg)) { //$NON-NLS-1$
					try {
						Wait<WebDriver> wait2 = new WebDriverWait(drv, s_localTimeout);
						wait2.until(ExpectedConditions.alertIsPresent());
					} catch (TimeoutException e) {
						//見つからなかったのでINVISIBLEである。
						result.add(""); //$NON-NLS-1$
						return result;
					}
				} else {
					wait.until(ExpectedConditions.alertIsPresent());
				}
			} catch (TimeoutException e) {
				throw new TimeoutException("\t\tAlert not found: msg=" + e.getMessage()); //$NON-NLS-1$
			}
			org.openqa.selenium.Alert t_alert = drv.switchTo().alert();

			String t_str = t_alert.getText();
			t_alert.accept();
			result.add(t_str); //$NON-NLS-1$
			return result;
		} else if ("ToWindow".equals(options)) { //$NON-NLS-1$
			//ToWindowモードの時
			if (getArg("title") != null) { //$NON-NLS-1$
				cur_reg = getArg("title").toString(); //$NON-NLS-1$
			} else {
				throw new NotFoundException("\t\tWindow Title not found:"); //$NON-NLS-1$
			}

			//正規表現でスキップ指定
			final String wid = drv.getWindowHandle();
			final Pattern w_pat = Pattern.compile(cur_reg);

			ExpectedCondition<Boolean> t_ec = new ExpectedCondition<Boolean>() {
				public Boolean apply(WebDriver drv) {
					//開いたウインドウも含め全部のウインドウIDを取得する
					Set<String> w_ids = drv.getWindowHandles();

					for(String s : w_ids) {
						drv.switchTo().window(s);
						temp_result = drv.getTitle();
						Matcher t_m = w_pat.matcher(temp_result);
						if (t_m.find()) {
							//ブラウザが最後尾に文字列を勝手に入れる場合がある。検証はcheckForを使うこと。
							return true;
						}
						drv.switchTo().window(wid);
					}
					return null;
				}
			};
			try {
				Wait<WebDriver> wait2 = new WebDriverWait(drv, s_localTimeout);
				wait2.until(t_ec);
				//見つかった。
			} catch (TimeoutException e) {
				throw new TimeoutException("\t\tWindow not found:msg=" + e.getMessage()); //$NON-NLS-1$
			}
			//WindowモードはAssertableではない。
			return null;
		} else if ("visible=true".equals(cur_reg.toLowerCase())) { //$NON-NLS-1$
			//その要素が存在する。
			try {
				Wait<WebDriver> wait2 = new WebDriverWait(drv, s_localTimeout);
				wait2.until(ExpectedConditions.visibilityOfElementLocated(getLocator()));
				result.add("visible=true"); //$NON-NLS-1$
				return result;
			} catch (TimeoutException e) {
				String target = getTarget();
				if (arg_map.containsKey("itemID")) {
					target += " itemID=" + arg_map.get("itemID").toString();
				}
				throw new TimeoutException("\t\tcheckfor " + target + " : " + Messages.getString("CheckFor.7")); //$NON-NLS-1$
			}
		} else if ("visible=false".equals(cur_reg.toLowerCase())) { //$NON-NLS-1$
			//その要素が見つからなくなるまで待つ。
			try {
				Wait<WebDriver> wait2 = new WebDriverWait(drv, s_localTimeout);
				wait2.until(ExpectedConditions.invisibilityOfElementLocated(getLocator()));
				result.add("visible=false"); //$NON-NLS-1$
				return result;
			} catch (TimeoutException e) {
				String target = getXpath();
				if (arg_map.containsKey("itemID")) {
					target += " itemID=" + arg_map.get("itemID").toString();
				}
				throw new TimeoutException("\t\tcheckfor " + target + " : " + Messages.getString("CheckFor.2")); //$NON-NLS-1$
			}
		} else if ("enabled=true".equals(cur_reg.toLowerCase())) { //$NON-NLS-1$
			//その要素が有効になるまで待つ。
			try {
				Wait<WebDriver> wait2 = new WebDriverWait(drv, s_localTimeout);
				ExpectedCondition<Boolean> t_ec = new ExpectedCondition<Boolean>() {
					public Boolean apply(WebDriver drv) {
						for (WebElement elm : drv.findElements(getLocator())) {
							if (elm.isEnabled()) {
								return true;
							}
						}
						return false;
					}
				};
				wait2.until(t_ec);
				result.add("enabled=true"); //$NON-NLS-1$
				return result;
			} catch (TimeoutException e) {
				String target = getXpath();
				if (arg_map.containsKey("itemID")) {
					target += " itemID=" + arg_map.get("itemID").toString();
				}
				throw new TimeoutException("\t\tcheckfor " + target + " : " + Messages.getString("CheckFor.3")); //$NON-NLS-1$
			}
		} else if ("enabled=false".equals(cur_reg.toLowerCase())) { //$NON-NLS-1$
			//その要素が無効になるまで待つ。
			try {
				Wait<WebDriver> wait2 = new WebDriverWait(drv, s_localTimeout);
				ExpectedCondition<Boolean> t_ec = new ExpectedCondition<Boolean>() {
					public Boolean apply(WebDriver drv) {
						for (WebElement elm : drv.findElements(getLocator())) {
							if (elm.isEnabled()) {
								return false;
							}
						}
						return true;
					}
				};
				wait2.until(t_ec);
				result.add("enabled=false"); //$NON-NLS-1$
				return result;
			} catch (TimeoutException e) {
				String target = getXpath();
				if (arg_map.containsKey("itemID")) {
					target += " itemID=" + arg_map.get("itemID").toString();
				}
				throw new TimeoutException("\t\tcheckfor " + target + " : " + Messages.getString("CheckFor.4")); //$NON-NLS-1$
			}
		} else if ("selected=true".equals(cur_reg.toLowerCase())) { //$NON-NLS-1$
			//その要素が存在して選択されている。
			try {
				Wait<WebDriver> wait2 = new WebDriverWait(drv, s_localTimeout);
				wait2.until(ExpectedConditions.elementToBeSelected(getLocator()));
				result.add("selected=true"); //$NON-NLS-1$
				return result;
			} catch (TimeoutException e) {
				String target = getXpath();
				if (arg_map.containsKey("itemID")) {
					target += " itemID=" + arg_map.get("itemID").toString();
				}
				throw new TimeoutException("\t\tcheckfor " + target + " : " + Messages.getString("CheckFor.7")); //$NON-NLS-1$
			}
		} else if ("selected=false".equals(cur_reg.toLowerCase())) { //$NON-NLS-1$
			//その要素が存在して非選択になるまで待つ。
			try {
				Wait<WebDriver> wait2 = new WebDriverWait(drv, s_localTimeout);
				ExpectedCondition<Boolean> t_ec = new ExpectedCondition<Boolean>() {
					public Boolean apply(WebDriver drv) {
						for (WebElement elm : drv.findElements(getLocator())) {
							if (elm.isSelected()) {
								return false;
							}
						}
						return true;
					}
				};
				wait2.until(t_ec);
				result.add("selected=false"); //$NON-NLS-1$
				return result;
			} catch (TimeoutException e) {
				String target = getXpath();
				if (arg_map.containsKey("itemID")) {
					target += " itemID=" + arg_map.get("itemID").toString();
				}
				throw new TimeoutException("\t\tcheckfor " + target + "is selected."); //$NON-NLS-1$
			}
		} else if ("focused=true".equals(cur_reg.toLowerCase())) { //$NON-NLS-1$
			//タブキー遷移のようなものの検証に使う。従って、falseパターンはない。
			//指定要素にカーソルが乗っている。jQuery部品は複合要素なので子孫まで調べる。
			ExpectedCondition<Boolean> t_ec = new ExpectedCondition<Boolean>() {
				public Boolean apply(WebDriver drv) {
					for (WebElement elm : drv.findElements(getLocator())) {
						if (elm.equals(drv.switchTo().activeElement())) {
							return true;
						}
					}
					temp_result = drv.switchTo().activeElement().toString();
					return false;
				}
			};
			//要素がFOCUSされるまで待つ。時間までにならなかったら例外と判定する。
			try {
				wait.until(t_ec);
				result.add("focused=true"); //$NON-NLS-1$
				return result;
			} catch (TimeoutException e) {
				//timeアウトは見つからなかった場合。その際に、現在の文字列を返す。
				String target = getXpath();
				if (arg_map.containsKey("itemID")) {
					target += " itemID=" + arg_map.get("itemID").toString();
				}
				throw new NotFoundException("\t\tcheckfor " + target + " msg=" + e.getMessage() + " >>" + temp_result + Messages.getString("CheckFor.10"));  //$NON-NLS-1$ //$NON-NLS-2$
			}
		} else {
			//それ以外は、タグ要素を比べる
			final Pattern r_pat = Pattern.compile(cur_reg);
			ExpectedCondition<Boolean> t_ec = new ExpectedCondition<Boolean>() {
				public Boolean apply(WebDriver drv) {
					//要素からtextを取得する。
					String t_str = null;

					//画面のTitleへのアクセスは、子孫にtitle要素があるとして"//title"とする。
					if ("//title".equals(getXpath())) { //$NON-NLS-1$ //$NON-NLS-2$
						t_str = drv.getTitle();
					} else {
						WebElement elm = drv.findElement(getLocator());

						String t_getter = (String)arg_map.get("getter"); //$NON-NLS-1$
						if (t_getter != null && t_getter.startsWith("@")) { //$NON-NLS-1$
							String t_attr = t_getter.substring(1);
							t_str = elm.getAttribute(t_attr);
						} else {
							t_str = elm.getText();
						}
					}
					//返却値の設定：失敗する場合、最後に読み取った文字
					temp_result = t_str;
					if (t_str != null) {
						if (cur_reg.matches("^=+[0-9\\.]+$")) { //$NON-NLS-1$
							//完全一致
							return cur_reg.equals("=" + temp_result);
						} else if (cur_reg.matches("^[<>=]+[0-9\\.]+$")) { //$NON-NLS-1$
							//数字の大小比較である。
							if (cur_reg.startsWith("$<")) { //$NON-NLS-1$
								if (cur_reg.startsWith("$<=")) { //$NON-NLS-1$
									return VCentral.lessThan(t_str, VCentral.pickNumber(cur_reg), true, false);
								} else {
									return VCentral.lessThan(t_str, VCentral.pickNumber(cur_reg), false, false);
								}
							} else if (cur_reg.startsWith("$>")) { //$NON-NLS-1$
								if (cur_reg.startsWith("$>=")) { //$NON-NLS-1$
									return VCentral.greaterThan(t_str, VCentral.pickNumber(cur_reg), true, false);
								} else {
									return VCentral.greaterThan(t_str, VCentral.pickNumber(cur_reg), false, false);
								}
							} else {
								//=から始まる。
								return VCentral.equalN(t_str, VCentral.pickNumber(cur_reg), false);
							}
						} else {
							//テキストを正規表現で評価する。
							Matcher t_m = r_pat.matcher(t_str);
							if (t_m.find()) {
								return true;
							}
						}
					}
					temp_result += Messages.getString("CheckFor.11"); //$NON-NLS-1$
					return false;
				}
			};
			//要素が期待値になるまで待つ。時間までにならなかったら例外と判定する。
			try {
				wait.until(t_ec);
			} catch (TimeoutException e) {
				//timeアウトは見つからなかった場合。その際に、現在の文字列を返す。
				String target = getXpath();
				if (arg_map.containsKey("itemID")) {
					target += " itemID=" + arg_map.get("itemID").toString();
				}
				throw new NotFoundException("\t\tcheckfor " + target + " msg=" + e.getMessage() + " >> " + temp_result + Messages.getString("CheckFor.13") + getXpath()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			}
		}
		result.add(temp_result);
		return result;
	}


	protected final String getXpath() {
		String t_path = super.getXpath();

		//$VAR条件が残っていたら、全体を指しているのだと解釈する。
		final Pattern t_pat = Pattern.compile("\\[contains\\([@\\w\\-\\.]+?,'\\$VAR'\\)\\]");
		Matcher t_m = t_pat.matcher(t_path);
		if (t_m.find()) {
			t_path = t_m.replaceAll("");
		}
		final Pattern t_pat2 = Pattern.compile("\\[[@\\w\\-\\.]+?='\\$VAR'\\]");
		t_m = t_pat2.matcher(t_path);
		if (t_m.find()) {
			t_path = t_m.replaceAll("");
		}
		return t_path;
	}

	//WebElementGetterのTarget欄の値を指定する。
	protected String getTarget() {
		if ("Alert".equals(options)) { //$NON-NLS-1$
			//Alertのメッセージが記録されていれば返す。
			arg_map.get("message"); //$NON-NLS-1$
		}
		return super.getTarget(); //$NON-NLS-1$
	}

	//Assert判定する
	protected boolean assertable() {
		if ("ToWindow".equals(options)) { //$NON-NLS-1$
			return false;
		}
		return true;
	}

	//path情報を持つアクションかどうか
	protected boolean hasPath() {
		if ("Alert".equals(options)) { //$NON-NLS-1$
			return false;
		}
		return true;
	}

	//このPageActionが対象とするWebElementとそのXpathを取得する。
	protected boolean getFocussingElements(WebDriver drv, ArrayList<WebElement> el_list, ArrayList<String> xp_list, String xpath) {
		if ("ToWindow".equals(options)) { //$NON-NLS-1$
			if (drv != null) {
				Set<String> w_ids = drv.getWindowHandles();

				for(String s : w_ids) {
					xp_list.add(s);
				}
			}
			return true;//WebElementGetterを開くサイン
		} else if ("Alert".equals(options)) { //$NON-NLS-1$
			if (drv != null) {
				//hasPathがfalseなのでフォーカス移動はできないが、走査はする。ヒットしたかどうかも判別できるようにする。

				try {
					org.openqa.selenium.Alert t_alert = drv.switchTo().alert();
					xp_list.add(drv.getWindowHandle());

					arg_map.put("message", t_alert.getText()); //$NON-NLS-1$
				} catch (NoAlertPresentException e) {
					//何もしない。
				}
			}
			return true;//WebElementGetterを開くサイン
		}
		return super.getFocussingElements(drv, el_list, xp_list, xpath);
	}


	//このPageActionが対象とするXPathの配列を返す
	protected String[] getFocussingXPaths(String xpath) {
		if ("ToWindow".equals(options) || "Alert".equals(options)) { //$NON-NLS-1$ //$NON-NLS-2$
			super.getFocussingXPaths(xpath);
		}

		if (xpath != null && xpath.length() > 0) {
			if ("//title".equals(xpath)) { //$NON-NLS-1$
				//画面のタイトル指定：htmlの中でtitle要素の指定。特に修正の必要はない。
				return new String[] {xpath};
			}
			return super.getFocussingXPaths(xpath);
		}
		String[] t_result = {"//*[@id]", "//*[@name][not(@id)]"}; //$NON-NLS-1$ //$NON-NLS-2$
		return t_result;
	}

	//以下、Assertと二重の書き込みになるが自動的に整合性は保たれる。
	//WebElementGetterのArgument欄の値を指定する。
	protected ArrayList<String> getLabelAndParams() {
		switch (options) {
			case "Alert": return null; //$NON-NLS-1$
			case "ToWindow":  //$NON-NLS-1$
				ArrayList<String> t_list = new ArrayList<String>();
				t_list.add(Messages.getString("CheckFor.8")); //$NON-NLS-1$
				return t_list;
			case "CheckFor": //$NON-NLS-1$
				ArrayList<String> t_list2 = super.getLabelAndParams();
				t_list2.add((String)arg_map.get("value")); //$NON-NLS-1$
				return t_list2;
		}
		return null;
	}

	//PageActionEditorの第二列に書き込む内容を合成する。
	@SuppressWarnings("unchecked")
	protected String getJsonElement(String target, String arg) {
		Object t_title = arg_map.get("title"); //$NON-NLS-1$

		if (target != null && target.length() > 0) {
			arg_map = ((HashMap<String, Object>)Tool.getObjectfromJSON(HashMap.class, "{" + target + "}")); //$NON-NLS-1$ //$NON-NLS-2$
		}
		if ("ToWindow".equals(options) && t_title != null) { //$NON-NLS-1$
			arg_map.put("value", t_title.toString()); //$NON-NLS-1$
		} else if ("Alert".equals(options)) { //$NON-NLS-1$
			arg_map.put("message", target); //$NON-NLS-1$
		}
		if (arg != null) {
			//確認の正規表現用
			arg_map.put("value", arg); //$NON-NLS-1$
		}
		return super.getJsonElement(null, null);
	}

	protected JPopupMenu getPopup(WebElementGetter wg, String target) {
		if (hasPath() && "CheckFor".equals(options)) { //$NON-NLS-1$
			JPopupMenu t_pop = super.getPopup(wg, target);
			return t_pop;
		}
		return null;
	}

}
