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

import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;

import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys;
import org.openqa.selenium.NotFoundException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.WebDriverWait;

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

public class SendKeys extends PageAction {

	//クリップボードの文字列(engrave以下)を保持
	private static String s_engraved = "";
	

	@Override
	protected void doCore(WebDriver drv, WebElement elm) {
		if (elm != null) {
			Object t_obj = arg_map.get("keys"); //$NON-NLS-1$
			String t_keys = t_obj.toString();
			if (t_keys.endsWith("\\n")) { //$NON-NLS-1$
				t_keys = t_keys.replaceFirst("^([\\s\\S]*)\\\\n$", "$1") + "\n"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			}
			
			//暫定的にコピペする。
			Toolkit kit = Toolkit.getDefaultToolkit();
			Clipboard clip = kit.getSystemClipboard();

			String clip_str = "";
			clip_str = getClipBoardText();

			if (clip_str.startsWith("engrave>")) {
				//再利用したい文字列だけ保持
				s_engraved = clip_str.substring(8);
			}

			if (t_keys.startsWith("chord:")) { //$NON-NLS-1$
				//※contrl+cなら、chord()でengrave文字列を退避する。
				t_keys = chord(t_keys);

				if (t_obj.toString().matches("^.*\\[\\s*CONTROL\\s*,\\s*v\\s*\\].*$")) {
					if (clip_str.startsWith("engrave>")) {
						StringSelection ss = new StringSelection(s_engraved);
						clip.setContents(ss, ss);
					}
					
					//文字列を送る。
					waitUntil1stSendKeys(drv, elm, t_keys);

					//contrl+vなら最後にクリップボードに戻す。
					restoreEngraved();
				} else {
					//文字列を送る。
					elm.sendKeys(t_keys);
				}
			} else {
				boolean after_waited = false; 
				//現在のテキストエリアをクリアし、カーソルを最初に戻す。
				if (t_keys.matches("^\\+\\[.*\\]$")) {
					//追加型のテキスト入力
					t_keys = t_keys.replaceFirst("^\\+:\\[(.*)\\]$", "$1");
				} else {
					//追加ではなく全て書き替えるので全選択する。
					waitUntil1stSendKeys(drv, elm, Keys.CONTROL + "a");
					
					//一度待てば大丈夫
					after_waited = true;
				}
				
				String t_ret = null;
				if (t_keys.indexOf("\n") == t_keys.length() - 1) {
					t_ret = "\n";
					t_keys = t_keys.substring(0, t_keys.length() - 1);
				}

				if (t_keys.length() > 0) {
					//クリップボードに文字列をコピーする。
					setClipboard(drv, clip, elm, t_keys);

					if (after_waited) {
						elm.sendKeys(Keys.CONTROL + "v");
					} else {
						waitUntil1stSendKeys(drv, elm, Keys.CONTROL + "v");
						//一度待てば大丈夫
						after_waited = true;
					}
				}
				
				//engravedがあれば、クリップボードに戻す。
				restoreEngraved();
				if (t_ret != null) {
					if (after_waited) {
						elm.sendKeys(Keys.RETURN);
					} else {
						waitUntil1stSendKeys(drv, elm, Keys.RETURN);
					}
				}
			}
		} else {
			String target = getXpath();
			if (arg_map.containsKey("itemID")) {
				target += " itemID=" + arg_map.get("itemID").toString();
			}
			throw new NotFoundException(this.getClass().getSimpleName() + " Not found " + target); //$NON-NLS-1$
		}
	}
	
	private static String s_clip_js = null;
	
	//ブラウザから取得できるようにクリップボードにコピーする。
	private void setClipboard(WebDriver drv, Clipboard clip, WebElement elm, String str) {
		if (PagePlayer.isRemote()) {
			//リモートの場合、Javascriptでクリップボードにコピーする。
			if (s_clip_js == null) {
				s_clip_js = "var t_x_t=document.createElement(\"textarea\");t_x_t.style.position='fixed';";
				s_clip_js += "t_x_t.style.top=0;t_x_t.style.left=0;t_x_t.style.width='2em';t_x_t.style.height='2em';";
				s_clip_js += "t_x_t.style.padding=0;t_x_t.style.border='none';t_x_t.style.outline='none';";
				s_clip_js += "t_x_t.style.boxShadow='none';t_x_t.style.background='transparent';t_x_t.value='$$$';";
				s_clip_js += "document.body.appendChild(t_x_t);t_x_t.select();document.execCommand('copy');";
				s_clip_js += "document.body.removeChild(t_x_t);";
			}
			JavascriptExecutor jsexe = (JavascriptExecutor)drv;
			jsexe.executeScript(s_clip_js.replace("$$$", str));
		} else {
			StringSelection ss = new StringSelection(str);
			clip.setContents(ss, ss);
		}
	}
	
