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

import cc.smtweb.framework.core.annotation.*;
import cc.smtweb.framework.core.mvc.controller.IEditor;
import cc.smtweb.framework.core.mvc.controller.access.ControllerAccess;
import cc.smtweb.framework.core.mvc.controller.access.MethodAccess;
import cc.smtweb.framework.core.mvc.controller.access.MethodParamAccess;
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.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.ResolvableType;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.ParseException;
import java.util.List;

@Slf4j
public class MethodParser {
    private final UrlMaker urlMaker;
    private final BeanManager controllers;
    private final WebDataBinder webDataBinder;
    private ScanContext scanContext;
//  private ControllerAccess controllerAccess;

    public MethodParser(UrlMaker urlMaker, ScanContext scanContext) {
        this.urlMaker = urlMaker;
        this.controllers = scanContext.getBeanManager();
        this.webDataBinder = scanContext.getWebDataBinder();
        this.scanContext = scanContext;
    }

    public void parse(Class<?> clazz, ControllerAccess controllerAccess, boolean isApi) throws ParseException {
//    this.controllerAccess = controllerAccess;
        // 扫描方法注解
        for (Method m : clazz.getMethods()) {
            int modifier = m.getModifiers();

            // && (R.class.isAssignableFrom(m.getReturnType()))
            if (Modifier.isPublic(modifier) && !Modifier.isStatic(modifier) && !Object.class.equals(m.getDeclaringClass())) {
                MethodAccess methodAccess = parseMethod(controllerAccess, m);

                if (scanContext.dealMethod(m, methodAccess)) {
                    if (isApi) {
                        urlMaker.addApi(clazz, m, methodAccess, controllers);
                    }
                }

//        SwScheduling scheduling = m.getAnnotation(SwScheduling.class);
//        SwConstruct construct = m.getAnnotation(SwConstruct.class);
//        SwDestroy destroy = m.getAnnotation(SwDestroy.class);
//        SwBean bean = m.getAnnotation(SwBean.class);
//        SwAction action = m.getAnnotation(SwAction.class);
//
//        if (scheduling != null) {
//          // 定时任务用seed值限定
//          log.debug("  add timer task: " + clazz.getSimpleName() + "." + m.getName());
//          controllers.addTask(new SchedulerMethodAccess(methodAccess, scheduling));
//        } else if(construct != null) {
//          controllers.addConstruct(construct.order(), methodAccess);
//        } else if(destroy != null) {
//          controllers.addDestroy(destroy.order(), methodAccess);
//        } else if(bean != null) {
//          controllers.addBeanMethod(methodAccess);
//        } else if(action != null) {
//          controllers.addAction(action, methodAccess);
//        } else if (isApi) {
//          urlMaker.addApi(clazz, m, methodAccess, controllers);
//        }
            }
        }
    }

    private MethodAccess parseMethod(ControllerAccess controllerAccess, Method method) {
        Class<?>[] paramTypes = method.getParameterTypes();
        Annotation[][] paramAnnotations = method.getParameterAnnotations();

        MethodParamAccess[] paramBinds = new MethodParamAccess[paramTypes.length];

        for (int i = 0; i < paramTypes.length; i++) {
            Class<?> paramType = paramTypes[i];
            String paramName = null;
            int bindType = ParamEditor.TYPE_UNKNOWN;
            if (!ObjectUtils.isEmpty(paramAnnotations[i])) {
                for (Annotation annotation : paramAnnotations[i]) {
                    // isAssignableFrom
                    Class<? extends Annotation> annotationType = annotation.annotationType();
                    if (SwParam.class.equals(annotationType)) {
                        bindType = ParamEditor.TYPE_PARAM;
                        String value = ((SwParam) annotation).value();
                        if (StringUtils.isNotBlank(value)) {
                            paramName = value;
                        }
                        break;
                    } else if (SwPathParam.class.equals(annotationType)) {
                        bindType = ParamEditor.TYPE_PATH;
                        break;
                    } else if (SwHeaderParam.class.equals(annotationType)) {
                        bindType = ParamEditor.TYPE_HEADER;
                        String value = ((SwHeaderParam) annotation).value();
                        if (StringUtils.isNotBlank(value)) {
                            paramName = value;
                        }
                        break;
                    } else if (SwBody.class.equals(annotationType)) {
                        bindType = ParamEditor.TYPE_BODY;
                        String value = ((SwBody) annotation).value();
                        if (StringUtils.isNotBlank(value)) {
                            paramName = value;
                        }
                        break;
                    } else if (SwAttr.class.equals(annotationType)) {
                        bindType = ParamEditor.TYPE_ATTR;
                        String value = ((SwAttr) annotation).value();
                        if (StringUtils.isNotBlank(value)) {
                            paramName = value;
                        }
                        break;
                    }
                }
            }

            IEditor editor = webDataBinder.findEditor(paramType, bindType);

            if (List.class.isAssignableFrom(paramType)) {
                // List bind类型使用泛型声明的对象
                ResolvableType resolvableType = ResolvableType.forMethodParameter(method, i);
                paramType = resolvableType.getGeneric(0).getRawClass();
                paramBinds[i] = new MethodParamAccess(editor, paramName, resolvableType.getGeneric(0).getRawClass(), bindType, paramType);
            } else {
                paramBinds[i] = new MethodParamAccess(editor, paramName, paramType, bindType, null);
            }

            if (editor == null) {
                log.error("not bind type:" + controllerAccess.getFullName() + "." + method.getName());
            }
        }

        return new MethodAccess(controllerAccess, method, paramBinds);
    }

}
