package net.northking.cctp.element.util;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

import com.github.markusbernhardt.xmldoclet.xjc.*;
import com.github.markusbernhardt.xmldoclet.xjc.Class;
import com.github.markusbernhardt.xmldoclet.xjc.Package;
import net.northking.cctp.element.core.annotation.Keyword;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;

/**
 * Generate library documents,<br>
 * Java annotation documents are output as XML.<br>
 * Using jar:
 * <pre>
 * &lt;dependency&gt;
 * 	&lt;groupId&gt;com.github.markusbernhardt&lt;/groupId&gt;
 * 	&lt;artifactId&gt;xml-doclet&lt;/artifactId&gt;
 * 	&lt;version&gt;1.0.5&lt;/version&gt;
 * &lt;/dependency&gt;
 * </pre>
 *
 * @author fengxiang.zhang
 * @since 1.0
 */
public class Javadoc2Libdoc {

    public static final String LIBRARY_INTRO_KEY = "__intro__";

    private Map<String, String> keywordDocumentationMap;

    public Javadoc2Libdoc(java.lang.Class<?> clazz) {
        InputStream inputStream = clazz.getClassLoader()
                .getResourceAsStream(clazz.getName().replace('.', '/') + ".javadoc");
        if (inputStream == null) {
            keywordDocumentationMap = new HashMap<>();
            return;
        }
        Root root = loadJavadocRoot(inputStream);
        keywordDocumentationMap = loadKeywordDocumentationMap(root, clazz.getName());
    }

    public String getKeywordDocumentation(String keywordName) {
        return keywordDocumentationMap.get(keywordName);
    }

    protected Root loadJavadocRoot(InputStream inputStream) {
        try {
            JAXBContext context = JAXBContext.newInstance(Root.class);
            Unmarshaller unmarshaller = context.createUnmarshaller();
            return (Root) unmarshaller.unmarshal(inputStream);
        } catch (JAXBException e) {
            return new ObjectFactory().createRoot();
        }
    }

    protected Map<String, String> loadKeywordDocumentationMap(Root root, String className) {
        Map<String, String> keywordDocumentation = new HashMap<String, String>();
        for (Package packageNode : root.getPackage()) {
            for (Class classNode : packageNode.getClazz()) {
                if (className.equals(classNode.getQualified())) {
                    keywordDocumentation.put(LIBRARY_INTRO_KEY, formatComment(classNode));
                }
                for (Method methodNode : classNode.getMethod()) {
                    for (AnnotationInstance annotationInstanceNode : methodNode.getAnnotation()) {
                        if (annotationInstanceNode.getName().equals(ClassUtils.getShortClassName(Keyword.class))) {
                            keywordDocumentation.put(methodNode.getName(),
                                    formatComment(methodNode));
                            break;
                        }
                    }
                }
            }
        }
        return keywordDocumentation;
    }

    protected String formatComment(Class classNode) {
        if (classNode.getComment() != null) {
            return classNode.getComment();
        }
        return "";
    }

    protected String formatComment(Constructor constructorNode) {
        if (constructorNode.getComment() != null) {
            return constructorNode.getComment();
        }
        return "";
    }

    protected String formatComment(Method methodNode) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("<div style=\"font-size:14px;\">");
        stringBuilder.append(formatDeprecated(methodNode));
        if (methodNode.getComment() != null) {
            stringBuilder.append("<div>");
            stringBuilder.append(methodNode.getComment());
            stringBuilder.append("</div>");
        }
        stringBuilder.append("<dl>");
        stringBuilder.append(formatParam(methodNode));
        stringBuilder.append(formatReturn(methodNode));
        stringBuilder.append("</dl>");

