package com.ftinc.si.assist.test;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map.Entry;

import com.ftinc.si.assist.run.Messages;
import com.ftinc.si.assist.run.VCentral;
import com.ftinc.si.assist.test.Fson.NotSupportedClassException;

import javassist.CannotCompileException;
import javassist.NotFoundException;

public class TestCommandRecord extends Record implements TestCommandIF {
	public TestCommandRecord() {
		super(0);
	}

	public TestCommandRecord(int i) {
		super(i);
	}

	public int id;  // 自動採番する。DBからではなくツールで。
	public String subject; //$0のこと。$0もしくはTestID(過去)$n。後者はObjectRecordに合わせた。
	public String className;
	public String methodName;// [static ] + methodName + [ varPos>=0]　　$1が可変引数ならvarPos=0
	public String returnType;
	public String testCase;
	public String[][] argTypes;   //$1,...$nのクラス名とstackの対。[[ClassName,[0-9]+$[0-9_]],[ClassName,$[0-9]+,...]  JSONである。
	public String argStatus;	//S0,S1,..,Assertの編集状態を文字列化。0は未完、1は完成。

	//DBの結果を読み込む
	public void read(ResultSet r) throws SQLException {
		testCase = r.getString("TestCase"); //$NON-NLS-1$
		id = r.getInt("ID"); //$NON-NLS-1$
		className = r.getString("ClassName"); //$NON-NLS-1$
		methodName = r.getString("MethodName"); //$NON-NLS-1$
		returnType = r.getString("ReturnType"); //$NON-NLS-1$
		subject = r.getString("Subject"); //$NON-NLS-1$
		setArgTypes(r.getString("ArgTypes")); //$NON-NLS-1$
		enable = r.getBoolean("Enable"); //$NON-NLS-1$
		argStatus = r.getString("ArgStatus"); //$NON-NLS-1$
	}


	// 引数は新ID
	public TestCommandRecord _dup(int nID) {
		TestCommandRecord t_rec = new TestCommandRecord(2);
		t_rec.id = nID;
		t_rec.subject = subject;
		t_rec.className = className;
		t_rec.methodName = methodName;
		t_rec.returnType = returnType;
		t_rec.testCase = testCase;
		t_rec.argTypes = argTypes.clone();
		t_rec.enable = enable;
		t_rec.argStatus = argStatus;
		return t_rec;
	}

	//静的メソッドかどうか
	public boolean isStatic() {
		if (methodName.startsWith("static")) { //$NON-NLS-1$
			return true;
		} else if (methodName.indexOf("$replay") >= 0) {
			//スナップショットを再生するコマンドなら元のスナップショットに委譲する。
			String t_id = methodName.replaceFirst("^\\$replay\\(\\s*(\\d+)\\s*\\).*$", "$1");
			TestCommandRecord t_rec = Tool._db().getTestCmd(t_id);
			if (t_rec != null) {
				return t_rec.isStatic();
			}
		}
		return false;
	}

	//可変長引数の位置。負なら可変長ではない。$1は0
	public int varArgsPos() {
		if (methodName.matches("^.*\\s([0-9]+)$")) { //$NON-NLS-1$
			String s = methodName.replaceFirst("^.*\\s([0-9]+)$", "$1"); //$NON-NLS-1$ //$NON-NLS-2$
			return Integer.valueOf(s);
		}
		return -1;
	}


	//引数の数を返す
	public int numArgs() {
		if (argTypes == null) {
			return 0;
		}
		return argTypes.length;
	}

	//i番目の引数のクラス名を返す。１番目の引数の場合、i=0
	public String getArgClassByName(String name) {
		if ("$_".equals(name)) { //$NON-NLS-1$
			return returnType;
		} else if ("$0".equals(name)) { //$NON-NLS-1$
			return className;
		} else if (name.matches("^\\$[0-9]+$")) { //$NON-NLS-1$
			String num = name.substring(1);
			//$1は位置が0
			return getArgClass(Integer.valueOf(num) - 1);
		}
		return null;
	}


	//i番目の引数のクラス名を返す。１番目の引数の場合、i=0
	public String getArgClass(int i) {
		if (numArgs() > 0) {
			return argTypes[i][0];
		}
		return ""; //$NON-NLS-1$
	}

