import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.SAXBuilder;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.util.*;
import java.util.Map.Entry;

/**
 * 实现自己的前端控制器
 *
 * @author allen
 */
public class DispatcherServlet extends HttpServlet {

    // 包扫描路径
    private String basePackage = "";

    /** 类的全路径名集合(所有类)*/
    private List<String> classNames = new ArrayList<>();

    /**
     * bean 容器 (spring创建对象,要放到容器---保存起来)
     */
    private Map<String, Object> beanContain = new HashMap<>();

    // 保存url和contrller对象以及具体方法的映射关系
    private Map<String, Method> handlerMapping = new HashMap<>();
    private Map<String, Object> controllerMap = new HashMap<>();

    @Override
    public void init(ServletConfig config) throws ServletException {

        try {
            // 1.加载配置文件
            doLoadConfig(config.getInitParameter("contextConfigLocation"));
            // 2.初始化所有相关联的类,扫描用户设定的包下面所有的类
            doScanner(basePackage);

            // 3.拿到扫描到的类,通过反射机制,实例化,并且放到ioc容器中(k-v beanName-bean) beanName默认是首字母小写
            doInstance();

            // 4.实现注入,主要针对service注入到controller
            doIoc();

            // 5.初始化HandlerMapping(将url和method对应上)
            initHandlerMapping();

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            // 处理请求
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("500!! Server Exception");
        }
    }
    /** spring mvc 也是同样的实现 */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        String url = req.getServletPath(); // 拿到URL ,根据url去找到对应的类, 调用对应的方法(debug)

        Method method = this.handlerMapping.get(url);
        if (method == null) {
            resp.getWriter().write("404 NOT FOUND!");
            return;
        }

        // 获取方法的参数列表
        Parameter[] parameters = method.getParameters();
        // 参数值--参数注入/解析/处理比较简陋
        Object[] paramValues = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            // 根据参数名称,做某些处理
            String requestParam = parameter.getType().getSimpleName();
            if (requestParam.equals("HttpServletRequest")) {
                // 参数类型已明确,这边强转类型
                paramValues[i] = req;
                continue;
            }
            if (requestParam.equals("HttpServletResponse")) {
                paramValues[i] = resp;
                continue;
            }
            if (requestParam.equals("String")) {
                String[] values = req.getParameterMap().get(parameter.getName()); // 请求中携带的参数值
                String value = Arrays.toString(values).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
                paramValues[i] = value;
            }
        }
        // 利用反射机制来调用 controller对象中的方法
        Object controller = this.beanContain.get(controllerMap.get(url));
        method.invoke(controller, paramValues);//第一个参数是method所对应的controller实例 在ioc容器中
    }

    /** 难点就是XML解析,复杂的XML */
    private void doLoadConfig(String location) throws Exception {
        if (location.startsWith("classpath:")) {
            location = location.replace("classpath:", "");
        } else if (location.contains("/")) {
            int lastSplitIndex = location.lastIndexOf('/');
            location = location.substring(lastSplitIndex + 1, location.length());
        }
        // 把web.xml中的contextConfigLocation对应value值的文件加载到留里面
        Class<? extends DispatcherServlet> aClass = this.getClass();
        URL file = aClass.getResource(location);
        System.out.println(file.toString());
        Document read_doc = new SAXBuilder().build(file);
        Element root = read_doc.getRootElement();
        for (Iterator<Element> itr = root.getChildren().iterator(); itr.hasNext(); ) {
            Element e = itr.next();
            if ("http://www.springframework.org/schema/context".equals(e.getNamespace().getURI())) {
                basePackage = e.getAttribute("base-package").getValue(); // TODO 获取扫描路径
            }

            if ("http://www.springframework.org/schema/mvc".equals(e.getNamespace().getURI())
                    && "annotation-driven".equals(e.getNamespace())) {
                System.out.println("开启注解");
            }
            // TODO 省略更多XML解析过程....
        }
    }

    /**
     * 扫描所有class文件(关注功能实现 --- 如果我写spring框架能够替换官方spring。还会纠结源码嘛?)
     */
    private void doScanner(String packageName) {
        // 把所有的.替换成/     com.study.mvc   com/study/mvc
        URL url = this.getClass().getResource("/" + packageName.replaceAll("\\.", "/") + "/");
        File dir = new File(url.getFile());
        for (File file : dir.listFiles()) {
            if (file.isDirectory()) {
                // 递归读取包
                doScanner(packageName + "." + file.getName());
            } else {
                String className = packageName + "." + file.getName().replace(".class", "");
                classNames.add(className);
                System.out.println("Spring容器扫面到的类有:" + packageName + "." + file.getName());
            }
        }
    }

    /**
     * 构建class对应的对象(加了指定注解,难点:)
     */
    private void doInstance() throws Exception {
        for (String className : classNames) {
            Class<?> clazz = Class.forName(className);
            // 把类搞出来,反射来实例化(加@Controller @Service需要实例化)
            if (clazz.isAnnotationPresent(Controller.class) || clazz.isAnnotationPresent(Service.class)) {
                // 创建一个对象,保存到IOC容器内
                beanContain.put(toLowerFirstWord(clazz.getSimpleName()), clazz.newInstance());
            } else {
                continue;
            }
        }
    }

    /**
     * 建立映射关系  url:/order/get/1  -->  method:
     */
    private void initHandlerMapping() throws IllegalAccessException, InstantiationException {
        for (Entry<String, Object> entry : beanContain.entrySet()) { // 遍历所有的对象
            Class controllerClass = entry.getValue().getClass();
            if (!controllerClass.isAnnotationPresent(Controller.class)) {
                continue; // 排除非Controller类
            }

            // 拼url时,是controller头的url拼上方法上的url
            String baseUrl = "";
            if (controllerClass.isAnnotationPresent(RequestMapping.class)) {
                RequestMapping controllerRequestMapping = (RequestMapping) controllerClass.getAnnotation(RequestMapping.class);
                baseUrl = controllerRequestMapping.value();
            }
            // 遍历所有方法,将方法和请求地址,形成映射
            Method[] methods = controllerClass.getMethods();
            for (Method method : methods) {
                if (!method.isAnnotationPresent(RequestMapping.class)) {
                    continue;
                }
                String requestUrl = method.getAnnotation(RequestMapping.class).value();
                requestUrl = (baseUrl + "/" + requestUrl).replaceAll("/+", "/");
                // 这里应该放置实例和method
                handlerMapping.put(requestUrl, method);

                // StudyController    map   =   "/study/test"  = new StudyController();
                controllerMap.put(requestUrl, entry.getKey());
            }
        }
    }

    /**
     * 以属性注入为主
     */
    private void doIoc() throws IllegalAccessException {
        for (Entry<String, Object> entry : beanContain.entrySet()) {
            // 拿到里面的所有属性
            Field fields[] = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true); // 可以访问私有属性
                if (!field.isAnnotationPresent(Resource.class)) {
                    continue;
                }
                Resource resource = field.getAnnotation(Resource.class);
                String target = resource.name();
                field.set(entry.getValue(), beanContain.get(target));
            }
        }
    }

    /**
     * 把字符串的首字母小写
     *
     * @param name
     * @return
     */
    private String toLowerFirstWord(String name) {
        char[] charArray = name.toCharArray();
        charArray[0] += 32;
        return String.valueOf(charArray);
    }
}