package cc.smtweb.framework.core.mvc.controller.scan;

import cc.smtweb.framework.core.exception.SwException;
import cc.smtweb.framework.core.mvc.controller.IActionManager;
import cc.smtweb.framework.core.mvc.controller.IStartListener;
import cc.smtweb.framework.core.mvc.controller.access.ControllerAccess;
import cc.smtweb.framework.core.mvc.controller.access.IMethodAccess;
import cc.smtweb.framework.core.mvc.controller.access.MethodAccess;
import cc.smtweb.framework.core.mvc.controller.access.SchedulerMethodAccess;
import cc.smtweb.framework.core.mvc.controller.binder.BeanContext;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
public class BeanManager implements IActionManager {
    private ApplicationContext applicationContext;
    private List<ControllerAccess> beans = new ArrayList<>();
    private Map<Class<?>, Object> beanMap = new HashMap<>();
    private List<ControllerAccess> singletonServices = new ArrayList<>();
    @Getter
    private Map<String, IMethodAccess> controllers = new HashMap<>();
    @Getter
    private BeanContext beanContext;
    @Getter
    private List<SchedulerMethodAccess> tasks = new ArrayList<>();

    private List<OrderMethodAccess> constructMethods = new ArrayList<>();
    private List<OrderMethodAccess> destroyMethods = new ArrayList<>();
    private List<IMethodAccess> beanMethods = new ArrayList<>();

    private Map<String, Class<?>> mapTableClass = new HashMap<>();
    private List<IStartListener> listStartListener = new ArrayList<>();

    private static BeanManager instance = null;

    static {
        instance = new BeanManager();
    }

    public static BeanManager getInstance() {
        return instance;
    }

    private BeanManager() {
    }

    public void install(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        beanContext = new BeanContext(beanMap, applicationContext);
    }

    public void init() throws Exception {
        // 读取DbEngine，用以初始化 EntityDao
        beanContext.init();

        // 自动设置@SwBean注解对象的@SwParam注解属性
        for (ControllerAccess controllerAccess : beans) {
            Object bean = controllerAccess.getSingletonInstance();
            addBeanMap(controllerAccess.getBeanType(), bean);
        }

        // 映射单例Bean字段值
        for (ControllerAccess controllerAccess : beans) {
            controllerAccess.initSingletonFields(beanContext, null);
        }

        for (IMethodAccess methodAccess : beanMethods) {
            Object bean = methodAccess.invoke(beanContext);
            addBeanMap(methodAccess.getReturnType(), bean);
            log.debug("[smt]init bean:" + methodAccess.getReturnType() + " by " + methodAccess.fullName());
        }

        // 映射单例控制器字段值
        for (ControllerAccess controllerAccess : singletonServices) {
            controllerAccess.initSingletonFields(beanContext, null);
        }

        // 执行初始化方法
        constructMethods.sort((a, b) -> a.order - b.order);

        for (OrderMethodAccess orderMethodAccess : constructMethods) {
            IMethodAccess methodAccess = orderMethodAccess.methodAccess;
            methodAccess.invoke(beanContext);
        }
    }

    private void addBeanMap(Class<?> beanType, Object bean) {
        if (bean != null) {
            beanMap.put(beanType, bean);
        }
    }

    IMethodAccess putIfAbsent(String url, IMethodAccess methodAccess) {
        return controllers.putIfAbsent(url, methodAccess);
    }

    void addBean(ControllerAccess controllerAccess) {
        this.beans.add(controllerAccess);
    }

    void addSingletonController(ControllerAccess controllerAccess) {
        this.singletonServices.add(controllerAccess);
    }

    void addTask(SchedulerMethodAccess schedulerMethodAccess) {
        this.tasks.add(schedulerMethodAccess);
    }

    void addConstruct(int order, MethodAccess methodAccess) {
        constructMethods.add(new OrderMethodAccess(order, methodAccess));
    }

    void addDestroy(int order, MethodAccess methodAccess) {
        destroyMethods.add(new OrderMethodAccess(order, methodAccess));
    }

    public MethodAccess[] loadDestroyMethods() {
        if (destroyMethods.isEmpty()) {
            return null;
        }

        destroyMethods.sort((a, b) -> b.order - a.order);

        int size = destroyMethods.size();
        MethodAccess[] result = new MethodAccess[size];
        for (int i = 0; i < size; i++) {
            result[i] = destroyMethods.get(i).methodAccess;
        }

        return result;
    }

    void addBeanMethod(IMethodAccess methodAccess) {
        this.beanMethods.add(methodAccess);
    }

    void addStartListener(Class<?> clazz) {
        if (!IStartListener.class.isAssignableFrom(clazz)) {
            throw new SwException("启动监听类必须实现IStartListener接口！");
        }
        try {
            IStartListener startListener = (IStartListener) clazz.newInstance();
            listStartListener.add(startListener);
        } catch (Exception e) {
            throw new SwException(e);
        }
    }

    public List<IStartListener> getStartListeners() {
        return listStartListener;
    }

    @Override
    public boolean api(String url, IMethodAccess methodAccess) {
        return this.putIfAbsent(url, methodAccess) == null;
    }

    public void addTableClass(String tableName, Class<?> clzz) {
        mapTableClass.put(tableName, clzz);
    }

    public Class<?> getTableClass(String tableName) {
        return mapTableClass.get(tableName);
    }

    private static class OrderMethodAccess {
        private final int order;
        private final MethodAccess methodAccess;

        OrderMethodAccess(int order, MethodAccess methodAccess) {
            this.order = order;
            this.methodAccess = methodAccess;
        }
    }
}
