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

import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;

import org.openqa.selenium.By;
import org.openqa.selenium.MutableCapabilities;
import org.openqa.selenium.NotFoundException;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.edge.EdgeDriver;
import org.openqa.selenium.edge.EdgeOptions;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.ie.InternetExplorerOptions;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.safari.SafariDriver;
import org.openqa.selenium.safari.SafariOptions;

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

public class PagePlayer {
	//外部PKGから参照される可能性あり。
	private static WebDriver s_driver = null;
	private static MutableCapabilities s_cap = null;
	private static boolean s_is_remote = false;

	//操作リスト：PageActionEditorから参照するためProtctedにする。
	protected ArrayList<PageAction> actions = new ArrayList<PageAction>();
	protected boolean snapmode = true; //defaultでは例外発生時にスナップを取る。
	protected PageAction cur_action; //エラーになった場合に備えて、現在のアクションを保持。

	protected String paramName() {
		if (actions != null) {
			for (int i = 0; i < actions.size(); i++) {
				if (actions.get(i).getArg("$PARAMETER_NAME") != null) {
					return actions.get(i).getArg("$PARAMETER_NAME").toString();
				}
			}
		}
		return null;
	}

	//System.getPropertyを試みる。失敗したら空文字を返す。
	protected static String getProperty(String key) {
		try {
			return System.getProperty(key);
		} catch (Exception e) {
			return null;
		}
	}

	//remoteかどうかを返す。
	protected static boolean isRemote() {
		return s_is_remote;
	}

	//hand,verifyを含まなければ完全自動。TestCommandを実行する前に呼び出されるので、ここで各アクションの初期化（定義されていれば）を行う。
	protected boolean isAutomatic() {
		boolean is_auto = true;
		for (int i = 0; i < actions.size(); i++) {
			PageAction t_act = actions.get(i);
			t_act.initAction();
			if (!t_act.isAutomatic()) {
				is_auto = false;
			}
		}
		return is_auto;
	}

	///////////////////////////////////////////
	protected boolean result_is_js; //判定用文字列がJavaScriptの場合

	private ArrayList<String> assert_regs;

	// driverの初期化
	public static void initDriver(HashMap<String, String> browser) throws Exception {
		if (s_driver != null) {
			return;
		}

		String bname = null;

		String server_address = ""; //$NON-NLS-1$
		String user_id = null; //$NON-NLS-1$
		String pwd = null; //$NON-NLS-1$

		if (browser != null) {
			bname = browser.get("browser"); //$NON-NLS-1$
			if (bname != null) {
				bname = bname.toLowerCase();
			}
			server_address = browser.get("proxy"); //$NON-NLS-1$
			user_id = browser.get("proxy.user_id"); //$NON-NLS-1$
			pwd = browser.get("proxy.password"); //$NON-NLS-1$
			if (browser.containsKey("class")) {
				// browser:= {"class":"WebDriverのクラス名",
				//            "url":"ブラウザのドライバのURL",
				//            "capabilities":{}
				//            }
				//classが設定されているのはリモートであろう。Appiumを想定。
				s_is_remote = true;

				//JsonからDesiredCapabilitiesの作成
				@SuppressWarnings("unchecked")
				HashMap<String, Object> t_map = (HashMap<String, Object>)Tool.getObjectfromJSON(HashMap.class, browser.get("capabilities"));
				s_cap = new DesiredCapabilities();
				for (Entry<String, Object> entry: t_map.entrySet()) {
					s_cap.setCapability(entry.getKey(), entry.getValue());
				}

				//WebDriverの作成
				Class<?> drv_class = Tool.forName(browser.get("class"));
				Class<?>[] arg_classes = new Class<?>[] {URL.class, DesiredCapabilities.class};
				Object[] arg_objs = new Object[] {new URL(browser.get("url")), s_cap};
				s_driver = (WebDriver)Tool.newObject(drv_class, arg_classes, arg_objs);
			}
		} else {
			//頼みの綱はSystem.property
			bname = getProperty("webtest.browser").toLowerCase(); //$NON-NLS-1$
			if (bname != null) {
				bname = bname.toLowerCase();
			}
			server_address = getProperty("webtest.proxy"); //$NON-NLS-1$
			user_id = getProperty("webtest.proxy.user_id"); //$NON-NLS-1$
			pwd = getProperty("webtest.proxy.password"); //$NON-NLS-1$

			try {
				setCapWithProxy(bname, server_address, user_id, pwd);
				s_driver = createDriver();
			} catch (Exception e) {
				//必要な処理：Exceptionを投げる。processモードでない場合、alert表示。
				Tool.alertAndStop(Messages.getString("PagePlayer.18") + e.getMessage()); //$NON-NLS-1$
			}
		}
		if (s_driver == null) {
			Tool.alertAndStop(Messages.getString("PagePlayer.19")); //$NON-NLS-1$
		}
	}


