package com.yvan.spring;


import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.util.StatusPrinter;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.dom.DOMDocument;
import org.dom4j.dom.DOMElement;
import org.dom4j.io.SAXReader;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * 根据inputStream (配置内容参考logback.xml)，重新初始化 Logback 的配置
 * Created by luoyifan on 2018/2/27.
 */
public class LogBackConfigLoader {

    private static final String PROCESS_ID_KEY = "PROCESS_ID";
    private static final String APP_NAME_KEY = "APP_NAME";
    private static final String PROPERTY_KEY = "property";
    private static final String OTHER_KEY = "other";
    private static final String APPENDER_KEY = "appender";
    private static final String LOGGER_KEY = "logger";
    private static final String ROOT_KEY = "root";

    public static void load(InputStream inputStream) {
        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
        JoranConfigurator configurator = new JoranConfigurator();
        configurator.setContext(lc);
        lc.reset();
        try {
            configurator.doConfigure(inputStream);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        StatusPrinter.printInCaseOfErrorsOrWarnings(lc);
    }

    public static String mergeLogbackConfigurationString(List<String> configPaths, boolean isDebug, String appName) throws IOException, DocumentException {
        ResourceLoader resourceLoader = new DefaultResourceLoader();

        Document t = new DOMDocument();
        Element configuration = new DOMElement("configuration");
        if (isDebug) {
            configuration.addAttribute("debug", "true");
        }
        t.setRootElement(configuration);

        Map<String, Node> nodeProperty = Maps.newLinkedHashMap();
        Map<String, Node> nodeAppender = Maps.newLinkedHashMap();
        Map<String, Node> nodeLogger = Maps.newLinkedHashMap();
        Node nodeRoot = null;
        List<Node> nodeOther = Lists.newArrayList();

        Map<String, String> maps = setupLogbackProperties(appName);
        for (Map.Entry<String, String> entry : maps.entrySet()) {
            DOMElement element = new DOMElement(PROPERTY_KEY);
            element.setAttribute("name", entry.getKey());
            element.setAttribute("value", entry.getValue());
            nodeProperty.put(entry.getKey(), element);
        }

        for (String v : configPaths) {
            SAXReader saxReader = new SAXReader();
            org.dom4j.Document source = saxReader.read(resourceLoader.getResource(v).getInputStream());
            for (org.dom4j.Element element : (List<Element>) source.getDocument().getRootElement().elements()) {
                if (element.getName().equalsIgnoreCase(PROPERTY_KEY)) {
                    nodeProperty.put(element.attributeValue("name"), element.detach());

                } else if (element.getName().equalsIgnoreCase(APPENDER_KEY)) {
                    nodeAppender.put(element.attributeValue("name"), element.detach());

                } else if (element.getName().equalsIgnoreCase(LOGGER_KEY)) {
                    nodeLogger.put(element.attributeValue("name"), element.detach());

                } else if (element.getName().equalsIgnoreCase(ROOT_KEY)) {
                    nodeRoot = element.detach();

                } else {
                    nodeOther.add(element.detach());

                }
            }
        }

        addNodeToDocument(nodeProperty.values(), configuration);
        addNodeToDocument(nodeOther, configuration);
        addNodeToDocument(nodeAppender.values(), configuration);
        addNodeToDocument(nodeLogger.values(), configuration);
        if (nodeRoot != null) {
            configuration.add(nodeRoot);
        }

        return t.asXML();
    }

    private static void addNodeToDocument(Collection<Node> nodes, Element configuration) {
        if (nodes == null || nodes.size() <= 0)
            return;
        for (Node node : nodes) {
            configuration.add(node);
        }
    }

    private static Map<String, String> setupLogbackProperties(String appName) {
        Map<String, String> r = Maps.newLinkedHashMap();
        r.put(PROCESS_ID_KEY, ManagementFactory.getRuntimeMXBean().getName());
        r.put(APP_NAME_KEY, appName);
        return r;
    }

    // 传入 spring-resource 描述的 location 集合, 执行对应的日志配置
    public static void load(List<String> logbackConfigs, boolean isDebug, String appName) throws Exception {

        String xmlContent = mergeLogbackConfigurationString(logbackConfigs, isDebug, appName);
        //String xmlTarget = outStream.toString();
        ByteArrayInputStream inputStream = new ByteArrayInputStream(xmlContent.getBytes("utf-8"));
        load(inputStream);
    }

    /*
    static ByteArrayOutputStream mergeLogbackConfiguration(List<String> configPaths, boolean isDebug) throws IOException, DocumentException {

        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document t = db.newDocument();

        Element configuration = t.createElement("configuration");
        configuration.setAttribute("debug", Boolean.toString(isDebug));
        t.appendChild(configuration);

        for (String location : logbackConfigs) {
            InputStream is = resourceLoader.getResource(location).getInputStream();
            if (is == null) {
                continue;
            }
            Document s = db.parse(is);
        }

        ByteArrayOutputStream outStream = new ByteArrayOutputStream();

        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        DOMSource source = new DOMSource(t);
        transformer.setOutputProperty(OutputKeys.ENCODING, "utf-8");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.transform(source, new StreamResult(outStream));

    }
    */
}
