package com.brilliance.isc.doc.utils;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.*;

import java.math.BigDecimal;

/**
 * XML解析器，将XML解析为JSON对象
 *
 * @author JSON.org, looly
 * @since 5.7.11
 */
public class JSONXMLParserSSTF {

    /**
     * 转换XML为JSONObject
     * 转换过程中一些信息可能会丢失，JSON中无法区分节点和属性，相同的节点将被处理为JSONArray。
     *
     * @param jo          JSONObject
     * @param xmlStr      XML字符串
     * @param keepStrings 如果为{@code true}，则值保持String类型，不转换为数字或boolean
     * @throws JSONException 解析异常
     */
    public static void parseJSONObject(JSONObject jo, String xmlStr, boolean keepStrings) throws JSONException {
        XMLTokener x = new XMLTokener(xmlStr, jo.getConfig());
        while (x.more() && x.skipPast("<")) {
            parse(x, jo, null, keepStrings);
        }
    }

    /**
     * Scan the content following the named tag, attaching it to the context.
     *
     * @param x       The XMLTokener containing the source string.
     * @param context The JSONObject that will include the new material.
     * @param name    The tag name.
     * @return true if the close tag is processed.
     * @throws JSONException JSON异常
     */
    private static boolean parse(XMLTokener x, JSONObject context, String name, boolean keepStrings) throws JSONException {
        char c;
        int i;
        JSONObject jsonobject;
        String string;
        String tagName;
        Object token;

        token = x.nextToken();

        if (token == XML.BANG) {
            c = x.next();
            if (c == '-') {
                if (x.next() == '-') {
                    x.skipPast("-->");
                    return false;
                }
                x.back();
            } else if (c == '[') {
                token = x.nextToken();
                if ("CDATA".equals(token)) {
                    if (x.next() == '[') {
                        string = x.nextCDATA();
                        if (string.length() > 0) {
                            context.accumulate("content", string);
                        }
                        return false;
                    }
                }
                throw x.syntaxError("Expected 'CDATA['");
            }
            i = 1;
            do {
                token = x.nextMeta();
                if (token == null) {
                    throw x.syntaxError("Missing '>' after '<!'.");
                } else if (token == XML.LT) {
                    i += 1;
                } else if (token == XML.GT) {
                    i -= 1;
                }
            } while (i > 0);
            return false;
        } else if (token == XML.QUEST) {

            // <?
            x.skipPast("?>");
            return false;
        } else if (token == XML.SLASH) {

            // Close tag </

            token = x.nextToken();
            if (name == null) {
                throw x.syntaxError("Mismatched close tag " + token);
            }
            if (!token.equals(name)) {
                throw x.syntaxError("Mismatched " + name + " and " + token);
            }
            if (x.nextToken() != XML.GT) {
                throw x.syntaxError("Misshaped close tag");
            }
            return true;

        } else if (token instanceof Character) {
            throw x.syntaxError("Misshaped tag");

            // Open tag <

        } else {
            tagName = (String) token;
            token = null;
            jsonobject = new JSONObject(context.getConfig().isOrder());
            for (; ; ) {
                if (token == null) {
                    token = x.nextToken();
                }

                // attribute = value
                if (token instanceof String) {
                    string = (String) token;
                    token = x.nextToken();
                    if (token == XML.EQ) {
                        token = x.nextToken();
                        if (!(token instanceof String)) {
                            throw x.syntaxError("Missing value");
                        }
                        jsonobject.accumulate(string, keepStrings ? token : stringToValue((String) token));
                        token = null;
                    } else {
                        jsonobject.accumulate(string, "");
                    }

                } else if (token == XML.SLASH) {
                    // Empty tag <.../>
                    if (x.nextToken() != XML.GT) {
                        throw x.syntaxError("Misshaped tag");
                    }
                    if (jsonobject.size() > 0) {
                        context.accumulate(tagName, jsonobject);
                    } else {
                        context.accumulate(tagName, "");
                    }
                    return false;

                } else if (token == XML.GT) {
                    // Content, between <...> and </...>
                    for (; ; ) {
                        token = x.nextContent();
                        if (token == null) {
                            if (tagName != null) {
                                throw x.syntaxError("Unclosed tag " + tagName);
                            }
                            return false;
                        } else if (token instanceof String) {
                            string = (String) token;
                            if (string.length() > 0) {
                                jsonobject.accumulate("content", keepStrings ? token : stringToValue(string));
                            }

                        } else if (token == XML.LT) {
                            // Nested element
                            if (parse(x, jsonobject, tagName, keepStrings)) {
                                if (jsonobject.size() == 0) {
                                    context.accumulate(tagName, "");
                                } else if (jsonobject.size() == 1 && jsonobject.get("content") != null) {
                                    context.accumulate(tagName, jsonobject.get("content"));
                                } else {
                                    context.accumulate(tagName, jsonobject);
                                }
                                return false;
                            }
                        }
                    }
                } else {
                    throw x.syntaxError("Misshaped tag");
                }
            }
        }
    }

    /**
     * 尝试转换字符串为number, boolean, or null，无法转换返回String
     *
     * @param string A String.
     * @return A simple JSON value.
     */
    public static Object stringToValue(String string) {
        // null处理
        if (StrUtil.isEmpty(string) || StrUtil.NULL.equalsIgnoreCase(string)) {
            return JSONNull.NULL;
        }

        // boolean处理
        if ("true".equalsIgnoreCase(string)) {
            return Boolean.TRUE;
        }
        if ("false".equalsIgnoreCase(string)) {
            return Boolean.FALSE;
        }

        // Number处理
        char b = string.charAt(0);
        if ((b >= '0' && b <= '9') || b == '-') {
            try {
                if (StrUtil.containsAnyIgnoreCase(string, ".", "e")) {
                    // pr#192@Gitee，Double会出现小数精度丢失问题，此处使用BigDecimal
                    return new BigDecimal(string);
                } else {
                    final long myLong = Long.parseLong(string);
                    if (string.equals(Long.toString(myLong))) {
                        if (myLong == (int) myLong) {
                            return (int) myLong;
                        } else {
                            return myLong;
                        }
                    }
                }
            } catch (Exception ignore) {
            }
        }

        // 其它情况返回原String值下
        return string;
    }

    /**
     * 转换XML为JSONObject
     * 转换过程中一些信息可能会丢失，JSON中无法区分节点和属性，相同的节点将被处理为JSONArray。
     *
     * @param jo          JSONObject
     * @param xmlStr      XML字符串
     * @param keepStrings 如果为{@code true}，则值保持String类型，不转换为数字或boolean
     * @return A JSONObject 解析后的JSON对象，与传入的jo为同一对象
     * @throws JSONException 解析异常
     * @since 5.3.1
     */
    public static JSONObject toJSONObject(JSONObject jo, String xmlStr, boolean keepStrings) throws JSONException {
        JSONXMLParserSSTF.parseJSONObject(jo, xmlStr, keepStrings);
        return jo;
    }
}
