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

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.ftinc.si.assist.run.Messages;
import com.ftinc.si.assist.run.plugins.TestCaseCreator;
import com.ftinc.si.assist.test.AssertRecord;
import com.ftinc.si.assist.test.ObjectRecord;
import com.ftinc.si.assist.test.Record;
import com.ftinc.si.assist.test.TestCaseRecord;
import com.ftinc.si.assist.test.TestCommandRecord;
import com.ftinc.si.assist.test.Tool;

public class JSTestCreator extends TestCaseCreator {

	//Excelから生成したファイルを元に、新たなテストケースを作成する。
	//<testcase name class method arg_types>共通の初期化と環境指定</testcase><cmd>...</cmd>...
	//      includes \t "file1","file2"... ,\n  //fileはローカルパス（複数可能）もしくはURL（1つのみ、browserが必要）
	//      browser \t chrome/firefox/ie/...  //省略可能。実行環境を指定する。L
	//      $[0-9] \t {"initialize":"ソース"} ,\n
	//      $[0-9] \t $(cssselector) ,\n
	//      $[0-9] \t 大域変数名 ,\n
	//<cmd>
	// $[0-9]! javascript式; ,\n
	// $[0-9_]? javascript式(boolean型) ,\n
	//</cmd>
	//<testcase name class method="メソッド名?" arg_types>source</testcase>
	@Override
	protected int createTestRecords(String s) {
		TestCaseRecord n_case = null;
		String method_name = ""; //$NON-NLS-1$
		String init_str = ""; //$NON-NLS-1$
		int arg_num = 0;

		final Pattern pat_case = Pattern.compile("<testcase\\s+name=\"([^\"]+)\"\\s+method=\"([^\"]+)\"\\s+vnum=\"(\\d+)\"[^>]*>([\\s\\S]*?)</testcase>"); //$NON-NLS-1$
		Matcher t_m = pat_case.matcher(s);
		if (t_m.find()) {
			String case_name = t_m.group(1);
			method_name = t_m.group(2);
			arg_num = Integer.valueOf(t_m.group(3));
			init_str = t_m.group(4);//includes,$[n_] \t str,...,\n

			ArrayList<String> init_srclist =  new ArrayList<String>();  //TestCommandで共通処理
			HashMap<String, Object> init_map = new HashMap<String, Object>();
			String[] init_list = null;
			if (init_str != null && init_str.length() > 0) {
				boolean is_browser = false;

				init_list = init_str.split(",\n");
				for (int i = 0; i < init_list.length; i++) {
					if (init_list[i].startsWith("includes")) {
						if (init_list[i].toLowerCase().matches("^.*\\t[fh]\\w+://.+$") && init_list[i].toLowerCase().indexOf(".htm") > 0) {
							//http呼び出しでhtmlを呼んでいるならブラウザ経由
							is_browser = true;
						}
						String[] files = init_list[i].replaceFirst("^includes\t", "").split(",")	;
						ArrayList<String> flist = new ArrayList<String>(Arrays.asList(files));
						init_map.put("includes", flist);
					} else if (init_list[i].startsWith("$")) {
						//0の場合、cssselectorとかある。_xarg[n]でよかろう。
						final Pattern pat_initarg = Pattern.compile("^\\$(\\d+)\t(.*)$");
						Matcher init_m = pat_initarg.matcher(init_list[i]);
						if (init_m.find()) {
							String t_val = init_m.group(2);
							if (t_val.startsWith("{\"initialize\"")) {
								//initialize属性からソースを切り出す。
								@SuppressWarnings("unchecked")
								HashMap<String, String> src_map = (HashMap<String, String>)Tool.getObjectfromJSON(HashMap.class, t_val);

								String t_src = src_map.get("initialize");
								if (t_src.indexOf("return ") < 0) {
									//return を含まない場合、Javascriptコードではない。Jsonである。parseするコードに変換する。
									t_src = "return JSON.parse('" + t_src + "');";
								}

								init_srclist.add("_xarg_" + init_m.group(1) + "=new function(){" + t_src + "};");
							} else {
								//シンボルもしくはCSSSelector
								init_srclist.add("_xarg_" + init_m.group(1) + "=" + t_val + ";");
							}
						}
					}
				}
				//Driverを生成するためのクラス名を記入する。
				if (is_browser) {
					init_map.put("driver", "com.ftinc.si.assist.test.js.JSDriverOnBrowser");
				} else {
					init_map.put("driver", "com.ftinc.si.assist.test.js.JSDefaultDriver");
				}

				//<testcase>...</testcase>の後の<cmd>json</cmd>を取得し、TestCommandRecordの数を確定させる。
				ArrayList<String> str_cmds = new ArrayList<String>();
				final Pattern pat_cmd = Pattern.compile("<cmd>([\\s\\S]+?)</cmd>"); //$NON-NLS-1$
				Matcher t_m2 = pat_cmd.matcher(s);
				while (t_m2.find()) {
					str_cmds.add(t_m2.group(1));
				}

				//新規もしくは既存のTestCaseのデータ領域を取得
				HashMap<Integer, TestCaseRecord> t_casemap = lookupTestCaseWithLength(case_name, str_cmds.size());
				n_case = t_casemap.get(0);
				if (n_case == null) {
					n_case = t_casemap.get(1);//既存に収まらなければ新規作成。
				}
				n_case.changed();

				//入れ物を作る。
				HashMap<Integer, TestCommandRecord> t_cmds = new HashMap<Integer, TestCommandRecord>();
				HashMap<String, ObjectRecord> t_objs = new HashMap<String, ObjectRecord>();
				HashMap<Integer, AssertRecord> t_asserts = new HashMap<Integer, AssertRecord>();

				//cmdの数だけTestCommandRecordを作る。
				for (int i = 0; i < str_cmds.size(); i ++) {
					TestCommandRecord new_cmd = new TestCommandRecord(2);
					new_cmd.id = n_case.begin + i;
					new_cmd.testCase = n_case.name;
					new_cmd.className = "com.ftinc.si.assist.test.js.JSTester";
					new_cmd.returnType = "java.lang.Integer";
					new_cmd.methodName = "test";
					new_cmd.subject = "$0";//staticであろうがなかろうが$0は作る。Excelには現れない。 //$NON-NLS-1$

					String[] types_list = {"java.util.HashMap", "java.util.ArrayList", "java.util.ArrayList"}; //$NON-NLS-1$
					String t_types = ""; //$NON-NLS-1$
					for (int j = 0; j <  types_list.length; j++) {
						if (t_types.length() > 0) {
							t_types += ","; //$NON-NLS-1$
						}
						t_types += "[" + types_list[j] + "," + "$" + Integer.toString(j + 1) + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
					}
					new_cmd.setArgTypes("[" + t_types + "]"); //$NON-NLS-1$ //$NON-NLS-2$

					//argTypesができてからでないとStatusをセットできない。
					for (int j = 0; j <  types_list.length; j++) {
						new_cmd.setArgStatus(j + 1, true);
					}
					new_cmd.setArgStatus(0, true);

					//Assertの分
					new_cmd.setArgStatus(types_list.length + 1, true);

					//str_cmdsはcmdタグ内のjson
					int num_ast = makeCommandRecord(new_cmd, arg_num, init_map, init_srclist, str_cmds.get(i), method_name, t_objs);

					//新しいTestCommandRecordとして登録。
					t_cmds.put(new_cmd.id, new_cmd);

					//Assert
					AssertRecord t_ast = new AssertRecord(2);
					t_ast.enable = true;
					t_ast.id = new_cmd.id;

					//$=の$は=が文字列ではなく演算子という印。
					t_ast.criteriaRex = "[\"$=" + Integer.toString(num_ast) + "\"]";

					//判定のための値は1つ（JSTester.test()の返却値たるInteger）のみ。
					t_ast.targetObj = "ASSERTING(0,$_.toString());";
					t_asserts.put(new_cmd.id, t_ast);
				}

				//入れ物にオブジェクトを格納する。
				try {
					ArrayList<Record> removings = null;
					if (t_casemap.containsKey(0)) {//クリアする必要がある。
						//既存テストケースの配下を削除
						Tool._db().clearCase(n_case, true);
					} else if (t_casemap.containsKey(-1)) {//削除する必要がある。
						removings = new ArrayList<Record>();
						removings.add(t_casemap.get(-1));

						Tool._db().clearCase(n_case, true);
					}

					//TestCaseの更新。配下は新規として登録する。
					if (!Tool._db().saveTestCase(n_case, removings, t_cmds, t_objs, t_asserts)) {
						return -1;
					}
					Tool.alertMSG(null, Messages.getString("ObjectTestCreator.58")); //$NON-NLS-1$
				} catch (SQLException e) {
					Tool.alertMSG(null, "ERROR:failed to rollback. " + e.getMessage());
					return -1;
				}
				return 0;
			}
		}
		return -1;
	}


