package railo.transformer.bytecode.reflection;


import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import railo.commons.lang.ClassUtil;

/**
 * A {@code Method} provides information about, and access to, a single method
 * on a class or interface.  The reflected method may be a class method
 * or an instance method (including an abstract method).
 *
 * <p>A {@code Method} permits widening conversions to occur when matching the
 * actual parameters to invoke with the underlying method's formal
 * parameters, but it throws an {@code IllegalArgumentException} if a
 * narrowing conversion would occur.
 *
 * @see Member
 * @see java.lang.Class
 * @see java.lang.Class#getMethods()
 * @see java.lang.Class#getMethod(String, Class[])
 * @see java.lang.Class#getDeclaredMethods()
 * @see java.lang.Class#getDeclaredMethod(String, Class[])
 */
public abstract class ASMMethod  {
    private Class[]             exceptionTypes;
    private Class               returnType;
    private Class[]             parameterTypes;
    //private int                 modifiers;
    private Class               clazz;
    

    /**
     * Package-private constructor used by ReflectAccess to enable
     * instantiation of these objects in Java code from the java.lang
     * package via sun.reflect.LangReflectAccess.
     */
    public ASMMethod(Class declaringClass, Class[] parameterTypes) {
        this.clazz = declaringClass;
        this.parameterTypes = parameterTypes;
    }
    

    /**
     * Returns the {@code Class} object representing the class or interface
     * that declares the method represented by this {@code Method} object.
     */
    public Class<?> getDeclaringClass() {
        return clazz;
    }

    /**
     * Returns the name of the method represented by this {@code Method}
     * object, as a {@code String}.
     */
    public abstract String getName();

    /**
     * Returns the Java language modifiers for the method represented
     * by this {@code Method} object, as an integer. The {@code Modifier} class should
     * be used to decode the modifiers.
     *
     * @see Modifier
     */
    public abstract int getModifiers();

    /**
     * Returns a {@code Class} object that represents the formal return type
     * of the method represented by this {@code Method} object.
     *
     * @return the return type for the method this object represents
     */

    public  Class<?> getReturnType() {
    	if(returnType==null) {
    		returnType = ClassUtil.loadClass(getReturnTypeAsString(),null);
    		if(returnType==null) initAddionalParams();
    	}
    	return returnType;
    }
    
    protected Class<?> _getReturnType() {
    	if(returnType==null) initAddionalParams();
        return returnType;
    }
    
    public abstract String getReturnTypeAsString();

    /**
     * Returns an array of {@code Class} objects that represent the formal
     * parameter types, in declaration order, of the method
     * represented by this {@code Method} object.  Returns an array of length
     * 0 if the underlying method takes no parameters.
     *
     * @return the parameter types for the method this object
     * represents
     */
    public Class<?>[] getParameterTypes() {
        return parameterTypes.clone();
    }


    /**
     * Returns an array of {@code Class} objects that represent
     * the types of the exceptions declared to be thrown
     * by the underlying method
     * represented by this {@code Method} object.  Returns an array of length
     * 0 if the method declares no exceptions in its {@code throws} clause.
     *
     * @return the exception types declared as being thrown by the
     * method this object represents
     */
    public Class<?>[] getExceptionTypes() {
    	if(exceptionTypes==null) initAddionalParams();
        return exceptionTypes.clone();
    }

    private void initAddionalParams() {
		try {
			Method m = clazz.getMethod(getName(), getParameterTypes());
			exceptionTypes=m.getExceptionTypes();
			returnType=m.getReturnType();
		}
		catch (SecurityException e) {
			// TODO remove block
			e.printStackTrace();
		}
		catch (NoSuchMethodException e) {
			// TODO remove block
			e.printStackTrace();
		}
		
	}


	/**
     * Compares this {@code Method} against the specified object.  Returns
     * true if the objects are the same.  Two {@code Methods} are the same if
     * they were declared by the same class and have the same name
     * and formal parameter types and return type.
     */
    public boolean equals(Object obj) {
    	if(this==obj) return true;
    	if(!(obj instanceof ASMMethod)) return false;
    	
    	ASMMethod other=(ASMMethod) obj;
    	
        return 
        this.getModifiers()==other.getModifiers() &&
        this.getName().equals(other.getName()) &&
        this.clazz.equals(other.clazz) &&
        this.returnType.equals(other.returnType) &&
        eq(this.getExceptionTypes(),other.getExceptionTypes()) &&
        eq(this.parameterTypes,other.parameterTypes);
    }

    private boolean eq(Class[] left, Class[] right) {
    	int l=left==null?0:left.length;
    	int r=right==null?0:right.length;
		if(l!=r) return false;
		if(l==0) return true;
		
		for(int i=0;i<left.length;i++){
			if(!left[i].equals(right[i])) return false;
		}
		return true;
	}

	/**
     * Invokes the underlying method represented by this {@code Method}
     * object, on the specified object with the specified parameters.
     * Individual parameters are automatically unwrapped to match
     * primitive formal parameters, and both primitive and reference
     * parameters are subject to method invocation conversions as
     * necessary.
     *
     * <p>If the underlying method is static, then the specified {@code obj}
     * argument is ignored. It may be null.
     *
     * <p>If the number of formal parameters required by the underlying method is
     * 0, the supplied {@code args} array may be of length 0 or null.
     *
     * <p>If the underlying method is an instance method, it is invoked
     * using dynamic method lookup as documented in The Java Language
     * Specification, Second Edition, section 15.12.4.4; in particular,
     * overriding based on the runtime type of the target object will occur.
     *
     * <p>If the underlying method is static, the class that declared
     * the method is initialized if it has not already been initialized.
     *
     * <p>If the method completes normally, the value it returns is
     * returned to the caller of invoke; if the value has a primitive
     * type, it is first appropriately wrapped in an object. However,
     * if the value has the type of an array of a primitive type, the
     * elements of the array are <i>not</i> wrapped in objects; in
     * other words, an array of primitive type is returned.  If the
     * underlying method return type is void, the invocation returns
     * null.
     *
     * @param obj  the object the underlying method is invoked from
     * @param args the arguments used for the method call
     * @return the result of dispatching the method represented by
     * this object on {@code obj} with parameters
     * {@code args}
     *
     * @exception IllegalAccessException    if this {@code Method} object
     *              enforces Java language access control and the underlying
     *              method is inaccessible.
     * @exception IllegalArgumentException  if the method is an
     *              instance method and the specified object argument
     *              is not an instance of the class or interface
     *              declaring the underlying method (or of a subclass
     *              or implementor thereof); if the number of actual
     *              and formal parameters differ; if an unwrapping
     *              conversion for primitive arguments fails; or if,
     *              after possible unwrapping, a parameter value
     *              cannot be converted to the corresponding formal
     *              parameter type by a method invocation conversion.
     * @exception InvocationTargetException if the underlying method
     *              throws an exception.
     * @exception NullPointerException      if the specified object is null
     *              and the method is an instance method.
     * @exception ExceptionInInitializerError if the initialization
     * provoked by this method fails.
     */
    public abstract Object invoke(Object obj, Object[] args) throws Throwable;//IllegalAccessException, IllegalArgumentException, InvocationTargetException;
}