	//clickやsendkeysで隠れていたために見えない場合のwait。
	private void waitUntil1stSendKeys(WebDriver drv, final WebElement elm, final java.lang.CharSequence... keys) {
		int t_sec = getTimeout();
		WebDriverWait wait = new WebDriverWait(drv, t_sec);

		final ExpectedCondition<Boolean> t_ec = new ExpectedCondition<Boolean>() {
			public Boolean apply(WebDriver drv) {
				try {
					elm.sendKeys(keys);
				} catch (Throwable e) {
					return false;
				}
				return true;
			}
		};
		wait.until(t_ec);
	}

	
	private String getClipBoardText() {
		//暫定的にコピペする。
		Toolkit kit = Toolkit.getDefaultToolkit();
		Clipboard clip = kit.getSystemClipboard();
		
		try {
			return (String) clip.getData(DataFlavor.stringFlavor);
		} catch (UnsupportedFlavorException | IOException e) {
			//unicode云々と文句を言われることがあるが無視する。
		}
		return "";
	}

	//clipboardにengravedを貼り付ける。
	private void restoreEngraved() {
		if (s_engraved.length() > 0) {
			Toolkit kit = Toolkit.getDefaultToolkit();
			Clipboard clip = kit.getSystemClipboard();

			StringSelection ss = new StringSelection("engrave>" + s_engraved);
			clip.setContents(ss, ss);
		}
	}
	
	
	//以下、Assertと二重の書き込みになるが自動的に整合性は保たれる。
	//WebElementGetterのArgument欄の値を指定する。
	protected ArrayList<String> getLabelAndParams() {
		ArrayList<String> t_list2 = new ArrayList<String>();
		t_list2.add("-"); //$NON-NLS-1$
		t_list2.add("Keys"); //$NON-NLS-1$
		Object t_keys = arg_map.get("keys"); //$NON-NLS-1$
		if (t_keys != null) {
			t_list2.add(t_keys.toString());
			if (!t_keys.toString().startsWith("chord:")) { //$NON-NLS-1$
				t_list2.add("chord:"); //$NON-NLS-1$
			}
		} else {
			t_list2.add("chord:"); //$NON-NLS-1$
		}
		return t_list2;
	}

	
	//このPageActionが対象とするXPathの配列を返す。入力可能なタグ以外対象外なので引数に影響されない。
	protected String[] getFocussingXPaths(String xpath) {
		if (xpath != null && xpath.length() > 0) {
			return super.getFocussingXPaths(xpath);
		}
		String[] t_result = {"//input[@type='text']", "//input[@type='password']", "//textarea"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		return t_result;
	}
	
	//PageActionEditorの第二列に書き込む内容を合成する。
	@SuppressWarnings("unchecked")
	protected String getJsonElement(String target, String arg) {
		if (target != null && arg != null) {
			//targetからMapを作り、keysを加えて、Json化した方が安全。
			arg_map = (HashMap<String, Object>)Tool.getObjectfromJSON(HashMap.class, "{" + target + "}"); //$NON-NLS-1$ //$NON-NLS-2$
			arg_map.put("keys", arg); //$NON-NLS-1$
		}

		return super.getJsonElement(null, null);
	}
	
	protected JPopupMenu getPopup(WebElementGetter wg, String target) {
		JPopupMenu t_pop = new JPopupMenu();
		t_pop.add(new JMenuItem(new InsertAction("\"id\":\"\"", "AddID", wg, target))); //$NON-NLS-1$ //$NON-NLS-2$
		t_pop.add(new JMenuItem(new InsertAction("\"xpath\":\"//*[@id='']\"", "AddXpath", wg, target))); //$NON-NLS-1$ //$NON-NLS-2$
		
		JMenu t_menu = new JMenu("Matches"); //$NON-NLS-1$
		t_menu.add(new JMenuItem(new InsertAction("Has Tag", "HasTag", wg, target))); //$NON-NLS-1$ //$NON-NLS-2$
		t_pop.add(t_menu);
		return t_pop;
	}
	
	//パラメータ指定欄でのキー入力結果により、パラメータを自動指定する場合に上書きする。
		protected boolean modifyByKeyInput(KeyEvent e, StringBuilder strb) {
			String c_str = strb.toString();
			if (c_str.startsWith("chord:")) { //$NON-NLS-1$
				int code = e.getKeyCode();

				ArrayList<String> t_list = new ArrayList<String>();
				if (e.isAltDown()) {
					t_list.add("ALT"); //$NON-NLS-1$
				}
				if (e.isControlDown()) {
					t_list.add("CONTROL"); //$NON-NLS-1$
				}
				if (e.isShiftDown()) {
					t_list.add("SHIFT"); //$NON-NLS-1$
				}
				if (e.isMetaDown()) {
					t_list.add("META"); //$NON-NLS-1$
				}
				
				switch (code) { //以下で処理した後、codeを0にする。処理した印とする。
					case KeyEvent.VK_F1: t_list.add("F1"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_F2: t_list.add("F2"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_F3: t_list.add("F3"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_F4: t_list.add("F4"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_F5: t_list.add("F5"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_F6: t_list.add("F6"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_F7: t_list.add("F7"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_F8: t_list.add("F8"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_F9: t_list.add("F9"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_F10: t_list.add("F10"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_F11: t_list.add("F11"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_F12: t_list.add("F12"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_UP: t_list.add("ARROW_UP"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_DOWN: t_list.add("ARROW_DOWN"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_LEFT: t_list.add("ARROW_LEFT"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_RIGHT: t_list.add("ARROW_RIGHT"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_KP_UP: t_list.add("UP"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_KP_DOWN: t_list.add("DOWN"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_KP_LEFT: t_list.add("LEFT"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_KP_RIGHT: t_list.add("RIGHT"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_BACK_SPACE: t_list.add("BACK_SPACE"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_CANCEL: t_list.add("CANCEL"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_CLEAR: t_list.add("CLEAR"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_DELETE: t_list.add("DELETE"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_END: t_list.add("END"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_ENTER: t_list.add("ENTER"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_ESCAPE: t_list.add("ESCAPE"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_HELP: t_list.add("HELP"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_HOME: t_list.add("HOME"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_INSERT: t_list.add("INSERT"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_PAGE_DOWN: t_list.add("PAGE_DOWN"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_PAGE_UP: t_list.add("PAGE_UP"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_PAUSE: t_list.add("PAUSE"); code = 0; break; //$NON-NLS-1$
					case KeyEvent.VK_HALF_WIDTH: t_list.add("ZENKAKU_HANKAKU"); code = 0; break; //$NON-NLS-1$
				}
				if (code != 0) {
					String t_s = KeyEvent.getKeyText(e.getKeyCode());
					if (t_s.length() > 0) {
						t_list.add(t_s.toLowerCase());
					}
				}
				if (t_list.size() > 0) {
					strb.replace(0, strb.length(), "chord:" + Tool.getJSONfromObject(t_list)); //$NON-NLS-1$
					return true;
				}
			}
			return false;
		}

	//キー変換表
	private static HashMap<String,Enum<Keys>> key_maps = null;
	private static void initKeyMaps() {
		key_maps = new HashMap<String,Enum<Keys>>();
		
		key_maps.put("ALT", Keys.ALT); //$NON-NLS-1$
		key_maps.put("ARROW_DOWN", Keys.ARROW_DOWN); //$NON-NLS-1$
		key_maps.put("ARROW_LEFT", Keys.ARROW_LEFT); //$NON-NLS-1$
		key_maps.put("ARROW_RIGHT", Keys.ARROW_RIGHT); //$NON-NLS-1$
		key_maps.put("ARROW_UP", Keys.ARROW_UP); //$NON-NLS-1$
		key_maps.put("BACK_SPACE", Keys.BACK_SPACE); //$NON-NLS-1$
		key_maps.put("CANCEL", Keys.CANCEL); //$NON-NLS-1$
		key_maps.put("CLEAR", Keys.CLEAR); //$NON-NLS-1$
		key_maps.put("COMMAND", Keys.COMMAND); //$NON-NLS-1$
		key_maps.put("CONTROL", Keys.CONTROL); //$NON-NLS-1$
		key_maps.put("DELETEL", Keys.DELETE); //$NON-NLS-1$
		key_maps.put("DOWN", Keys.DOWN); //$NON-NLS-1$
		key_maps.put("END", Keys.END); //$NON-NLS-1$
		key_maps.put("ENTER", Keys.ENTER); //$NON-NLS-1$
		key_maps.put("ESCAPE", Keys.ESCAPE); //$NON-NLS-1$
		key_maps.put("F1", Keys.F1); //$NON-NLS-1$
		key_maps.put("F10", Keys.F10); //$NON-NLS-1$
		key_maps.put("F11", Keys.F11); //$NON-NLS-1$
		key_maps.put("F12", Keys.F12); //$NON-NLS-1$
		key_maps.put("F2", Keys.F2); //$NON-NLS-1$
		key_maps.put("F3", Keys.F3); //$NON-NLS-1$
		key_maps.put("F4", Keys.F4); //$NON-NLS-1$
		key_maps.put("F5", Keys.F5); //$NON-NLS-1$
		key_maps.put("F6", Keys.F6); //$NON-NLS-1$
		key_maps.put("F7", Keys.F7); //$NON-NLS-1$
		key_maps.put("F8", Keys.F8); //$NON-NLS-1$
		key_maps.put("F9", Keys.F9); //$NON-NLS-1$
		key_maps.put("HELP", Keys.HELP); //$NON-NLS-1$
		key_maps.put("HOME", Keys.HOME); //$NON-NLS-1$
		key_maps.put("INSERT", Keys.INSERT); //$NON-NLS-1$
		key_maps.put("LEFT", Keys.LEFT); //$NON-NLS-1$
		key_maps.put("LEFT_ALT", Keys.LEFT_ALT); //$NON-NLS-1$
		key_maps.put("LEFT_CONTROL", Keys.LEFT_CONTROL); //$NON-NLS-1$
		key_maps.put("LEFT_SHIFT", Keys.LEFT_SHIFT); //$NON-NLS-1$
		key_maps.put("META", Keys.META); //$NON-NLS-1$
		key_maps.put("PAGE_DOWN", Keys.PAGE_DOWN); //$NON-NLS-1$
		key_maps.put("PAGE_UP", Keys.PAGE_UP); //$NON-NLS-1$
		key_maps.put("PAUSE", Keys.PAUSE); //$NON-NLS-1$
		key_maps.put("RIGHT", Keys.RIGHT); //$NON-NLS-1$
		key_maps.put("SHIFT", Keys.SHIFT); //$NON-NLS-1$
		key_maps.put("UP", Keys.UP); //$NON-NLS-1$
		key_maps.put("ZENKAKU_HANKAKU", Keys.ZENKAKU_HANKAKU); //$NON-NLS-1$
	}
	
	//表示されないキーを文字列に変換する。
	private String chord(String keys) {
		String t_result = keys;
		if (keys.startsWith("chord:")) { //$NON-NLS-1$
			//特殊キーとの同時入力
			if ( key_maps == null) {
				initKeyMaps();
			}
			
			keys = keys.replaceFirst("^chord:", ""); //$NON-NLS-1$ //$NON-NLS-2$
			@SuppressWarnings("unchecked")
			ArrayList<String> t_keys = (ArrayList<String>)Tool.getObjectfromJSON(ArrayList.class, keys);
			
			if (t_keys.contains("CONTROL")) {
				if (t_keys.contains("c")) {
					//engravedがあれば退避する
					String clip_str = "";
					clip_str = getClipBoardText();
					if (clip_str.startsWith("engrave>")) {
						//再利用したい文字列だけ保持
						s_engraved = clip_str.substring(8);
					}
				}
			}
			
			t_result = ""; //$NON-NLS-1$
			for (int i = 0; i < t_keys.size(); i++) {
				if (key_maps.containsKey(t_keys.get(i))) {
					t_result += key_maps.get(t_keys.get(i));
				} else {
					//1文字しか許容しない。
					t_result += t_keys.get(i).substring(0, 1);
				}
			}
			//同時押下の形式に変換する。
			t_result = Keys.chord(t_result);
		}
		return t_result;
	}
}