	//この実装ではポップアップは考慮外
	public ArrayList<String> play(HashMap<String, String> browser) throws Exception {
		try {
			initDriver(browser);
		} catch (Exception e) {
			throw new IllegalStateException("Failed to initialize browser.");
		}

		if (s_driver == null) {
			throw new IllegalStateException("Failed to initialize browser.");
		}
		if (actions.size() == 0) {
			//ドライバが存在して、アクションがないとき終了すべし。
			s_driver.quit();
			s_driver = null;
			return null;
		}
		ArrayList<String> result = new ArrayList<String>();
		assert_regs = new ArrayList<String>();
		if (actions.size() > 0) {
			PageAction.s_win_id = s_driver.getWindowHandle();

			ArrayList<String> asserts = new ArrayList<String>();

			for (int i = 0; i < actions.size(); i++) {
				PageAction t_act = actions.get(i);
				try {
					ArrayList<String> t_strlist = doAction(t_act, s_driver);

					//評価文字列を返却するアクションの場合には、実行結果を文字列として受け取り、返却値に反映する。
					if (t_strlist != null && t_act.assertable()) {
						ArrayList<String> t_reg = t_act.getRegs();
						asserts.addAll(t_reg);
						result.addAll(t_strlist);
					}
				} catch (SnapshotException e) {
					//snapshotでのエラーはそのまま返す。スナップショットはそれ以上取れないため。
					throw e;
				} catch (Exception e) {
					//見つからなかった場合の手がかりとして、Windowやフレームへのフォーカス漏れがあるのでヒントを残す。
					String t_msg = "";
					if (t_act.arg_map.containsKey("xpath")) {
						t_msg = "xpath=" + t_act.arg_map.get("xpath").toString();
					}
					if (snapmode) {
						takeSnap(s_driver, t_msg + "[" + e.getMessage() + "]");
						if (i < actions.size() -1) {
							//リセットする前に終わったら、リセットだけはチャレンジする。
							recoveryReset(s_driver);
						}
					}
					if (e instanceof NotFoundException) {
						throw new NotFoundException(Tool.removeRedundancy(null, t_msg + "[\n" + Tool.makeIndent(e) + "]"));
					}
					throw e;
				}
			}
			if (asserts.size() > 0) {
				assert_regs = asserts;
			}
		} else {
			//actionsがない場合、TestCaseのLastである。
			quit();
		}
		return result;
	}

	//ドライバを終了する。
	public static void quit() {
		if (s_driver != null) {
			s_driver.quit();
			s_driver = null;
		}
	}

	//編集モードの処理のための中継処理のメソッド
	private ArrayList<String> doAction(PageAction act, WebDriver drv) {
		//有効化されているアクションのみを実行する。
		cur_action = act;
		return act.doAction(drv);
	}

	//半ばでエラーになったとしても、最後にリセットが入っていればリセットを試みる。
	private void recoveryReset(WebDriver drv) {
		if (actions.get(actions.size() -1) instanceof ResetBrowser) {
			actions.get(actions.size() -1).doAction(drv);
		}
	}

