package com.ftinc.si.assist.run;

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

import com.ftinc.si.assist.test.AssertRecord;
import com.ftinc.si.assist.test.Fson;
import com.ftinc.si.assist.test.Fson.NotSupportedClassException;
import com.ftinc.si.assist.test.ObjectRecord;
import com.ftinc.si.assist.test.TestCaseRecord;
import com.ftinc.si.assist.test.TestCommandIF;
import com.ftinc.si.assist.test.TestCommandRecord;
import com.ftinc.si.assist.test.Tool;

import javassist.CannotCompileException;
import javassist.NotFoundException;

//snapshotを再利用し、必要な情報を更新してから実行する。
public class SnapshotCommandRecord extends TestCommandRecord {
	//snapshotのjsonを格納
	protected static Integer s_id_snapshot;

	public static String snapshotID() {
		if (s_id_snapshot != null) {
			return s_id_snapshot.toString();
		}
		return "0";
	}

//	@SuppressWarnings("unchecked")
	public SnapshotCommandRecord() {
		super(0);
	}

	public void _cpAttribute(TestCommandIF a) {
		id = a.getId();

		String t_id = "0";
		if (s_id_snapshot != null) {
			t_id = s_id_snapshot.toString();
		}

		TestCommandRecord rec = Tool._db().getTestCmd(t_id);

		testCase = rec.testCase;
		className = rec.className;
		methodName = rec.methodName;
		returnType = rec.returnType;
		subject = rec.subject;
		argTypes = rec.argTypes;
		enable = true;
		argStatus = rec.argStatus;
	}

	//snapshotのため、引数のインスタンス生成をここで行う。
	//※Fakeの場合もOK。スナップショットしたインスタンスからクラスを切り出し、fromJsonでインスタンスを作っている。
	public Object getTObject(Integer i) throws SecurityException, IllegalArgumentException, ClassNotFoundException,
			InstantiationException, IllegalAccessException, NoSuchFieldException, CannotCompileException, IOException,
			NotFoundException, NoSuchMethodException, InvocationTargetException, ParseException, NotSupportedClassException {

		//DBに格納されている更新用引数情報（JSON）を取得する。IDがsnapshotより新しい。
		ObjectRecord t_rec = new ObjectRecord(0);
		t_rec.id = Integer.toString(id) + "$" + i.toString(); //$NON-NLS-1$
		t_rec = Tool._db().getObjectRecord(t_rec);

		//TestCommandRecord.preExec()により、snapshotまでの処理および状態が再現されている。
		String t_key = s_id_snapshot.toString() + "$" + i.toString(); //$NON-NLS-1$
		Object snap_obj = Tool.getInstance(t_key);

		Object t_obj = snap_obj;
		if (snap_obj != null) {
			//snapshotにおける、該当引数のインスタンスがnullではない場合。
			Class<?> t_c = snap_obj.getClass();

			//FakeのJsonPathの末端で生成するソースを与える時に参照する可能性あり。
			Tool.addInstance(Integer.toString(id) + "$" + i.toString(), t_obj);

			if (t_rec != null && t_rec.jSON != null && t_rec.jSON.length() > 0) {
				//必要な属性だけ上書きする。
				if (Tool.isPrimitive2(t_c) && !t_c.isArray()) {
					//配列以外のprimitive（FTでの区分はコンストラクタを作りようがない）の場合、置き換える。
					t_obj = Fson.fromJson(t_rec.jSON, t_c);
				} else {
					//更新

					//通常の引数に大域マップ更新情報が入っている場合の処理。大域マップ属性（true指定はこのため）のみ更新する。
					HashMap<String, Object> _map2 = Tool.getCommentMapFromJson(true,t_rec.jSON);
					if (_map2.size() > 0) {
						//対象の属性があれば更新する。
						VCentral.prepare(_map2);
					}

					//更新
					if (t_obj == null) {
						//値が設定されていないなら作る。
						t_obj = Fson.fromJson(t_rec.jSON, null);
					} else {
						Fson.updateFromJson(t_obj, t_rec.jSON);
					}

					//変数の属性(false指定はこのため)を、ピンポイントで更新する。
					HashMap<String, Object> _map3 = Tool.getCommentMapFromJson(false, t_rec.jSON);
					for (Entry<String, Object> entry: _map3.entrySet()) {
						Fson.updateByJsonPath(t_obj, entry.getKey(), entry.getValue().toString());
					}
				}
			}
		} else if (t_rec == null) {
			//何もしない。nullが返るはず。
		} else if (t_rec.jSON != null && t_rec.jSON.length() > 0) {
			//snapshotコマンドにおける該当引数がnullである場合、必要ならば、ここで新たに作る。
			Class<?> t_c = Tool.forName(t_rec.className);
			if (Tool.isPrimitive2(t_c)) {
				//primitiveの場合、置き換える。
				t_obj = Fson.fromJson(t_rec.jSON, t_c);
			} else {
				//更新
				if (!t_rec.isPOJO()) { //$NON-NLS-1$
					//fakeの場合、引数インスタンスがnullの場合は考えなくてよい。環境が与えるものであるから。
					//nullが入ってくるようなことは、異常な環境の作りである!!!!!
				} else if (!isStatic()) {
					t_obj = Fson.fromJson(t_rec.jSON, null);
				}

				//通常の引数に大域マップ更新情報が入っている場合の処理。大域マップ属性のみ更新する。
				HashMap<String, Object> _map2 = Tool.getCommentMapFromJson(true, t_rec.jSON);
				if (_map2.size() > 0) {
					//対象の属性があれば更新する。
					VCentral.prepare(_map2);
				}
			}
		}

		//Fakeの場合、Singletonなのでinstance_tableのインスタンスを置き換える。
		if (t_rec != null && !t_rec.isPOJO()) {
			Tool.addInstance(t_rec.className, t_obj);
		}
		return t_obj;
	}


