package net.northking.cctp.element.core.support;


import net.northking.cctp.element.core.InitializingBean;
import net.northking.cctp.element.core.Library;
import net.northking.cctp.element.core.LibraryContext;
import net.northking.cctp.element.core.PresetConfiguration;
import net.northking.cctp.element.core.scanner.LibraryDefinition;
import net.northking.cctp.element.core.keyword.IKeywordFactory;
import net.northking.cctp.element.core.keyword.Keyword;
import net.northking.cctp.element.core.keyword.KeywordFactory;
import net.northking.cctp.element.log.Logger;
import net.northking.cctp.element.util.Assert;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * DefaultLibraryContext
 *
 * @author fengxiang.zhang
 * @since 1.0
 */
public class DefaultLibraryContext implements LibraryContext, BeanRegistry {

    private static final org.slf4j.Logger logger = LoggerFactory.getLogger(DefaultLibraryContext.class);
    private LibraryDefinition libraryDefinition;
    private BeanFactory beanFactory;
    private BeanRegistry beanRegistry;
    private Map<String, Keyword> keywords;
    private Map<String, String> presetParams = new HashMap<>();
    private Map<Class<?>, Object> threadLevelCache = new HashMap<>();

    @Override
    public Library getLibrary() {
        return this.beanFactory.getBean(libraryDefinition.getLibraryClass());
    }

    @Override
    public ClassLoader getClassLoader() {
        Assert.notNull(libraryDefinition);
        return libraryDefinition.getResourceLoader().getClassLoader();
    }

    @Override
    public Map<String, Keyword> getKeywords() {
        return keywords;
    }

    @Override
    public void refreshThreadCache() {
        threadLevelCache.forEach((k, v) -> ThreadLevelCache.cache(k, v));
    }

    @Override
    public <T> T getBean(Class<T> requiredType) {
        return beanFactory.getBean(requiredType);
    }

    @Override
    public Collection<Object> getAllBeans() {
        return beanFactory.getAllBeans();
    }

    @Override
    public void registerBeans(Class<?>... clazz) {
        beanRegistry.registerBeans(clazz);
    }

    @Override
    public void registerBean(Class<?> cls, Object bean) {
        beanRegistry.registerBean(cls, bean);
    }

    public void initialize(Map<String, String> preset, boolean onlyScan) throws Exception {
        Assert.notNull(libraryDefinition);
        //注册注解Bean
        registerCandidateComponents(libraryDefinition);
        //依赖注入
        registerBeanPostProcess(beanFactory);
        //关键字抽取
        createKeywords(beanFactory);
        //预设参数
        setPresetParams(preset);
        //Bean初始化完成后执行
        postProcessAfterInitialization(beanFactory, onlyScan);
    }

    /**
     * Set library definition
     *
     * @param definition
     */
    public void setLibraryDefinition(LibraryDefinition definition) {
        Assert.notNull(definition);
        this.libraryDefinition = definition;
    }

    /**
     * Set bean factory
     *
     * @param beanFactory
     */
    public void setBeanFactory(BeanFactory beanFactory) {
        Assert.notNull(beanFactory);
        this.beanFactory = beanFactory;
    }

    public void setBeanRegistry(BeanRegistry beanRegistry) {
        Assert.notNull(beanRegistry);
        this.beanRegistry = beanRegistry;
    }

    /**
     * Register builtin logger
     *
     * @param logger
     */
    public void registerLogger(Logger logger) {
        this.registerBuiltinComponent(Logger.class, logger);
    }

    // #########################################################################
    // Internal
    // #########################################################################

    private void setPresetParams(Map<String, String> preset) {
        logger.debug("Add preset configuration information to library instance");
        Map<String, PresetConfiguration.PresetParam> defaultPresetParam =
                getLibrary().getDefaultPresetParams().asMap();
        for (Map.Entry<String, PresetConfiguration.PresetParam> entry : defaultPresetParam.entrySet()) {
            presetParams.put(entry.getKey(), entry.getValue().getDefaultValue());
        }
        if (preset != null) {
            for (Map.Entry<String, String> entry : preset.entrySet()) {
                addPresetParam(entry.getKey(), entry.getValue(), defaultPresetParam);
            }
        }
    }

    private void addPresetParam(String key, String value, Map<String, PresetConfiguration.PresetParam> defaultPreset) {
        Assert.notNull(key);
        PresetConfiguration.PresetParam param = defaultPreset.get(key);
        if (param == null || param.validate(value)) {
            presetParams.put(key, value);
        } else {
            logger.error("预设参数:[{}={}]失败，值非法", key, value);
        }
    }

    /**
     * Register annotation beans
     *
     * @param definition
     */
    private void registerCandidateComponents(LibraryDefinition definition) {
        this.registerBeans(definition.getBeanClass().toArray(new Class[0]));
    }


    /**
     * Register bean post process,e.g:injection
     *
     * @param beanFactory
     * @throws Exception
     */
    private void registerBeanPostProcess(BeanFactory beanFactory) throws Exception {
        new BeanPostProcessor(beanFactory).process();
    }

    /**
     * 注册内置功能bean
     *
     * @param cls
     * @param object
     */
    private void registerBuiltinComponent(Class<?> cls, Object object) {
        Object cache = ThreadLevelCache.get(cls);
        if (cache != null) {
            // 支持对象注入
            registerBean(cls, cache);
        } else {
            registerBean(cls, object);
            // 支持静态引用
            ThreadLevelCache.cache(cls, object);
            threadLevelCache.put(cls, object);
        }
    }

    /**
     * Create Keywords.
     *
     * @param beanFactory
     * @return
     */
    private void createKeywords(BeanFactory beanFactory) {
        IKeywordFactory keywordFactory = new KeywordFactory();
        keywords = keywordFactory.createKeywords(beanFactory.getAllBeans());
    }

    /**
     * 执行初始化后接口方法
     *
     * @param beanFactory
     * @throws Exception
     */
    private void postProcessAfterInitialization(BeanFactory beanFactory, boolean onlyScan) throws Exception {
        Assert.notNull(beanFactory);
        if (!onlyScan) {
            logger.debug("执行InitializingBean方法.");
            Collection<Object> allBeans = beanFactory.getAllBeans();
            for (Object bean : allBeans) {
                if (InitializingBean.class.isAssignableFrom(bean.getClass())) {
                    InitializingBean initializingBean = (InitializingBean) bean;
                    initializingBean.afterPropertiesSet();
                }
            }
        }
    }
}