        stringBuilder.append("</div>");
        return stringBuilder.toString();
    }

    static String formatJavaType(String type) {
        return StringUtils.replaceEach(type, new String[]{"java.lang.", "java.util."}, new String[]{"", ""});
    }

    protected String formatMethodDeclare(Method methodNode) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(formatJavaType(methodNode.getReturn().getQualified()));
        stringBuilder.append(" " + methodNode.getName());
        stringBuilder.append("(");
        MethodParameter parameter;
        int length = methodNode.getParameter().size();
        for (int i = 0; i < length; i++) {
            parameter = methodNode.getParameter().get(i);
            stringBuilder.append(formatJavaType(parameter.getType().getQualified()));
            stringBuilder.append(" " + parameter.getName());
            if (i < length - 1) {
                stringBuilder.append(", ");
            }
        }
        stringBuilder.append(")<br>");
        return stringBuilder.toString();
    }

    protected String formatParam(Method methodNode) {
        boolean hasTag = false;
        StringBuilder stringBuilderParam = new StringBuilder();
        stringBuilderParam.append("<dt><span style=\"font-size:12px;font-weight:bold;\">参数:</span></dt>");
        for (TagInfo tagInfo : methodNode.getTag()) {
            if (!tagInfo.getName().equals("@param")) {
                continue;
            }
            hasTag = true;
            String text = tagInfo.getText();
            int index = text.indexOf(' ');
            stringBuilderParam.append("<dd>");
            if (index > -1) {
                stringBuilderParam.append("<code>");
                stringBuilderParam.append(StringUtils.substring(text, 0, index));
                stringBuilderParam.append("</code> - ");
                stringBuilderParam.append(StringUtils.trim(StringUtils.substring(text, index + 1)));
            } else {
                stringBuilderParam.append("<code>");
                stringBuilderParam.append(text);
                stringBuilderParam.append("</code> - ");
                stringBuilderParam.append(" ");
            }
            stringBuilderParam.append("</dd>");
        }
        if (hasTag) {
            return stringBuilderParam.toString();
        }
        return "";
    }

    protected String formatReturn(Method methodNode) {
        boolean hasTag = false;
        StringBuilder stringBuilderParam = new StringBuilder();
        stringBuilderParam.append("<dt><span style=\"font-size:12px;font-weight:bold;\">返回:</span></dt>");
        for (TagInfo tagInfo : methodNode.getTag()) {
            if (!tagInfo.getName().equals("@return")) {
                continue;
            }
            hasTag = true;
            String text = tagInfo.getText();
            stringBuilderParam.append("<dd>");
            stringBuilderParam.append(text);
            stringBuilderParam.append("</dd>");
        }
        if (hasTag) {
            return stringBuilderParam.toString();
        }
        return "";
    }

    protected String formatSee(Method methodNode) {
        boolean hasTag = false;
        StringBuilder stringBuilderParam = new StringBuilder();
        stringBuilderParam.append("<br><b>See Also:</b><br>");
        for (TagInfo tagInfo : methodNode.getTag()) {
            if (!tagInfo.getName().equals("@see")) {
                continue;
            }
            hasTag = true;
            String camelCasedKeyword = tagInfo.getText();
            int index = camelCasedKeyword.indexOf('#');
            if (index >= 0) {
                camelCasedKeyword = camelCasedKeyword.substring(index + 1);
            }
            camelCasedKeyword = camelCasedKeyword.trim();

            stringBuilderParam.append("&nbsp;&nbsp;&nbsp;&nbsp;`");
            char[] camelCasedKeywordArray = camelCasedKeyword.toCharArray();
            stringBuilderParam.append(Character.toUpperCase(camelCasedKeywordArray[0]));
            for (int i = 1; i < camelCasedKeywordArray.length; i++) {
                if (camelCasedKeywordArray[i] >= 'A' && camelCasedKeywordArray[i] <= 'Z') {
                    stringBuilderParam.append(' ');
                }
                stringBuilderParam.append(camelCasedKeywordArray[i]);
            }
            stringBuilderParam.append("`<br>");
        }
        if (hasTag) {
            return stringBuilderParam.toString();
        }
        return "";
    }

    protected String formatDeprecated(Method methodNode) {
        String text = "";
        for (TagInfo tagInfo : methodNode.getTag()) {
            if (!tagInfo.getName().equals("@deprecated")) {
                continue;
            }
            text = tagInfo.getText();
        }
        if (StringUtils.isNotBlank(text)) {
            StringBuilder stringBuilderParam = new StringBuilder();
            stringBuilderParam.append("<div><span style=\"font-weight:bold;\">已过时。</span>&nbsp;");
            stringBuilderParam.append("<span style=\"font-style:italic\">");
            stringBuilderParam.append(text);
            stringBuilderParam.append("</span></div>");
            return stringBuilderParam.toString();
        }
        return "";
    }
}