package com.baidu.unbiz.multitask.spring.integration;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.PriorityOrdered;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import com.baidu.unbiz.multitask.annotation.TaskBean;
import com.baidu.unbiz.multitask.annotation.TaskService;
import com.baidu.unbiz.multitask.exception.TaskBizException;
import com.baidu.unbiz.multitask.task.Taskable;

/**
 * Fetcher Bean容器,与spring集成
 *
 * @author wangchongjie
 * @since 2015-8-9 下午2:18:00
 */
@Component
public class TaskBeanContainer implements ApplicationContextAware, PriorityOrdered {

    private static final Log LOG = LogFactory.getLog(TaskBeanContainer.class);

    // Spring应用上下文环境
    private static ApplicationContext applicationContext;
    private static Map<String, Taskable<?>> container = new ConcurrentHashMap<String, Taskable<?>>();
    private static AtomicBoolean initing = new AtomicBoolean(false);
    private static CountDownLatch hasInit = new CountDownLatch(1);
    private static volatile String springContainerInstanceFlag = "";

    // @PostConstruct
    public static void initFetcherContainer() {
        initFetcherContainer(applicationContext);
    }

    /**
     * 初始化Fetcher容器
     *
     * @param factory
     */
    public static void initFetcherContainer(ListableBeanFactory factory) {
        if (initing.get()) {
            waitInit();
            return;
        }
        if (initing.compareAndSet(false, true)) {
            Map<String, Object> fetcherServices = factory.getBeansWithAnnotation(TaskService.class);
            for (Object service : fetcherServices.values()) {
                regiserOneService(service);
            }
            hasInit.countDown();
        } else {
            waitInit();
        }
    }

    private static void waitInit() {
        try {
            hasInit.await();
        } catch (InterruptedException e) {
            LOG.error("Interrupted while waiting init.", e);
        }
    }

    /**
     * 向容器中注册一个service中带FetcherBean的方法,并包装成Fetcher
     *
     * @param service
     */
    public static void regiserOneService(Object service) {
        Class<?> clazz = service.getClass();
        for (Method method : ReflectionUtils.getAllDeclaredMethods(clazz)) {
            TaskBean bean = method.getAnnotation(TaskBean.class);
            if (bean != null) {
                registerFetcher(service, method, bean.value());
            }
        }
    }

    /**
     * 优先使用@Resource方式注入,此处为预留接口,也可获取Fetcher Bean
     *
     * @param beanName
     *
     * @return bean
     */
    @SuppressWarnings("unchecked")
    public <T> T bean(String beanName) {
        T bean = (T) container.get(beanName);
        if (bean != null) {
            return bean;
        } else {
            return (T) TaskBeanContainer.getBean(beanName);
        }
    }

    public Taskable<?> task(String beanName) {
        return bean(beanName);
    }

    /**
     * 注册一个Fetcher
     *
     * @param service
     * @param method
     * @param beanName
     */
    private static void registerFetcher(final Object service, final Method method, final String beanName) {
        if (TaskBeanContainer.containsBean(beanName)) {
            throw new TaskBizException("Fetcher bean duplicate for Spring:" + beanName);
        }

        final int paramLen = method.getGenericParameterTypes().length;
        Taskable<?> fetcher = TaskBeanHelper.newFetcher(service, method, beanName, paramLen);

        BeanFactory factory = applicationContext.getAutowireCapableBeanFactory();

        if (factory instanceof DefaultListableBeanFactory) {
            DefaultListableBeanFactory defaultFactory = (DefaultListableBeanFactory) factory;
            defaultFactory.registerSingleton(beanName, fetcher);
            // GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            // beanDefinition.setBeanClass(task.getClass());
            // listFactory.registerBeanDefinition(beanName, beanDefinition);
            LOG.info("DefaultListableBeanFactory Fetcher register: " + beanName);
        } else if (factory instanceof AbstractBeanFactory) {
            AbstractBeanFactory abstFactory = (AbstractBeanFactory) factory;
            abstFactory.registerSingleton(beanName, fetcher);
            LOG.info("AbstractBeanFactory Fetcher register: " + beanName);
        } else {
            container.put(beanName, fetcher);
            LOG.info("LocalContainer Fetcher register: " + beanName);
        }
    }

    /**
     * 显式注册TaskBean
     *
     * @param service
     * @param beanName
     * @param funcName
     * @param paramsType
     */
    public static void registerTaskBean(Object service, String beanName, String funcName, Class<?>... paramsType) {
        Method method = null;
        try {
            method = service.getClass().getMethod(funcName, paramsType);
        } catch (NoSuchMethodException e) {
            LOG.error("register TaskBean fail:", e);
        }
        registerFetcher(service, method, beanName);
    }

    /**
     * spring bean是否存在
     *
     * @param name
     *
     * @return spring bean是否存在
     */
    public static boolean containsBean(String name) {
        return applicationContext.containsBean(name);
    }

    /**
     * 获取spring bean
     *
     * @param name
     *
     * @return spring bean
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) {
        return (T) applicationContext.getBean(name);
    }

    /**
     * 设置spring上下文
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        TaskBeanContainer.applicationContext = applicationContext;
        String newValue = String.valueOf(applicationContext.hashCode());
        LOG.info("fetcherBean container id:" + newValue);
        // 不同的Spring Context Refreshing,允许重新初始化,此处不会有并发
        if (!springContainerInstanceFlag.equals(newValue)) {
            hasInit = new CountDownLatch(1);
            initing.set(false);
            initFetcherContainer();
            springContainerInstanceFlag = newValue;
        }
    }

    /**
     * 设置spring构建优先级
     */
    @Override
    public int getOrder() {
        return PriorityOrdered.HIGHEST_PRECEDENCE;
    }
}