	//i番目の引数のstackを返す。１番目の引数の場合、i=0
	public String getArgStack(int i) {
		if (numArgs() > 0) {
			return argTypes[i][1];
		}
		return ""; //$NON-NLS-1$
	}

	//DB格納のためにJSONにする。
	private String getArgJSON() {
		if (argTypes != null) {
			return Tool.getJSONfromObject(argTypes);
		}
		return ""; //$NON-NLS-1$
	}

	//n番目の引数情報を置き換える。
	public void setArgType(int n, String cname, String stack) {
		if (numArgs() > 0) {
			argTypes[n][0] = cname;
			argTypes[n][1] = stack;
			changed();
		}
	}


	//argTypesの初期化
	public void setArgTypes(String json) {
		if (json == null || json.length() == 0) {
			argTypes = new String[0][];
		} else {
			argTypes = (String[][]) Tool.getObjectfromJSON(String[][].class, json);
		}
	}

	//可変長引数の場合の追加
	public void addArg(String cname, String stack) {
		ArrayList<String[]> t_list = new ArrayList<String[]>();
		for (int i = 0; i < numArgs(); i++) {
			t_list.add(argTypes[i]);
		}
		t_list.add(new String[] {cname, stack});
		argTypes = t_list.toArray(new String[t_list.size()][]);
	}

	//最後を削除する。
	public void removeLast() {
		ArrayList<String[]> t_list = new ArrayList<String[]>();
		for (int i = 0; i < numArgs() - 1; i++) {
			t_list.add(argTypes[i]);
		}
		argTypes = t_list.toArray(new String[t_list.size()][]);
	}

	//参照しているtestIDが再番された時に呼ばれる
	public void relink(String oid, String nid) {
		if (subject != null && subject.startsWith(oid)) {
			subject = subject.replaceFirst("[0-9]+(\\$[0-9_]+)", nid + "$1"); //$NON-NLS-1$ //$NON-NLS-2$
			changed();
		}
		for (int i = 0; i < numArgs(); i++) {
			String t_stack = argTypes[i][1];
			if (t_stack.startsWith(oid)) {
				argTypes[i][1] = t_stack.replaceFirst("[0-9]+(\\$[0-9_]+)", nid + "$1"); //$NON-NLS-1$ //$NON-NLS-2$
				changed();
			}
		}
	}


	public boolean isComplete() {
		Boolean[] blist = (Boolean[]) Tool.getObjectfromJSON(Boolean[].class, argStatus);

		if (Arrays.asList(blist).contains(false)) {
			return false;
		}
		return true;
	}


	public String getUpdateSQL() {
		if (removed && isNew()) {
			return null;//DBに未登録のまま削除
		}
		if (removed) {
			return "delete from \"tbl_TestCommandRecord\" where \"ID\"='" + Integer.toString(id) + "'"; //$NON-NLS-1$ //$NON-NLS-2$
		}
		if (isNew()) {
			return "insert into \"tbl_TestCommandRecord\" (\"ID\", \"ClassName\", \"MethodName\", \"ReturnType\", \"Subject\"," //$NON-NLS-1$
					+ " \"ArgTypes\", \"TestCase\", \"Enable\", \"ArgStatus\") " //$NON-NLS-1$
					+ "values (" + Integer.toString(id) + ",'" + className + "','" + methodName + "','" + returnType  //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
					+ "','" + subject + "','" + getArgJSON() + "','" + testCase + "','" + Boolean.toString(enable)  //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
					+ "','" + argStatus + "')" ; //$NON-NLS-1$ //$NON-NLS-2$
		} else if (isUpdated()) {
			return "update \"tbl_TestCommandRecord\" set \"ClassName\"='" + className + "', \"MethodName\"='" + methodName  //$NON-NLS-1$ //$NON-NLS-2$
					+ "', \"ReturnType\"='" + returnType + "', \"Subject\"='" + subject + "', \"ArgTypes\"='" + getArgJSON()+ "', \"ArgStatus\"='" + argStatus //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
					+ "', \"TestCase\"='" + testCase + "', \"Enable\"='" + Boolean.toString(enable)   //$NON-NLS-1$ //$NON-NLS-2$
					+ "' where \"ID\"='" + Integer.toString(id) + "'"; //$NON-NLS-1$ //$NON-NLS-2$
		}
		return null;
	}

