package qunar.tc.qconfig.client.spring;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ClassUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import static qunar.tc.qconfig.client.spring.QConfigPropertyAnnotationUtil.buildStringValueResolver;
import static qunar.tc.qconfig.client.spring.QConfigPropertyAnnotationUtil.loadQConfigFilesInAnnotation;

/**
 * User: zhaohuiyu
 * Date: 7/5/13
 * Time: 7:57 PM
 */
class QConfigAnnotationProcessor extends PropertyPlaceholderConfigurer implements BeanPostProcessor, ApplicationContextAware, BeanClassLoaderAware {

    private boolean trimValue;

    private final Map<Class, AnnotationManager> managers = new HashMap<>();

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        Class<?> clazz = getRealClass(bean);
        if (AnnotationUtils.isAnnotationDeclaredLocally(DisableQConfig.class, clazz)) return bean;

        AnnotationManager manager = getOrCreateManager(clazz);
        manager.init(clazz);
        manager.processBean(bean);
        return bean;
    }

    private Class<?> getRealClass(Object bean) {
        Class<?> clazz = bean.getClass();
        while (ClassUtils.isCglibProxyClass(clazz)) {
            clazz = clazz.getSuperclass();
        }
        return clazz;
    }


    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }


    public void setTrimValue(boolean trimValue) {
        this.trimValue = trimValue;
    }

    private ClassLoader beanClassLoader;

    private Map<String, Properties> filenameToPropsMap;

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        beanClassLoader = classLoader;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (applicationContext instanceof ConfigurableApplicationContext) {
            ConfigurableListableBeanFactory beanFactory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
            if (beanFactory != null) {
                filenameToPropsMap = loadQConfigFilesInAnnotation(beanFactory, beanClassLoader);
            }
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        if (filenameToPropsMap != null && !filenameToPropsMap.isEmpty()) {
            doProcessProperties(beanFactory, buildStringValueResolver(filenameToPropsMap));
        }
    }

    private AnnotationManager getOrCreateManager(Class<?> clazz) {
        synchronized (managers) {
            AnnotationManager manager = managers.get(clazz);
            if (manager == null) {
                manager = new AnnotationManager(trimValue);
                managers.put(clazz, manager);
            }
            return manager;
        }
    }

}
