/************************************************
 * 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.*;
import net.sf.joyaop.AbstractInterceptor;
import net.sf.joyaop.AspectRuntimeException;
import net.sf.joyaop.framework.AspectFactory;
import net.sf.joyaop.util.ClassUtils;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
 * @author Shen Li
 */
public class DefaultAspectFactory implements AspectFactory {
    private static final Class[] interfaceClasses = new Class[]{WriteReplace.class};

    public Object newInstance(Class implementationClass) {
        return newInstance(implementationClass, new Class[0], new Object[0]);
    }

    public Object newInstance(Class implementaionClass, Class[] argTypes, Object[] args) {
        if (!Modifier.isAbstract(implementaionClass.getModifiers())) {
            return ClassUtils.newInstance(implementaionClass, argTypes, args);
        }
        Factory factory = ClassUtils.getProxyFactory(implementaionClass);
        if (factory == null) {
            CallbackFilter callbackFilter = new CallbackFilterImpl(implementaionClass);
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(implementaionClass);
            enhancer.setInterfaces(interfaceClasses);
            enhancer.setClassLoader(ClassUtils.getClassLoader());
            enhancer.setCallbacks(new Callback[]{
                new SelfCallback(),
                new ProxyCallback(),
                new GetterCallback(),
                new WriteReplaceCallbackFactory(interfaceClasses, callbackFilter).getCallback(),
                new AbstractInterceptorCallback()
            });
            enhancer.setCallbackFilter(callbackFilter);
            factory = (Factory) enhancer.create(argTypes, args);
            ClassUtils.putProxyFactory(implementaionClass, factory);
            return factory;
        }
        return factory.newInstance(argTypes, args, factory.getCallbacks());
    }

    private static class GetterCallback implements MethodInterceptor, Serializable {
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            String name = method.getName();
            if (!name.startsWith("get")) {
                throw new AspectRuntimeException();
            }
            Object proxy = InvocationImpl.getCurrentInvocation().getProxy();
            if (method.getReturnType().isAssignableFrom(proxy.getClass())) {
                return proxy;
            }
            if (name.length() > 3) {
                name = new StringBuffer(name.length() - 3)
                        .append(name.substring(3, 4).toLowerCase())
                        .append(name.substring(4)).toString();
                return InvocationImpl.getCurrentInvocation().getParameter(name);

            }
            throw new AspectRuntimeException("Method " + method.getName() + "is not a getter method.");
        }
    }

    private static class ProxyCallback implements Dispatcher, Serializable {
        public Object loadObject() throws Exception {
            return InvocationImpl.getCurrentInvocation().getProxy();
        }
    }

    private static class AbstractInterceptorCallback implements Dispatcher, Serializable {
        public Object loadObject() throws Exception {
            return InvocationImpl.getCurrentInvocation();
        }
    }

    private static class SelfCallback implements NoOp, Serializable {
    }

    private static class CallbackFilterImpl implements CallbackFilter, Serializable {
        private Class implementationClass;

        public CallbackFilterImpl(Class implementationClass) {
            this.implementationClass = implementationClass;
        }

        public int accept(Method method) {
            // NOTE the abstract parameter and target getter methods must be defined in the impl class itself.
            if (!Modifier.isAbstract(method.getModifiers())) {
                return 0;
            }
            Class clazz = method.getDeclaringClass();
            if (implementationClass == clazz) {
                if (method.getName().startsWith("get")) {
                    return 2;
                } else {
                    throw new AspectRuntimeException();
                }
            } else if (WriteReplace.class == clazz) {
                return 3;
            } else if (clazz.isAssignableFrom(AbstractInterceptor.class)) {
                return 4;
            } else { // TODO
                return 1;
            }
        }
    }
}