	private void takeSnap(WebDriver drv, String msg) {
		if(drv instanceof TakesScreenshot && snapmode) {
			Snap t_snap = new Snap(cur_action);
			t_snap.arg_map.put("file_header", "error"); //$NON-NLS-1$ //$NON-NLS-2$

			//エラーになったアクションをスナップショットのコメントに書き込む。
			String t_msg = "Action=" + Tool.getJSONfromObject(cur_action.arg_map) + "\n error=" + msg; //$NON-NLS-1$ //$NON-NLS-2$

			List<WebElement> t_list = drv.findElements(By.xpath("//frame|//iframe")); //$NON-NLS-1$
			if (t_list != null && t_list.size() > 0) {
				int n = t_list.size();
				t_msg += Messages.getString("PagePlayer.0") + Integer.toString(n); //$NON-NLS-1$
			}
			t_snap.arg_map.put("message", t_msg); //$NON-NLS-1$

			try {
				t_snap.doCore(drv, null);
			} catch (Exception e) {
				String e_msg = "";
				if (msg != null && msg.length() > 0) {
					//既にseleniumのビルド情報は入っているので除去する。
					e_msg = e.getMessage();
				}
				throw new SnapshotException("\t\tsnapshot " + e_msg + "[" + msg + "] " + Messages.getString("PagePlayer.35")); //$NON-NLS-1$
			}
		} else {
			throw new SnapshotException("\t\tsnapshot " + msg +  " " + Messages.getString("PagePlayer.35")); //$NON-NLS-1$
		}
	}

	protected ArrayList<String> getAssertRegs() {
		return assert_regs;
	}

	//Proxy情報があればセットする。
	private static void setCapWithProxy(String browser, String svr_address, String user, String pwd) {
		//user,pwdをnullにしておかない。
		if (user == null) user = "";
		if (pwd == null) pwd = "";

		if (browser.equals("firefox")) { //$NON-NLS-1$
			s_cap = new FirefoxOptions();
		} else if (browser.equals("chrome")) { //$NON-NLS-1$
			s_cap = new ChromeOptions();
		} else if (browser.equals("safari")) { //$NON-NLS-1$
			s_cap = new SafariOptions();
		} else if (browser.equals("ie")) { //$NON-NLS-1$
			s_cap = new InternetExplorerOptions();
		} else if (browser.equals("edge")) { //$NON-NLS-1$
			s_cap = new EdgeOptions();
		} else {
			s_cap = DesiredCapabilities.htmlUnit();
		}

		//Proxyのアドレスがセットされていたら、s_capにプロキシ情報をセットする。
		//※但し、プロキシサーバによってはスクリプトによる認証を受け付けない場合がある。その場合、ブラウザに認証情報を記憶させること。
		if (svr_address != null && svr_address.length() > 0) {
			Proxy proxy = new Proxy();
			proxy.setProxyType(Proxy.ProxyType.MANUAL);
			if (user.length() > 0) {
				proxy.setHttpProxy(user + ":" + pwd + "@" + svr_address);
				proxy.setSslProxy(user + ":" + pwd + "@" + svr_address);

				//socksも追加
				proxy.setSocksProxy(svr_address);
				proxy.setSocksUsername(user);
				proxy.setSocksPassword(pwd);
				Tool.logIfDebug(null, "setHttpProxy=" + user + ":" + pwd + "@" + svr_address);
			} else {
				proxy.setHttpProxy(svr_address);

				//socksも追加
				proxy.setSocksProxy(svr_address);
				Tool.logIfDebug(null, "setHttpProxy=" + svr_address);
			}
			s_cap.setCapability(CapabilityType.PROXY, proxy);
		}
	}

	//desiredcapabilityを元にWebDriverを構成する。
	private static WebDriver createDriver() {
		WebDriver t_drv = null;

		//bnameはSeleniumで規定した正式名
		String bname = s_cap.getBrowserName();
		if (bname.equals("firefox")) { //$NON-NLS-1$
			t_drv = new FirefoxDriver((FirefoxOptions)s_cap);
		} else if (bname.equals("internet explorer")) { //$NON-NLS-1$
			t_drv = new InternetExplorerDriver((InternetExplorerOptions)s_cap);
		} else if (bname.equals("edge")) { //$NON-NLS-1$
			t_drv = new EdgeDriver((EdgeOptions)s_cap);
		} else if (bname.equals("chrome")) { //$NON-NLS-1$
			t_drv = new ChromeDriver((ChromeOptions)s_cap);
		} else if (bname.equals("safari")) { //$NON-NLS-1$
			t_drv = new SafariDriver((SafariOptions)s_cap);
		}
		return t_drv;
	}
}
