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

import cc.smtweb.framework.core.annotation.*;
import cc.smtweb.framework.core.cache.AbstractCache;
import cc.smtweb.framework.core.cache.CacheManager;
import cc.smtweb.framework.core.db.DbEngine;
import cc.smtweb.framework.core.db.EntityDao;
import cc.smtweb.framework.core.mvc.controller.IEditor;
import cc.smtweb.framework.core.mvc.controller.access.BindFieldAccess;
import cc.smtweb.framework.core.mvc.controller.access.ControllerAccess;
import cc.smtweb.framework.core.mvc.controller.access.FieldAccess;
import cc.smtweb.framework.core.mvc.controller.access.SingletonFieldAccess;
import cc.smtweb.framework.core.mvc.controller.binder.ParamEditor;
import cc.smtweb.framework.core.mvc.controller.binder.WebDataBinder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.collection.SynchronizedCollection;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

@Slf4j
public class ClassParser {
    private BeanManager beanManager;
    private UrlMaker urlMaker;
    private WebDataBinder webDataBinder;
    private ScanContext scanContext;
    private Field singletonField;
    private boolean singleton;
    //  private List<ControllerAccess> cacheList;
    private MethodParser methodParser;
    private List<Class<?>> threadSafeTypes = new ArrayList<>();
    private DbEngine dbEngine;

    public ClassParser(UrlMaker urlMaker, ScanContext scanContext) {
        this.urlMaker = urlMaker;
        this.scanContext = scanContext;
        this.beanManager = scanContext.getBeanManager();
//    this.cacheList = cacheList;
        this.webDataBinder = scanContext.getWebDataBinder();
        this.methodParser = new MethodParser(urlMaker, scanContext);
        this.dbEngine = beanManager.getBeanContext().getBean(DbEngine.class);

        threadSafeTypes.add(AtomicBoolean.class);
        threadSafeTypes.add(AtomicInteger.class);
        threadSafeTypes.add(AtomicLong.class);
        threadSafeTypes.add(SynchronizedCollection.class);
        threadSafeTypes.add(ConcurrentMap.class);
    }

    public void parse(String dirName, Class<?> clazz) throws InstantiationException, IllegalAccessException, ParseException {
        SwBean swBean = clazz.getAnnotation(SwBean.class);

        // TODO ISwCache.class.isAssignableFrom(clazz)
        if (swBean != null) {
            ControllerAccess controllerAccess = buildControllerAccess(clazz, swBean.alwaysCreate());
            if (controllerAccess.isSingleton()) {
                controllerAccess.newSingletonInstance();
                log.debug("[smt]init bean: " + clazz.getName());
                beanManager.addBean(controllerAccess);

                methodParser.parse(clazz, controllerAccess, false);
            } else {
                log.error(clazz.getName() + " must be singleton, can not use field: " + singletonField.getName());
            }
            return;
        }

        SwService swService = clazz.getAnnotation(SwService.class);
        if (swService != null) {
            urlMaker.setServiceUrl(swService.value());
            log.debug("[smt]init service: " + clazz.getName());
            //Service类，非单例，以免多线程并发相互干扰
            ControllerAccess controllerAccess = buildControllerAccess(clazz, false);
            if (controllerAccess.isSingleton()) {
                controllerAccess.newSingletonInstance();
                beanManager.addSingletonController(controllerAccess);
            }

            // 处理url路径
            methodParser.parse(clazz, controllerAccess, true);
            return;
        }

        SwCache swCache = clazz.getAnnotation(SwCache.class);
        if (swCache != null) {
            CacheManager cacheManager = CacheManager.getIntance();
            cacheManager.install((AbstractCache) clazz.newInstance());
            return;
        }

        SwTable swTable = clazz.getAnnotation(SwTable.class);
        if (swTable != null) {
            beanManager.addTableClass(swTable.value(), clazz);
        }

        SwStartListener startListener = clazz.getAnnotation(SwStartListener.class);
        if (startListener != null) {
            beanManager.addStartListener(clazz);
        }
    }

    private ControllerAccess buildControllerAccess(Class<?> clazz, boolean alwaysCreate) {
        List<FieldAccess> fieldList = new ArrayList<>();
        singleton = true;
        singletonField = null;
        // 扫描本类和父类的 field
        Class<?> parentClazz = clazz;

        // !parentClazz.equals(stopClazz)
        while (parentClazz != null && !parentClazz.isInterface()) {
            parseFields(parentClazz, fieldList);
            parentClazz = parentClazz.getSuperclass();
        }

        return new ControllerAccess(clazz, fieldList, alwaysCreate || singleton);
    }

    private void parseFields(Class<?> clazz, List<FieldAccess> fieldList) {
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            FieldAccess fieldAccess = parseField(field);

            if (fieldAccess != null) {
                fieldList.add(fieldAccess);
            }
        }
    }

    private FieldAccess parseField(Field field) {
        // 得到属性上的注解
        SwParam swParam = field.getAnnotation(SwParam.class);

        if (swParam != null) {
            if (Object.class.equals(swParam.type())) {
                IEditor editor = webDataBinder.findEditor(field.getType(), ParamEditor.TYPE_FIELD);
                return new BindFieldAccess(editor, field, swParam.value());
            } else {
                EntityDao entityDao = dbEngine.findDao(swParam.type());
                return new SingletonFieldAccess(entityDao, field);
            }
        } else {
            if (singleton) {
                if (!isConstance(field) && !isThreadSafe(field)) {
                    // 记录首个发现的线程不安全字段
                    singletonField = field;
                    singleton = false;
                }
            }
        }

        return null;
    }

    private static boolean isConstance(Field field) {
        int fieldModifiers = field.getModifiers();
        return Modifier.isStatic(fieldModifiers) && Modifier.isFinal(fieldModifiers);
    }

    private boolean isThreadSafe(Field field) {
        Class fieldType = field.getDeclaringClass();
        for (Class<?> type : threadSafeTypes) {
            if (type.isAssignableFrom(fieldType)) {
                return true;
            }
        }

        return false;
    }

}

