package com.ftinc.si.assist.run;

import java.util.ArrayList;

import javassist.CannotCompileException;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;

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

//メモリ上で既存クラスの振舞を変更する。
//TestCommandエンジン上でのみ動作保証する。
//プロセス生成後に最初に読み込む。一度読んだクラスには効かない。
//主にstaticなクラスの挙動を変える。IntialContextなど環境系のクラスが対象。
public class CodeFairy {
	private static FakeMethodRecord cur_fr;
	private static ArrayList<String> s_done = new ArrayList<String>();
	
	//指定のクラスに対してDBに入っているメソッドを全て変更する。
	public static void prepare(String cname) throws NotFoundException, CannotCompileException {
		ArrayList<FakeMethodRecord> t_list = Tool._db().getFakeMethods(cname, Tool.version);
		String t_msg = ""; //$NON-NLS-1$
		try {
			CtClass t_c = null;
			for (FakeMethodRecord fr : t_list) {//全てのfrは同じクラスである。
				t_c = execPrepare(fr);
				if (s_done.contains(t_c.getName())) {
					//処理済み。Snapshotの場合などありうるので、そのまま終わる。
					return;
				}
			}
			if (t_c != null) {
				//toClassで失敗するということは、load済という可能性大。
				t_msg = Messages.getString("CodeFairy.1"); //$NON-NLS-1$
				Tool.toClass(t_c);
				s_done.add(t_c.getName());
			}
		} catch (CannotCompileException e) {
			throw new CannotCompileException(t_msg + "\n" + e.getMessage() + "(method =" + cur_fr.methodName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}
	}
	
	public static void prepare(FakeMethodRecord...infos) throws NotFoundException, CannotCompileException {
		String t_msg = ""; //$NON-NLS-1$
		try {
			CtClass t_c = null;
			for (FakeMethodRecord fr : infos) {//全てのfrは同じクラスである。
				if (t_c != null && !t_c.getName().equals(fr.className)) {
					//異なるクラスが混在していたら終わる。
					Tool.logForTesting(null, Messages.getString("CodeFairy.0")); //$NON-NLS-1$
				}
				t_c = execPrepare(fr);
				if (s_done.contains(t_c.getName())) {
					//処理済み。Snapshotの場合などありうるので、そのまま終わる。
					return;
				}
			}
			if (t_c != null) {
				//toClassで失敗するということは、load済という可能性大。
				t_msg = Messages.getString("CodeFairy.1"); //$NON-NLS-1$
				Tool.toClass(t_c);
				s_done.add(t_c.getName());
			}
		} catch (CannotCompileException e) {
			throw new CannotCompileException(t_msg + "\n" + e.getMessage() + "(method =" + cur_fr.methodName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}
		
	}
	
	private static CtClass execPrepare(FakeMethodRecord fr) throws NotFoundException, CannotCompileException {
		CtClass t_c = null;
		CtMethod t_b = null;
		
		cur_fr = fr;
		
		if (fr.className.equals(fr.methodName)) {
			throw new NotFoundException(Messages.getString("CodeFairy.5") + cur_fr.methodName + ")"); //$NON-NLS-1$ //$NON-NLS-2$
		} else {
			t_b = Tool.getCtMethod(t_c, fr.className, fr.methodName, fr.argTypes);
			t_c = t_b.getDeclaringClass();
			t_c.defrost();
			
			//継承メソッドかどうかのフラグ。overrideなら新規メソッドを作る。宣言済みメソッドなら上書き。
			boolean inherited = fr.className.equals(t_c.getName());
			
			//この段階で成功していれば、退避用にfr.methodName+"$"も作っている。
			String src = CodeProcessor.preProcessMain(null, null, fr.source);	
			if (src != null) {
				CtMethod t_m = CtNewMethod.make(src, t_c);
				t_m.setName(fr.methodName + "_2"); //$NON-NLS-1$
				t_c.addMethod(t_m);

				//String src = CodeProcessor.preProcessMain(fr.className, null, null, fr.source);	
				
				//既存のメソッドの名称変更と、代わりのメソッドをソースを元に実装する。abstractメソッドは不可。
//				if (Tool.makeSkinOfMethod(t_c, t_b, src, inherited) == null) {
				if (Tool.makeSkinOfMethod(t_c, t_b, fairySource(fr.className, fr.methodName), inherited, null) == null) {
					throw new NotFoundException(Messages.getString("CodeFairy.7")); //$NON-NLS-1$
				}
			} else {
				throw new CannotCompileException(Messages.getString("CodeFairy.3") + src); //$NON-NLS-1$
			}
		}
		return t_c;
	}
	
	// メソッドのソースを出力する。編集モードで実行する際に、ソースを編集できるようにした。
	private static String fairySource(String cname, String MethodName) {
		//終了かどうかは、Toolに任せる。リロードのためである。
		String s_delegate = "{"; //$NON-NLS-1$

		//Fakeの編集不要ならFalseが返り続行する。999で終了した場合、再度プロセスを立ち上げて実行する。
		s_delegate +="com.ftinc.si.assist.test.Tool#pushedFake(\""  //$NON-NLS-1$
				+ cname + "\",\"" + MethodName + "\", $type, $0, $args);"; //$NON-NLS-1$ //$NON-NLS-2$
		
		//fakeにimpleへの参照リンクがある。「_impl」である。
		s_delegate += "if ($0!=null){$_=$0." + MethodName + "_2($$);}else{$_=" + cname + "." +  MethodName + "_2($$);}"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
		s_delegate += "}"; //$NON-NLS-1$
		return s_delegate;
	}

}