	public String toString() {
		return "ID=" + Integer.toString(id) + "\t" + "Class=" + className + "\t" + "Method=" + methodName + "\n"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
	}

	public static ArrayList<TestCommandRecord> convertToObject(ArrayList<TestCommandRecord> cmdlist, int snapshot) {
		if (cmdlist == null) return null;

		ArrayList<TestCommandRecord> t_list = new  ArrayList<TestCommandRecord>();
		for (int i = 0; i < cmdlist.size(); i++) {
			TestCommandRecord t_rec = cmdlist.get(i);
			t_list.add(t_rec);
		}

		return t_list;
	}

	@Override
	public int getId() {
		return id;
	}

	@Override
	public String getTestCase() {
		return testCase;
	}

	@Override
	public String getSubject() {
		return subject;
	}

	@Override
	public void setSubject(String s) {
		subject = s;
	}

	@Override
	public String getClassName() {
		return className;
	}

	@Override
	public void setClassName(String s) {
		className = s;
	}

	@Override
	public String getMethodName() {
		String m = methodName;
		m = m.replaceFirst("static+\\s*", ""); //$NON-NLS-1$ //$NON-NLS-2$
		m = m.replaceFirst("\\s*[0-9]+", ""); //$NON-NLS-1$ //$NON-NLS-2$
		return m;
	}

	@Override
	public void setMethod(String m, boolean isStatic, int varAPos) {
		methodName = m;
		if (isStatic) {
			methodName = "static " + methodName; //$NON-NLS-1$
		}
		if (varAPos >= 0) {//$1は0．
			methodName += " " + Integer.toString(varAPos); //$NON-NLS-1$
		}
	}

	@Override
	public String getReturnType() {
		return returnType;
	}

	@Override
	public void setReturnType(String s) {
		returnType = s;
	}

	@Override
	public String[][] getArgTypes() {
		return argTypes;
	}

	@Override
	public void setArgTypes(String[][] s) {
		argTypes = s;
	}

	@Override
	public boolean getArgStatus(int n) {
		Boolean[] t_s;
		try {
			t_s = (Boolean[])Fson.fromJson(argStatus, Tool.forName("Boolean[]")); //$NON-NLS-1$
			return t_s[n];
		} catch (Exception e) {
			Tool.alertMSG(null, e.getMessage() + " @TestCommandRecord#getArgStatus"); //$NON-NLS-1$
		}
		return false;
	}

	@Override
	public void setArgStatus(int n, boolean b) {

		Boolean[] t_s;
		try {
			if (argStatus == null) {
				int m = argTypes.length + 2; //$0,Assertを加算
				Boolean[] t_status = new Boolean[m];
				for (int i = 0; i < m; i++) {
					t_status[i] = false;
				}
				argStatus = Fson.toJson(t_status, Boolean[].class);
			}

			t_s = (Boolean[])Fson.fromJson(argStatus, Boolean[].class);
			t_s[n] = b;
			argStatus = Fson.toJson(t_s, Boolean[].class);

		} catch (Exception e) {
			Tool.alertMSG(null, e.getMessage() + " @TestCommandRecord#setArgStatus"); //$NON-NLS-1$
		}
	}


	//////////////snapshotのため、引数のインスタンス生成をここで行う。
	public Object getTObject(Integer i) throws SecurityException, IllegalArgumentException, ClassNotFoundException,
			InstantiationException, IllegalAccessException, NoSuchFieldException, CannotCompileException, IOException,
			NotFoundException, NoSuchMethodException, InvocationTargetException, ParseException, NotSupportedClassException {

		Object t_obj = null;
		if (i == 0) {
			t_obj = Tool.getTObject(id, subject);
		} else {
			t_obj = Tool.getTObject(id, getArgStack(i - 1));
		}
		return t_obj;
	}

	//今のところ、VCentralでverifyを指定した時のみ対応する。
	public boolean isAutomatic() {
		@SuppressWarnings("unchecked")
		HashMap<String, String> t_map = (HashMap<String, String>)VCentral.getValue("_POST_EXECS"); //$NON-NLS-1$
		if (t_map != null) {
			for (Entry<String, String> entry: t_map.entrySet()) {
				String val =entry.getValue();
				if (val.startsWith("verify")) {
					return false;
				}
			}
		}
		return true;
	}


