package com.ftinc.si.assist.test;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;

import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.NotFoundException;

//CtClassをEditor系に見せないようにしたい。javassist依存性を減らしたい。
//しかし使いたい。pathがなくてもclassが読めるため。
public class ClassOutLine {
	private CtClass m_class;
	private boolean is_fake;
	
	public CtClass getCtClass() {
		return m_class;
	}
	
	public ClassOutLine(CtClass ctc, boolean isfake) {
		m_class =ctc;		
		is_fake = isfake;
	}
	
	public ClassOutLine getSuper() {
		if ("java.lang.Object".equals(m_class.getName())) return null;
		
		CtClass t_class;
		try {
			t_class = m_class.getSuperclass();
			if (t_class != null) {
				return new ClassOutLine(t_class, is_fake);
			}
		} catch (NotFoundException e) {
			Tool.logIfDebug(e, "@ClassOutLine#getSuper");
		}
		return null;
	}

	public ArrayList<ClassOutLine> getInterfaces() {
		ArrayList<ClassOutLine> t_list = new ArrayList<ClassOutLine>();
		CtClass[] t_classes;
		try {
			t_classes = m_class.getInterfaces();
			for (int i = 0; i < t_classes.length; i++) {
				ClassOutLine t_co = new ClassOutLine(t_classes[i], false);
				t_list.add(t_co);
				t_list.addAll(t_co.getInterfaces());
			}
			return t_list;
		} catch (NotFoundException e) {
			Tool.logIfDebug(e, "@ClassOutLine#getInterface");
		}
		return null;
	}

	
	public String getClassName() {
		if (m_class == null) return null;
		
		return m_class.getName();
	}
	
	public boolean isInterface() {
		return m_class.isInterface();
	}
	
	//publicメソッドの名前と型のマップを作る。ClassReadDialogから呼ばれる。
	public HashMap<String, String> getDeclaredMethods()  {
		CtMethod[] t_m = m_class.getDeclaredMethods();
		HashMap<String, String> t_map = new HashMap<String, String>();
		
		for (Integer i = 0; i < t_m.length; i++) {
			String num = String.format("%1$03d", i);//3桁の数字で先頭0埋め //$NON-NLS-1$
			String name_key = num + "_name";
			
			String mName = t_m[i].getName();
			String head = "";
			
			//メソッド名の登録
			if (Modifier.isStatic(t_m[i].getModifiers())) {
				//匿名クラスの静的なメソッドは除外する。
				if (!mName.matches("^access\\$[0-9]+$")) {
					//staticの場合、「static 」を先頭に追加。
					head = "static";
				}
			}
			if (Modifier.isFinal(t_m[i].getModifiers())) {
				if (head.length() > 0) {
					head = "/" + head;
				}
				head = "final" + head;
			}
			if (Modifier.isAbstract(t_m[i].getModifiers())) {
				if (head.length() > 0) {
					head = "/" + head;
				}
				head = "abstract" + head;
			}
			mName = head + " " + mName;//staticやfinalの修飾子を付ける。
			
			String arg_key = num + "_$_";
			try {
				//返却値の型
				t_map.put(arg_key, t_m[i].getReturnType().getName());
			} catch (NotFoundException e) {
				Tool.alertMSG(null, e.getMessage() + " failed in getMethods.(NotFound)");
			}
			
			CtClass[] t_args;
			try {
				//引数の型を登録する。
				t_args = t_m[i].getParameterTypes();
				if (t_args.length > 0) {
					for (Integer j = 1; j < t_args.length; j++) {
						arg_key = num + "_$" + j.toString();
						t_map.put(arg_key, t_args[j - 1].getName());
					}
					arg_key = num + "_$" + Integer.toString(t_args.length);//$nは１から始まる。

					CtClass t_last = t_args[t_args.length - 1];
					String cname = t_last.getName();
					if (((javassist.Modifier.VARARGS & t_m[i].getModifiers()) != 0) && t_last.isArray()) {
						//可変長引数として扱う。
						//①配列の要素のクラス名を使う
						cname = cname.replaceFirst("\\[\\]", "");

						//②メソッド名に可変長引数の位置を記録する
						mName += " " + Integer.toString(t_args.length - 1);//引数配列は０から始まる。
					}
					t_map.put(arg_key, cname);
				}
			} catch (NotFoundException e) {
				Tool.alertMSG(null, e.getMessage() + " failed in getMethods.(NotFound)");
			}
			//メソッドのアノテーション
			String t_ant = Tool.readAnnotationFromMethod(t_m[i], "updated");
			if (t_ant.length() > 0) {
				t_map.put(num + "_updated", t_ant);
			}
			
			t_map.put(name_key, mName);

		}
		return t_map;
	}

