/*
 * Copyright 1999-2012 Alibaba Group.
 *  
 * 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 com.alibaba.dubbo.rpc.support;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.concurrent.atomic.AtomicLong;

import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.common.utils.ReflectUtils;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.RpcInvocation;

/**
 * RpcUtils
 * 
 * @author william.liangf
 * @author chao.liuc
 */
public class RpcUtils {

    private static final Logger logger = LoggerFactory.getLogger(RpcUtils.class);

    public static Class<?> getReturnType(Invocation invocation) {
        try {
            if (invocation != null && invocation.getInvoker() != null
                    && invocation.getInvoker().getUrl() != null
                    && ! invocation.getMethodName().startsWith("$")) {
                String service = invocation.getInvoker().getUrl().getServiceInterface();
                if (service != null && service.length() > 0) {
                    Class<?> cls = ReflectUtils.forName(service);
                    Method method = cls.getMethod(invocation.getMethodName(), invocation.getParameterTypes());
                    if (method.getReturnType() == void.class) {
                        return null;
                    }
                    return method.getReturnType();
                }
            }
        } catch (Throwable t) {
            logger.warn(t.getMessage(), t);
        }
        return null;
    }

    public static Type[] getReturnTypes(Invocation invocation) {
        try {
            if (invocation != null && invocation.getInvoker() != null
                    && invocation.getInvoker().getUrl() != null
                    && ! invocation.getMethodName().startsWith("$")) {
                String service = invocation.getInvoker().getUrl().getServiceInterface();
                if (service != null && service.length() > 0) {
                    Class<?> cls = ReflectUtils.forName(service);
                    Method method = cls.getMethod(invocation.getMethodName(), invocation.getParameterTypes());
                    if (method.getReturnType() == void.class) {
                        return null;
                    }
                    return new Type[]{method.getReturnType(), method.getGenericReturnType()};
                }
            }
        } catch (Throwable t) {
            logger.warn(t.getMessage(), t);
        }
        return null;
    }
    
    private static final AtomicLong INVOKE_ID = new AtomicLong(0);
    
	public static Long getInvocationId(Invocation inv) {
    	String id = inv.getAttachment(Constants.ID_KEY);
		return id == null ? null : new Long(id);
	}
    
    /**
     * 幂等操作:异步操作默认添加invocation id
     * @param url
     * @param inv
     */
    public static void attachInvocationIdIfAsync(URL url, Invocation inv){
    	if (isAttachInvocationId(url, inv) && getInvocationId(inv) == null && inv instanceof RpcInvocation) {
    		((RpcInvocation)inv).setAttachment(Constants.ID_KEY, String.valueOf(INVOKE_ID.getAndIncrement()));
        }
    }
    
    private static boolean isAttachInvocationId(URL url , Invocation invocation) {
    	String value = url.getMethodParameter(invocation.getMethodName(), Constants.AUTO_ATTACH_INVOCATIONID_KEY);
    	if ( value == null ) {
    		//异步操作默认添加invocationid
    		return isAsync(url,invocation) ;
    	} else if (Boolean.TRUE.toString().equalsIgnoreCase(value)) {
    		//设置为添加,则一定添加
    		return true;
    	} else {
    		//value为false时,不添加
    		return false;
    	}
    }
    
    public static String getMethodName(Invocation invocation){
    	if(Constants.$INVOKE.equals(invocation.getMethodName()) 
                && invocation.getArguments() != null 
                && invocation.getArguments().length > 0 
                && invocation.getArguments()[0] instanceof String){
            return (String) invocation.getArguments()[0];
        }
    	return invocation.getMethodName();
    }

    public static Object[] getArguments(Invocation invocation){
    	if(Constants.$INVOKE.equals(invocation.getMethodName()) 
                && invocation.getArguments() != null 
                && invocation.getArguments().length > 2
                && invocation.getArguments()[2] instanceof Object[]){
            return (Object[]) invocation.getArguments()[2];
        }
    	return invocation.getArguments();
    }

    public static Class<?>[] getParameterTypes(Invocation invocation){
    	if(Constants.$INVOKE.equals(invocation.getMethodName()) 
                && invocation.getArguments() != null 
                && invocation.getArguments().length > 1
                && invocation.getArguments()[1] instanceof String[]){
            String[] types = (String[]) invocation.getArguments()[1];
            if (types == null) {
            	return new Class<?>[0];
            }
            Class<?>[] parameterTypes = new Class<?>[types.length];
            for (int i = 0; i < types.length; i ++) {
            	parameterTypes[i] = ReflectUtils.forName(types[0]);
            }
            return parameterTypes;
        }
    	return invocation.getParameterTypes();
    }
    
    public static boolean isAsync(URL url, Invocation inv) {
    	boolean isAsync ;
    	//如果Java代码中设置优先.
    	if (Boolean.TRUE.toString().equals(inv.getAttachment(Constants.ASYNC_KEY))) {
    		isAsync = true;
    	} else {
	    	isAsync = url.getMethodParameter(getMethodName(inv), Constants.ASYNC_KEY, false);
    	}
    	return isAsync;
    }
    
    public static boolean isOneway(URL url, Invocation inv) {
    	boolean isOneway ;
    	//如果Java代码中设置优先.
    	if (Boolean.FALSE.toString().equals(inv.getAttachment(Constants.RETURN_KEY))) {
    		isOneway = true;
    	} else {
    		isOneway = ! url.getMethodParameter(getMethodName(inv), Constants.RETURN_KEY, true);
    	}
    	return isOneway;
    }
    
}