	public AssertRecord getAssert() throws ProcessException {
		//外部プロセスが規定されていたら実行する。
		postExec();

		@SuppressWarnings("unchecked")
		HashMap<String, String> t_map = (HashMap<String, String>)VCentral.getValue("_ASSERTABLES"); //$NON-NLS-1$
			//t_mapはTestCommand実行のたびに初期化されるので、このAssert専用となっている。。

		if (t_map == null) {
			return Tool._db().getAssert(id);
		}

		//VCentralに判定条件が設定されている場合、Javassistは不要であり、定型的な処理になる。
		return new AssertMacroRecord(id, t_map);
	}

	//事前実行することがあれば行う。snapshotなど
	public void preExec() {
	}

	//InstanceTableに登録する際に、キーを返す。
	public String getInstanceID() {
		return Integer.toString(id);
	}

	//テスト実行後に設定したコマンドを実行する。
	protected void postExec() throws ProcessException {
		@SuppressWarnings("unchecked")
		HashMap<String, String> t_map = (HashMap<String, String>)VCentral.getValue("_POST_EXECS"); //$NON-NLS-1$
		if (t_map != null) {
			for(Entry<String, String> entry : t_map.entrySet()) {
				@SuppressWarnings("unchecked")
				ArrayList<String> t_list = (ArrayList<String>)Tool.getObjectfromJSON(ArrayList.class, entry.getValue());
				int t_res = Tool.executeCommand(new StringBuilder(), entry.getKey(), t_list.toArray(new String[t_list.size()]));
				if (t_res > 0) {
					//何かおかしい。
					throw new ProcessException(entry.getKey() + "failed. ERRORCODE=" + Integer.toString(t_res)); //$NON-NLS-1$
				}
			}
		}
	}

	//独自の例外クラス
	public class ProcessException extends Exception {
		public ProcessException(String msg) {
			super(msg);
		}
	}

	//通常のTestCommandのためのAssert派生クラス：DBの変化がある場合、上位クラスのAssertを拡張する。
	//VBAでテストコマンドを作成する場合、VCentralに判定条件が設定するので、Javaのコードは不要。
	class AssertMacroRecord extends AssertRecord {
		private HashMap<String, String> assertable_map;

		public AssertMacroRecord(int n, HashMap<String, String> map) {
			super(0);
			id = n;
			assertable_map = map;
			targetObj = "_ASSERTABLES";//nullを回避する。nullならAssertの最初の判定で失敗する。 //$NON-NLS-1$
		}

		//Assertのレコードがすべて入力されたかどうか。
		public boolean isCompleted() {
			return true;
		}

		protected ArrayList<Object> getResultString(TestCommandRecord curCmd) throws SecurityException,
				IllegalArgumentException, CannotCompileException, InstantiationException, IllegalAccessException, ClassNotFoundException,
				NotFoundException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException, ParseException {

			ArrayList<Object> t_result = new ArrayList<Object>();
			ArrayList<String> t_regs = new ArrayList<String>();

			//dbtest/objtestがある場合。判定の正規表現はgetValue()、値取得はgetKey()。
			//カラム指定が*でなければ、指定カラムの値の文字列表現がt_resultに追加される。
			//※カラム指定は、getKey()の最後尾である。
			for (Entry<String, String> entry: assertable_map.entrySet()) {
				if (entry.getKey().startsWith("SQL_") && entry.getValue().toLowerCase().matches("^select\\s[\\s\\S]*$")) {
					//Select文の場合、存在（true）すれば成功。
					t_regs.add("true");
				} else {
					t_regs.add(entry.getValue());
				}
				Object t_str = VCentral.getValue(entry.getKey());
				t_result.add(t_str);
			}
			criteriaRex = Tool.getJSONfromObject(t_regs);

			if (t_result.size() == 0) {
				throw new NotFoundException(Messages.getString("TestCommandRecord.70")); //$NON-NLS-1$
			}

			return t_result;
		}

	}

	@Override
	public String getName4Log() {
		return getMethodName();
	}

}