/*
   Copyright 2007 Attila Szegedi

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
package org.dynalang.mop.beans;

import java.lang.reflect.Array;
import java.lang.reflect.Member;
import java.lang.reflect.UndeclaredThrowableException;

import org.dynalang.mop.CallProtocol;
import org.dynalang.mop.BaseMetaobjectProtocol.Result;

class SimpleDynamicMethod<T extends Member> extends DynamicMethod<T>
{
    private final T member;
    // We can directly reference the classes, as they're declared parameters,
    // and are accessible to the class that owns this method anyway. Therefore,
    // referencing them here can't cause memory leaks.
    private final Class<?>[] paramTypes;
    
    SimpleDynamicMethod(T method) {
        this.member = method;
        this.paramTypes = getParameterTypes(method);
    }
    
    T getMember() {
        return member;
    }
    
    @Override
    public Object call(Object target, CallProtocol callProtocol, Object... args) {
        if(args == null) {
            // null is treated as empty args
            args = NULL_ARGS;
        }
        
        int argsLen = args.length;
        int paramTypesLen = paramTypes.length;
        int paramTypesLenM1 = paramTypesLen - 1;
        
        boolean varArg = isVarArgs(member);
        int fixArgsLen = varArg ? paramTypesLenM1 : paramTypesLen;   
        
        // Convert arguments if needed
        int min = Math.min(argsLen, fixArgsLen);
        boolean argsCloned = false;
        for(int i = 0; i < min; ++i) {
            Object src = args[i];
            Object dst = marshal(args[i], paramTypes[i], callProtocol);
            if(dst != src) {
                if(!argsCloned) {
                    args = args.clone();
                }
                args[i] = dst;
            }
        }
        if(varArg) {
            Class<?> varArgType = paramTypes[paramTypesLenM1];
            Class<?> componentType = varArgType.getComponentType(); 
            if(argsLen != paramTypesLen) {
                // Zero or multiple varargs specified -- we must resize the
                // arguments array
                Object[] newargs = new Object[paramTypesLen];
                System.arraycopy(args, 0, newargs, 0, paramTypesLenM1);
                int varArgLen = argsLen - paramTypesLenM1;
                // Set the last argument to be the vararg array
                Object varArgArray = Array.newInstance(componentType, 
                        varArgLen);
                newargs[paramTypesLenM1] = varArgArray;
                // Populate the vararg array
                for(int i = 0; i < varArgLen; ++i) {
                    Array.set(varArgArray, i, marshal(
                            args[paramTypesLenM1 + i], componentType, 
                            callProtocol));
                }
                args = newargs;
            }
            else {
                // exactly one vararg specified. It might be an array of the 
                // expected type in itself
                Object lastArg = args[paramTypesLenM1];
                Object varArgArray = callProtocol.representAs(lastArg, 
                        varArgType);
                if(varArgArray != null && !varArgType.isInstance(varArgArray)) {
                    // It isn't. Well then, let's assume it's a one-element
                    // vararg, and construct a 1-element array for it
                    varArgArray = Array.newInstance(componentType, 1);
                    Array.set(varArgArray, 0, marshal(lastArg, componentType,
                            callProtocol));
                }
                // If varArgArray isn't identical to lastArg, update the last 
                // args element
                if(lastArg != varArgArray) {
                    if(!argsCloned) {
                        args = args.clone();
                    }
                    args[paramTypesLenM1] = varArgArray;
                }
            }
        }
        try {
            return invoke(member, target, args);
        }
        catch(RuntimeException e) {
            throw e;
        }
        catch(Exception e) {
            throw new UndeclaredThrowableException(e);
        }
    }
    
    private Object marshal(Object src, Class<?> targetClass, 
	    CallProtocol callProtocol) {
        Object dst = callProtocol.representAs(src, targetClass);
        if(dst == Result.noAuthority || dst == Result.noRepresentation) {
            String className = src == null ? "null" : src.getClass().getName();
            throw new IllegalArgumentException("Can't represent " + className +
                    " as " + targetClass.getName() + " for invoking " + member);
        }
        return dst;
    }
}