package org.albianj.aop.impl;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.albianj.aop.*;
import org.albianj.loader.AlbianClassLoader;
import org.albianj.logger.AlbianLoggerLevel;
import org.albianj.service.AlbianServiceRouter;
import org.albianj.service.IAlbianService;
import org.albianj.verify.Validate;

import java.lang.reflect.Method;
import java.util.Map;

/**
 * Created by xuhaifeng on 16/5/30.
 */
public class AlbianServiceAopProxy implements MethodInterceptor {
    IAlbianService _service = null;
    Map<String, IAlbianServiceAopAttribute> _aopAttributes = null;


    public Object newInstance(IAlbianService service, Map<String, IAlbianServiceAopAttribute> aopAttributes) {
        this._service = service;
        this._aopAttributes = aopAttributes;
        try {
            Enhancer enhancer = new Enhancer();  //增强类
            //不同于JDK的动态代理。它不能在创建代理时传obj对 象,obj对象必须被CGLIB包来创建
            enhancer.setClassLoader(AlbianClassLoader.getInstance());

            enhancer.setSuperclass(this._service.getClass()); //设置被代理类字节码(obj将被代理类设置成父类;作为产生的代理的父类传进来的)。CGLIB依据字节码生成被代理类的子类
            enhancer.setCallback(this);    //设置回调函数,即一个方法拦截
            Object proxy = enhancer.create(); //创建代理类
            return proxy;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        String mName = method.getName();
        if (mName.equals("hashCode")
                || mName.equals("toString")
                || mName.equals("equals")
                || mName.equals("clone")
                || mName.equals("finalize")) {
            Object rc = methodProxy.invokeSuper(proxy, args);
            return rc;
        }

        Method rm = this._service.getClass().getMethod(mName, method.getParameterTypes());
        AlbianAopAttribute attr = rm.getAnnotation(AlbianAopAttribute.class);
        if (null != attr && attr.avoid()) {
            Object rc = methodProxy.invokeSuper(proxy, args);
            return rc;
        }

        if (Validate.isNullOrEmpty(_aopAttributes)) {
            Object rc = methodProxy.invokeSuper(proxy, args);
            return rc;
        }

        IAlbianAopContext ctx = new AlbianAopContext();

        Object rc = null;
        for (IAlbianServiceAopAttribute asaa : _aopAttributes.values()) {
            IAlbianAopService aas = AlbianServiceRouter.getSingletonService(
                    IAlbianAopService.class, asaa.getServiceName(), false);
            if (null == aas) continue;

            if (asaa.matches(mName)) {
                try {
                    aas.before(ctx, _service, method, args);
                } catch (Throwable e) {
                    AlbianServiceRouter.addLog("AOPService", AlbianServiceRouter.AlbianRuntimeLogName, AlbianLoggerLevel.Error, e,
                            "execute before method in the aop service:%s for real service:%s is fail.",
                            asaa.getServiceName(), this._service.getServiceName());
//                    AlbianServiceRouter.getLogger2().log(IAlbianLoggerService2.AlbianRunningLoggerName,
//                            IAlbianLoggerService2.InnerThreadName, AlbianLoggerLevel.Error,e,
//                            "execute the before method in the aop service:%s for real service:%s is fail.",
//                            asaa.getServiceName(),this._service.getServiceName());
                }
            }
        }

        Throwable throwable = null;
        try {
            rc = methodProxy.invokeSuper(proxy, args);
        } catch (Throwable t) {
            throwable = t;
            AlbianServiceRouter.addLog("AOPService", AlbianServiceRouter.AlbianRuntimeLogName, AlbianLoggerLevel.Error, t,
                    "exception in proxy service:%s method:%s.",
                    this._service.getServiceName(), mName);
//            AlbianServiceRouter.getLogger2().log(IAlbianLoggerService2.AlbianRunningLoggerName,
//                    IAlbianLoggerService2.InnerThreadName, AlbianLoggerLevel.Error,t,
//                    "execute the proxy service:%s method:%s is fail.",this._service.getServiceName(),mName);
        }

        for (IAlbianServiceAopAttribute asaa : _aopAttributes.values()) {
            IAlbianAopService aas = AlbianServiceRouter.getSingletonService(
                    IAlbianAopService.class, asaa.getServiceName(), false);
            if (null == aas) continue;

            if (asaa.matches(mName)) {
                try {
                    aas.after(ctx, _service, method, rc, throwable, args);
                } catch (Throwable e) {
                    AlbianServiceRouter.addLog("AOPService", AlbianServiceRouter.AlbianRuntimeLogName, AlbianLoggerLevel.Error, e,
                            "exception in the after method in the aop service:%s for real service:%s is fail.",
                            asaa.getServiceName(), this._service.getServiceName());

//                    AlbianServiceRouter.getLogger2().log(IAlbianLoggerService2.AlbianRunningLoggerName,
//                            IAlbianLoggerService2.InnerThreadName, AlbianLoggerLevel.Error,e,
//                            "execute the after method in the aop service:%s for real service:%s is fail.",
//                            asaa.getServiceName(),this._service.getServiceName());
                }
            }
        }

        if (null != throwable) throw throwable;
        return rc;

    }
}