	//DBに入っていれば、それで構成する。なければ、Snapshotの元のAssertを構成し、IDを変更する。
	public AssertRecord getAssert() {
		try {
			postExec();
		} catch (ProcessException e) {
			Tool.logForTesting(e, "@SnapshotCommandRecord#getAssert.");
			return null;
		}

		AssertRecord t_ast = Tool._db().getAssert(id);
		if (t_ast == null) {
			//外部プロセスが規定されていたら実行する。

			SnapshotAssertRecord t_rec = new SnapshotAssertRecord(0);
			t_rec.id = id;
			t_rec.targetObj = "_ASSERTABLES";//nullを回避するため適当な文字列を入れる。Assertの最初の判定を通過するために必要。 //$NON-NLS-1$
			t_rec.snapshot_id = s_id_snapshot; //Inspector再利用のための布石
			return t_rec;
		}

		return t_ast;
	}


	//事前実行することがあれば行う。snapshotなど
	//外部コマンド起動はVCentralで直接行うため、ここでは行わない。
	public void preExec() {
		if (id > s_id_snapshot + 1) {
			//再構築が必要なのは、s_id_snapshot + 2から。
			VCentral.clear();
			ArrayList<TestCaseRecord> t_list = Tool._db().getTestCaseList(testCase, 0, 0, "", Tool.version); //$NON-NLS-1$

			//snapshotがあれば、その直前まで実行する。第二引数はログを出力しない設定。
			TestCaseRecord t_case = t_list.get(0);
			TestCommander tester = new TestCommander(t_case, false);

			//id_snapshotがnullでないのは、preExecからの呼び出しのみ。
			tester.execTestCase(s_id_snapshot);
		}
	}

	//Web用のAssert派生クラス
	class SnapshotAssertRecord extends AssertRecord {
		private ArrayList<String> m_asserts;

		public SnapshotAssertRecord(int i) {
			super(i);
			m_asserts = new ArrayList<String>();
		}

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

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

			if (targetObj != null && targetObj.length() > 0 && !targetObj.toString().equals("_ASSERTABLES")) {
				return super.getResultString(curCmd);
			}

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

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

			if (t_map != null) {
				//dbtestやobjtestがある場合。判定の正規表現はgetValue()、値取得はgetKey()。
				//カラム指定が*でなければ、指定カラムの値の文字列表現がt_resultに追加される。
				//※カラム指定は、getKey()の最後尾である。
				for (Entry<String, String> entry: t_map.entrySet()) {
					Object t_obj = VCentral.getValue(entry.getKey());
					if (t_obj != null) {
						t_result.add(t_obj.toString());
					} else {
						t_result.add("$null");
					}

					//比較演算子の前には$を付ける。さもないと、普通の正規表現になる。
					String t_str = entry.getValue();
					m_asserts.add(t_str);
				}
			}
			if (criteriaRex == null || criteriaRex.length()==0 || criteriaRex.equals("[]")) {
				criteriaRex = Tool.getJSONfromObject(m_asserts);
			}

			if ((!"[]".equals(criteriaRex) && t_result.size() > 0) || ("[]".equals(criteriaRex) && t_result.size() == 0)) {
				return t_result;
			}
			throw new NotFoundException(Messages.getString("WebTestCommandRecord.4")); //$NON-NLS-1$
		}
	}
}