	//※JSの場合、パラメータ組に対して必要なJavascriptを合成してTestCommandを作る。（従ってJavaTestのようなsnapshotは不要）
	// $[0-9]! javascript式; ,\n
	// $[0-9_]? javascript式(boolean型) ,\n
	private static int makeCommandRecord(TestCommandRecord obj, int n, HashMap<String, Object> initMap, ArrayList<String> initsrcs, String strcmd, String mname, HashMap<String, ObjectRecord> objs) {
		String[] srclist = strcmd.split(",\n");

		// Javascriptの実行文のリスト(s_list)とコメント文
		ArrayList<String> xcmds1 = new ArrayList<String>();
		ArrayList<String> xcmds2 = new ArrayList<String>();

		String xstr;
		for (int i = 0; i < initsrcs.size(); i++) {
			xcmds1.add(initsrcs.get(i));
		}

		//※ $の前に?があるのはfakeのパターンである。
		final Pattern t_pat0 = Pattern.compile("^(\\$\\d+|GLOBAL)!([\\s\\S]+?)\\s*\\Z");  // _$[0-9]//?... //$NON-NLS-1$
		final Pattern t_pat1 = Pattern.compile("^(\\$?\\w+)\\?([\\s\\S]+?)\\s*\\Z");
		Matcher t_m;

		//$n!...
		for (int i = 0; i < srclist.length; i++) {
			t_m = t_pat0.matcher(srclist[i]);
			if (t_m.find()) {
				xstr = t_m.group(1);//$n
				if (xstr.equals("GLOBAL")) {
					xstr = "";//大域変数（$2で評価法が指定されている）の評価。
				} else {
					xstr = xstr.replace("$", "_xarg_");
				}
				xcmds1.add(xstr + t_m.group(2));//文末には;がある。
			}
		}
		String str_main = "_xarg__=";
		if (mname.startsWith(".")) {
			str_main += "_xarg_0";
		}
		str_main += mname + "(";
		for (int i = 1; i <= n; i++) {
			if (i != 1) {
				str_main += ",";
			}
			str_main += "_xarg_" + Integer.toString(i);
		}
		str_main += ");";
		xcmds1.add(str_main);

		int num_ast = 0;//返却値。成功すべき評価式の数。
		//$n?...
		for (int i = 0; i < srclist.length; i++) {
			t_m = t_pat1.matcher(srclist[i]);
			if (t_m.find()) {
				num_ast++;
				xstr = t_m.group(1);//$n
				if (xstr.equals("GLOBAL")) {
					xstr = "";//大域変数（$2で評価法が指定されている）の評価。
				} else {
					xstr = xstr.replace("$", "_xarg_");
				}
				if (t_m.group(2).startsWith("(function(")) {
					//関数実行型の評価式
					xstr = t_m.group(2);//$n
					xstr = xstr.replace("$", "_xarg_");
					xcmds2.add("if(!" + xstr + "){return false;}");//文末には;がある。
				} else {
					xcmds2.add("if(!" + xstr + t_m.group(2) + "){return false;}");//文末には;がある。
				}
			}
		}

		//引数は、HashMap<String, Object>とArrayList<String>, ArrayList<String>
		//$0
		ObjectRecord o_rec0 =  new ObjectRecord(2);
		o_rec0.id = Integer.toString(obj.id) + "$0";
		o_rec0.jSON = "{}"; //$NON-NLS-1$
		o_rec0.testID = obj.id;
		o_rec0.testCase = obj.getTestCase();
		o_rec0.isPOJO = true;
		o_rec0.stack = "$0";
		o_rec0.className = "com.ftinc.si.assist.test.js.JSTester";
		objs.put(o_rec0.id, o_rec0);

		//$1
		ObjectRecord o_rec1 =  new ObjectRecord(2);
		o_rec1.id = Integer.toString(obj.id) + "$1";
		o_rec1.jSON = Tool.getJSONfromObject(initMap); //$NON-NLS-1$
		o_rec1.testID = obj.id;
		o_rec1.testCase = obj.getTestCase();
		o_rec1.isPOJO = true;
		o_rec1.stack = "$1";
		o_rec1.className = "java.util.HashMap";
		objs.put(o_rec1.id, o_rec1);

		//$2
		ObjectRecord o_rec2 =  new ObjectRecord(2);
		o_rec2.id = Integer.toString(obj.id) + "$2";
		o_rec2.jSON = Tool.getJSONfromObject(xcmds1); //$NON-NLS-1$
		o_rec2.testID = obj.id;
		o_rec2.testCase = obj.getTestCase();
		o_rec2.isPOJO = true;
		o_rec2.stack = "$2";
		o_rec2.className = "java.util.ArrayList";
		objs.put(o_rec2.id, o_rec2);

		//$3
		ObjectRecord o_rec3 =  new ObjectRecord(2);
		o_rec3.id = Integer.toString(obj.id) + "$3";
		o_rec3.jSON = Tool.getJSONfromObject(xcmds2); //$NON-NLS-1$
		o_rec3.testID = obj.id;
		o_rec3.testCase = obj.getTestCase();
		o_rec3.isPOJO = true;
		o_rec3.stack = "$3";
		o_rec3.className = "java.util.ArrayList";
		objs.put(o_rec3.id, o_rec3);

		return num_ast;
	}
}
