package aQute.bnd.compatibility;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;

public class RuntimeSignatureBuilder {
	final Scope root;

	public RuntimeSignatureBuilder(Scope root) {
		this.root = root;
	}

	static public String identity(Class< ? > c) {
		return Scope.classIdentity(c.getName());
	}

	static public String identity(Method m) {
		return Scope.methodIdentity(m.getName(), getDescriptor(m.getReturnType(), m.getParameterTypes()));
	}

	static public String identity(Constructor< ? > m) {
		return Scope.constructorIdentity(getDescriptor(void.class, m.getParameterTypes()));
	}

	static public String identity(Field m) {
		return Scope.fieldIdentity(m.getName(), getDescriptor(m.getType(), null));
	}

	static public String getDescriptor(Class< ? > base, Class< ? >[] parameters) {
		StringBuilder sb = new StringBuilder();
		if (parameters != null) {
			sb.append("(");
			for (Class< ? > parameter : parameters) {
				sb.append(getDescriptor(parameter));
			}
			sb.append(")");
		}
		sb.append(getDescriptor(base));
		return sb.toString();
	}

	public Scope add(Class< ? > c) {
		Scope local = add(root, getEnclosingScope(c), c.getModifiers(), c.getTypeParameters(), Kind.CLASS, identity(c),
				c.getGenericSuperclass(), c.getGenericInterfaces(), null);

		for (Field f : c.getDeclaredFields()) {
			add(local, // declaring scope
					local, // enclosing
					f.getModifiers(), // access modifiers
					null, // fields have no type vars
					Kind.FIELD, // field
					identity(f), // the name of the field
					f.getGenericType(), // the type of the field
					null, // fields have no parameters
					null // fields have no exceptions
			);
		}

		for (Constructor< ? > constr : c.getConstructors()) {
			add(local, // class scope
					local, // enclosing
					constr.getModifiers(), // access modifiers
					constr.getTypeParameters(), // Type vars
					Kind.CONSTRUCTOR, // constructor
					identity(constr), // <init>(type*)
					void.class, // Always void
					constr.getGenericParameterTypes(), // parameters types
					constr.getGenericExceptionTypes() // exception types
			);
		}

		for (Method m : c.getDeclaredMethods()) {
			if (m.getDeclaringClass() != Object.class) {
				add(local, // class scope
						local, // enclosing
						m.getModifiers(), // access modifiers
						m.getTypeParameters(), Kind.METHOD, // method
						identity(m), // <name>(type*)return
						m.getGenericReturnType(), // return type
						m.getGenericParameterTypes(), // parameter types
						m.getGenericExceptionTypes() // exception types
				);
			}
		}

		return local;
	}

	private Scope getEnclosingScope(Class< ? > c) {
		Method m = c.getEnclosingMethod();
		if (m != null) {
			Scope s = getGlobalScope(m.getDeclaringClass());
			return s.getScope(identity(m));
		}
		// TODO
		// Constructor cnstr = c.getEnclosingConstructor();
		// if (m != null) {
		// Scope s = getGlobalScope(cnstr.getDeclaringClass());
		// return s.getScope(identity(cnstr));
		//
		// }
		Class< ? > enclosingClass = c.getEnclosingClass();
		if (enclosingClass != null) {
			return getGlobalScope(enclosingClass);
		}

		return null;
	}

	private Scope getGlobalScope(Class< ? > c) {
		if (c == null)
			return null;
		String id = identity(c);
		return root.getScope(id);
	}

	private Scope add(Scope declaring, Scope enclosing, int modifiers, TypeVariable< ? >[] typeVariables, Kind kind,
			String id, Type mainType, Type[] parameterTypes, Type exceptionTypes[]) {

		Scope scope = declaring.getScope(id);
		assert scope.access == Access.UNKNOWN;
		scope.setAccess(Access.modifier(modifiers));
		scope.setKind(kind);
		scope.setGenericParameter(convert(typeVariables));
		scope.setBase(convert(scope, mainType));
		scope.setParameterTypes(convert(parameterTypes));
		scope.setExceptionTypes(convert(exceptionTypes));
		scope.setDeclaring(declaring);
		scope.setEnclosing(enclosing);
		return scope;
	}

	private GenericType convert(Scope source, Type t) {
		if (t instanceof ParameterizedType) {
			// C<P..>
			ParameterizedType pt = (ParameterizedType) t;
			/* Scope reference = */root.getScope(identity((Class< ? >) pt.getRawType()));
			Type args[] = pt.getActualTypeArguments();
			GenericType[] arguments = new GenericType[args.length];
			int n = 0;
			for (Type arg : args)
				arguments[n++] = convert(source, arg);
			// return new GenericType(reference,null,arguments);

		} else if (t instanceof TypeVariable) {
			// TypeVariable tv = (TypeVariable) t;
			// return new GenericType(source,tv.getName(), null);
		} else if (t instanceof WildcardType) {
			// WildcardType wc = (WildcardType) t;
			// wc.
		} else if (t instanceof GenericArrayType) {

		}
		if (t instanceof Class< ? >) {
			// raw = ((Class<?>) t).getName() + ";";
		} else
			throw new IllegalArgumentException(t.toString());

		return null;
	}

	private GenericParameter[] convert(TypeVariable< ? > vars[]) {
		if (vars == null)
			return null;

		GenericParameter out[] = new GenericParameter[vars.length];
		for (int i = 0; i < vars.length; i++) {
			GenericType gss[] = convert(vars[i].getBounds());
			out[i] = new GenericParameter(vars[i].getName(), gss);
		}
		return out;
	}

	private GenericType[] convert(Type[] parameterTypes) {
		if (parameterTypes == null || parameterTypes.length == 0)
			return GenericType.EMPTY;

		GenericType tss[] = new GenericType[parameterTypes.length];
		for (int i = 0; i < parameterTypes.length; i++) {
			// tss[i] = new GenericType(parameterTypes[i]);
		}
		return tss;
	}

	private static String getDescriptor(Class< ? > c) {
		StringBuilder sb = new StringBuilder();
		if (c.isPrimitive()) {
			if (c == boolean.class)
				sb.append("Z");
			else if (c == byte.class)
				sb.append("Z");
			else if (c == char.class)
				sb.append("C");
			else if (c == short.class)
				sb.append("S");
			else if (c == int.class)
				sb.append("I");
			else if (c == long.class)
				sb.append("J");
			else if (c == float.class)
				sb.append("F");
			else if (c == double.class)
				sb.append("D");
			else if (c == void.class)
				sb.append("V");
			else
				throw new IllegalArgumentException("unknown primitive type: " + c);
		} else if (c.isArray()) {
			sb.append("[");
			sb.append(getDescriptor(c));
		} else {
			sb.append("L");
			sb.append(c.getName().replace('.', '/'));
			sb.append(";");
		}
		return sb.toString();
	}

}