	public HashMap<String, String> getDeclaredConstructors()  {
		CtConstructor[] t_m = m_class.getDeclaredConstructors();
		HashMap<String, String> t_map = new HashMap<String, String>();
		
		for (Integer i = 0; i < t_m.length; i++) {
		
			String num = String.format("%1$03d", i);//3桁の数字で先頭0埋め //$NON-NLS-1$

			String t_key = num + "_name";
			t_map.put(t_key, "constructor_" + num);

			CtClass[] t_args;
			String arg_key;

			try {
				//引数の型を登録する。
				t_args = t_m[i].getParameterTypes();
				
				if (t_args.length > 0) {
					for (Integer j = 1; j < t_args.length; j++) {
						arg_key = num + "_$" + j.toString();
						t_map.put(arg_key, t_args[j - 1].getName());
					}
					arg_key = num + "_$" + Integer.toString(t_args.length);//$nは１から始まる。

					CtClass t_last = t_args[t_args.length - 1];
					String cname = t_last.getName();
					if (t_last.isArray() && t_m[i].getModifiers() == javassist.Modifier.VARARGS) {
						//可変長引数として扱う。
						//①配列の要素のクラス名を使う
						cname = cname.replaceFirst("\\[\\]", "");

						//②番号と合わせて、可変長引数の位置を記録する。$1は0．
						t_map.put(num + "_varArgPos", Integer.toString(t_args.length - 1));
					}
					t_map.put(arg_key, cname);
				}
			} catch (NotFoundException e) {
				Tool.alertMSG(null, e.getMessage() + " failed in getMethods.(NotFound)");
			}
		}
		return t_map;
	}

	//このクラスで保持している全てのフィールドの名前と型名をMapとして返す。
	public HashMap<String, String> getFields(FakeMethodRecord frec)  {
		if (getSuper() == null) {
			return getDeclaredFields(frec);
		}
		HashMap<String, String> t_m = getDeclaredFields(frec);

		t_m.putAll(getSuper().getFields(frec));
		
		return t_m;
	}
	
	//このクラスで宣言されているフィールドの名前と型名をMapとして返す。
	//引数が指定されている場合、このクラスはFake系である。編集中のFakeメソッドが引数。そのメソッドを除外した仮想フィールドをMapに加える。
	public HashMap<String, String> getDeclaredFields(FakeMethodRecord frec)  {
		HashMap<String, String> t_map =  new HashMap<String, String>();
		CtField[] t_m = m_class.getDeclaredFields();
		for (int i = 0; i < t_m.length; i++) {
			try {
				if (!Modifier.isStatic(t_m[i].getModifiers()) && !Modifier.isFinal(t_m[i].getModifiers())) {
					//インスタンスが対象のリフレクションで使うために、finalでもstaticでもないこと。
					t_map.put(t_m[i].getName(), t_m[i].getType().getName());//メンバ変数名, クラス名
				}
			} catch (NotFoundException e) {
				Tool.alertMSG(null, "The type of " + t_m[i].getName() + " not found.");
			}
		}
		//fakeの場合、仮想メンバ変数がある。
		if (is_fake) {
			ArrayList<FakeMethodRecord> frecs = Tool._db().getFakeMethods(m_class.getName(), Tool.version);
			for (int k = 0; k < frecs.size(); k++) {
				//Fakeメソッドが指定されているならば、それは編集中であるので、除外する。
				if (!frecs.get(k).equals(frec)) {
					String[][] t_list = frecs.get(k).fields;
					if (t_list != null) {
						for (int j = 0; j < t_list.length; j++) {
							if (t_list[j] == null) {
								break;
							} else if (!t_map.containsValue(t_list[j][0])) {
								//同じメンバ変数名がないならば登録する。
								t_map.put(t_list[j][0], t_list[j][1]);
							} else if (!t_map.get(t_list[j][0]).equals(t_list[j][1])) {
								//名前は登録済みだが、クラス名が不整合
								return null;
							}
						}
					}
				}
			}
		}
		
		return t_map;
	}


	//仮想フィールドをも対象にする
	public Class<?> getFieldType(String fname) {
		try {
			CtField tfld = m_class.getField(fname);
			return Tool.forName(tfld.getType().getName());
		} catch (NotFoundException e) {
			//仮想フィールドの可能性あり。
			if (is_fake) {
				ArrayList<FakeMethodRecord> frecs = Tool._db().getFakeMethods(m_class.getName(), Tool.version);
				for (int k = 0; k < frecs.size(); k++) {
					String t_type = frecs.get(k).getFieldType(fname);
					if (t_type != null && t_type.length() > 0) {
						try {
							return Tool.forName(t_type);
						} catch (ClassNotFoundException e1) {
							return null;
						}
					}
				}
			}
			ClassOutLine t_s = getSuper();
			if (t_s != null) {
				if (!t_s.getClassName().equals("java.lang.Object")) {
					return t_s.getFieldType(fname);
				}
			}
		} catch (ClassNotFoundException e) {
			Tool.logIfDebug(e, "@ClassOutLine#getFieldType");
		}
		return null;
	}

}
