package net.northking.cctp.element.internal.utils;

import com.alibaba.fastjson.JSON;
import freemarker.cache.StringTemplateLoader;
import freemarker.core.TemplateElement;
import freemarker.template.Configuration;
import freemarker.template.Template;
import jodd.util.collection.MapEntry;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.StringWriter;
import java.util.*;

/**
 * TemplateHelper
 *
 * @author fengxiang.zhang
 * @since 1.0
 */
public class TemplateHelper {
    private final static Logger logger = LoggerFactory.getLogger(TemplateHelper.class);
    private static final Set<String> FREE_MARK_WORDS = new HashSet<>();
    private static final Configuration configuration;

    static {
        FREE_MARK_WORDS.add("as");
        configuration = configuration();
    }

    private static Configuration configuration() {
        Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
        StringTemplateLoader templateLoader = new StringTemplateLoader();
        configuration.setTemplateLoader(templateLoader);
        configuration.setDefaultEncoding("UTF-8");
        return configuration;
    }

    /**
     * 通过模板Code文本和MAP参数结构数据生成真实报文
     *
     * @param templateCode FM模板文本
     * @param params       MAP参数结构数据
     * @return
     */
    public static String processTemplate(String templateCode, Map<String, Object> params) {
        try {
            String name = UUID.randomUUID().toString();
            StringWriter stringWriter = new StringWriter();
            Template template = new Template(name, templateCode, configuration);
            template.process(params, stringWriter);
            return stringWriter.toString();
        } catch (Exception e) {
            throw new RuntimeException("您的模板与元数据字段不匹配，请仔细检查或者通过原始报文生成！",e);
        }
    }

    /**
     * 通过FM模板分析出对应的MAP参数数据结构
     *
     * @param templateCode FM模板文本
     * @return
     */
    public static Map<String, Object> toTemplateParamMap(String templateCode) throws Exception {
        String name = UUID.randomUUID().toString();
        Template template = new Template(name, templateCode, configuration);
        Map<String, Object> paramMap = new LinkedHashMap<>();
        Vector<TemplateElement> vector = new Vector<>();
        vector.add(template.getRootTreeNode());
        toTemplateParamMap(vector.elements(), paramMap, null);
        return paramMap;
    }

    /**
     * 验证模板语法
     *
     * @param templateCode
     * @return
     * @throws Exception
     */
    public static boolean validate(String templateCode) throws Exception {
        String name = UUID.randomUUID().toString();
        StringWriter stringWriter = new StringWriter();
        Template template = new Template(name, templateCode, configuration);
        template.dump(stringWriter);
        return true;
    }

    public static String handleDefaultValue(Object value) {
        return JSON.toJSONString(value == null ? "null" : value.toString());
    }


    public static boolean isFreeMarkWord(String str) {
        return FREE_MARK_WORDS.contains(str);
    }

    /**
     * 通过FM模板分析出对应的MAP参数数据结构
     *
     * @param enums FM节点元素
     * @param map   MAP参数
     * @param alias FM迭代块中变量名
     */
    private static void toTemplateParamMap(Enumeration<TemplateElement> enums, Map<String, Object> map, String alias) {
        while (enums.hasMoreElements()) {
            TemplateElement element = enums.nextElement();
            String nodeName = element.getNodeName();
            switch (nodeName) {
                case "TextBlock": {
                    break;
                }
                case "DollarVariable": {
                    String s = element.getSource();
                    Map.Entry<String, Object> entry = dollarVariable(s, alias);
                    if (entry != null) {
                        map.put(entry.getKey(), entry.getValue());
                    }
                    break;
                }
                case "IteratorBlock": {
                    String source = element.getSource();
                    String listedExp = StringUtils.substringBetween(source, "<#list", ">");
                    String loopVarName = StringUtils.substringBefore(listedExp, " as ");
                    if (loopVarName.contains("[")) {
                        // 模板中声明了变量值
                        break;
                    }
                    // 可能后面跟有其他处理函数：<#list productList?sort_by("id")?reverse as p>
                    loopVarName = StringUtils.substringBefore(loopVarName, "?").trim();
                    if (alias != null) {
                        loopVarName = StringUtils.removeStart(loopVarName, alias + ".");
                    }
                    String loopVar2Name = StringUtils.substringAfter(listedExp, " as ").trim();

                    List<Map<String, Object>> list = new ArrayList<>();
                    map.put(loopVarName, list);
                    for (int i = 0; i < 2; i++) {
                        // Mock List集合生成2条数据
                        Map<String, Object> subMap = new LinkedHashMap<>();
                        list.add(subMap);
                        if (element.getChildCount() > 0) {
                            toTemplateParamMap(element.children(), subMap, loopVar2Name);
                        }
                    }
                    // 空格内容都没有的空迭代块
                    break;
                }
                default:
                    //System.out.println(element.getNodeName());
                    toTemplateParamMap(element.children(), map, alias);
            }
        }
    }

    /**
     * 解析$表达式内容
     *
     * @param dollarStr $表达式
     * @param alias     如果在迭代块内，需传入该值
     */
    private static Map.Entry<String, Object> dollarVariable(String dollarStr, String alias) {
        String s = StringUtils.substringBetween(dollarStr, "${", "}").trim();
        if (isConstDollar(s)) {
            return null;
        }
        String varName;
        String value = "";
        if (s.contains("?")) {
            varName = StringUtils.substringBefore(s, "?").trim();
            value = extractDefaultValue(StringUtils.substringAfter(s, "?"));
        } else if (s.contains("!")) {
            varName = StringUtils.substringBefore(s, "!").trim();
            value = extractDefaultValue("!" + StringUtils.substringAfter(s, "!"));
        } else {
            varName = s;
        }
        if (alias != null) {
            varName = StringUtils.removeStart(varName, alias + ".");
        }
        return new MapEntry<>(varName, value);
    }

    /**
     * 从$表达式中提取默认值
     *
     * @param s $表达式
     */
    private static String extractDefaultValue(String s) {
        String value;
        if (s.contains("!")) {
            value = StringUtils.substringAfter(s, "!").trim();
        } else if (s.contains("default")) {
            value = StringUtils.substringBetween(s, "default", ")");
            value = StringUtils.substringAfter(value, "(").trim();
        } else {
            return "";
        }
        if (value.startsWith("'") || value.startsWith("\"")) {
            value = removeQuotWrap(value);
        } else if (value.contains(" ")) {
            // FreeMark 默认取值规则，没加引号取到第一个空格结束
            value = StringUtils.substringBefore(value, " ");
        }
        return value;
    }

    /**
     * 移除$表达式默认值外框引号
     */
    private static String removeQuotWrap(String s) {
        if (s.length() == 0) {
            return s;
        }
        String s1 = s.trim();
        char start = s1.charAt(0);
        if (start == '"' || start == '\'') {
            s1 = s1.substring(1);
        }
        char end = s1.charAt(s1.length() - 1);
        if (end == '"' || end == '\'') {
            s1 = s1.substring(0, s1.length() - 1);
        }
        return s1;
    }

    /**
     * ${"aaa"} = aaa
     *
     * @param dollarVar $表达式中内容
     */
    private static boolean isConstDollar(String dollarVar) {
        return StringUtils.startsWithAny(dollarVar, "r\"", "\"", "'", "r'");
    }

}
