/************************************************
 * Copyright (c) Shen Li. All rights reserved.  *
 * http://joyaop.sourceforge.net                *
 * -------------------------------------------- *
 * Distributable under LGPL license.            *
 * See terms of license at gnu.org.             *
 ************************************************/
package net.sf.joyaop.impl;

import net.sf.cglib.proxy.Callback;
import net.sf.joyaop.AspectRuntimeException;
import net.sf.joyaop.Interceptor;
import net.sf.joyaop.framework.*;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * @author Shen Li
 */
class InterceptorCallbackFactory extends CallbackFactory {
    private Callback cachedCallback;
    private List cachedInterceptorReferences;
    private Method method;
    private Set interceptorAspects;
    private AspectizedClass aspectizedClass;
    private boolean needThreadLocalInvocation;

    public InterceptorCallbackFactory(AspectizedClass aspectizedClass, Method method, Set interceptorAspects) {
        this.aspectizedClass = aspectizedClass;
        this.method = method;
        this.interceptorAspects = interceptorAspects;
        needThreadLocalInvocation = needThreadLocalInvocation();
        boolean hasInstanceScopeInterceptor = hasInstanceScopeInterceptors();
        RuntimeAspectInstanceFactory factory = new RuntimeAspectInstanceFactory(aspectizedClass.getOriginalClass());
        Object target = getTargetObject(factory);
        if (!hasInstanceScopeInterceptor && !hasInstanceScopeMixin()) {
            cachedCallback = new CallbackImpl(getAspectInstances(factory), target, needThreadLocalInvocation);
        } else if (!hasInstanceScopeInterceptor) {
            cachedInterceptorReferences = getAspectInstances(factory);
        }
    }

    public Callback getCallback(RuntimeAspectInstanceFactory factory) {
        if (cachedCallback != null) {
            return cachedCallback;
        }
        if (cachedInterceptorReferences != null) {
            return new CallbackImpl(cachedInterceptorReferences, getTargetObject(factory), needThreadLocalInvocation);
        }
        return new CallbackImpl(getAspectInstances(factory), getTargetObject(factory), needThreadLocalInvocation);
    }

    private Object getTargetObject(RuntimeAspectInstanceFactory factory) {
        Class clazz = method.getDeclaringClass();
        if (aspectizedClass.getOriginalClass() == clazz || Object.class == clazz) {
            return null;
        }
        MixinAspect aspect = (MixinAspect) aspectizedClass.getMixin(clazz);
        if (aspect != null) {
            return factory.getRuntimeAspectInstance(aspect);
        }
        throw new AspectRuntimeException("Can't find the target object for method " + method.getName());
    }

    private boolean hasInstanceScopeMixin() {
        Class clazz = method.getDeclaringClass();
        if (aspectizedClass.getOriginalClass() == clazz || Object.class == clazz) {
            return false;
        }
        MixinAspect aspect = (MixinAspect) aspectizedClass.getMixin(clazz);
        if (aspect != null) {
            return (RuntimeAspect.INSTANCE_SCOPE.equals(aspect.getScope()));
        }
        throw new AspectRuntimeException("Can't find the target object for method " + method.getName());
    }

    private boolean hasInstanceScopeInterceptors() {
        for (Iterator i = interceptorAspects.iterator(); i.hasNext();) {
            if (RuntimeAspect.INSTANCE_SCOPE.equals(((InterceptorAspect) i.next()).getScope())) {
                return true;
            }
        }
        return false;
    }

    private boolean needThreadLocalInvocation() {
        for (Iterator i = interceptorAspects.iterator(); i.hasNext();) {
            // doesn't use the super method
            if (!Interceptor.class.isAssignableFrom(((InterceptorAspect) i.next()).getAspectClass())) {
                return true;
            }
        }
        Class clazz = method.getDeclaringClass();
        if (aspectizedClass.getOriginalClass() == clazz || Object.class == clazz) {
            return needThreadLocalInvocation(aspectizedClass.getOriginalClass());
        }
        MixinAspect aspect = (MixinAspect) aspectizedClass.getMixin(clazz);
        if (aspect != null) {
            return needThreadLocalInvocation(aspect.getAspectClass());
        }
        throw new AspectRuntimeException("Can't find the target object for method " + method.getName());
    }

    private List getAspectInstances(RuntimeAspectInstanceFactory factory) {
        List refs = new ArrayList(interceptorAspects.size());
        for (Iterator i = interceptorAspects.iterator(); i.hasNext();) {
            refs.add(factory.getRuntimeAspectInstance((InterceptorAspect) i.next()));
        }
        return refs;
    }
}