package com.taobao.arthas.core.config;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

import static com.taobao.arthas.core.util.ArthasCheckUtils.isEquals;
import static com.taobao.arthas.core.util.ArthasCheckUtils.isIn;
import static com.taobao.arthas.core.util.StringUtils.isBlank;

/**
 * Feature编解器(线程安全)<br/>
 * <p/>
 * 用于封装系统内部features/attribute等扩展字段的管理
 * Created by dukun on 15/3/31.
 */
public class FeatureCodec {

    /**
     * KV片段分割符<br/>
     * KV片段定义为一个完整的KV对，例如字符串<span>;k1=v1;k2=v2;</span>
     * 其中<b>;</b>即为KV片段分隔符
     */
    private final char kvSegmentSeparator;

    /**
     * KV分割符<br/>
     * KV定义为一个KV对区分K和V的分割符号，例如字符串<span>k1=v1</span>
     * 其中<b>=</b>即为KV分隔符
     */
    private final char kvSeparator;

    /**
     * 转义前缀符
     */
    private static final char ESCAPE_PREFIX_CHAR = '\\';

    /**
     * 使用指定的KV分割符构造FeatureParser<br/>
     *
     * @param kvSegmentSeparator KV对之间的分隔符
     * @param kvSeparator        K与V之间的分隔符
     */
    public FeatureCodec(final char kvSegmentSeparator, final char kvSeparator) {

        // 分隔符禁止与转义前缀符相等
        if (isIn(ESCAPE_PREFIX_CHAR, kvSegmentSeparator, kvSeparator)) {
            throw new IllegalArgumentException("separator can not init to '" + ESCAPE_PREFIX_CHAR + "'.");
        }

        this.kvSegmentSeparator = kvSegmentSeparator;
        this.kvSeparator = kvSeparator;
    }

    /**
     * map集合转换到feature字符串
     *
     * @param map map集合
     * @return feature字符串
     */
    public String toString(final Map<String, String> map) {

        final StringBuilder featureSB = new StringBuilder().append(kvSegmentSeparator);

        if (null == map
                || map.isEmpty()) {
            return featureSB.toString();
        }

        for (Map.Entry<String, String> entry : map.entrySet()) {

            featureSB
                    .append(escapeEncode(entry.getKey()))
                    .append(kvSeparator)
                    .append(escapeEncode(entry.getValue()))
                    .append(kvSegmentSeparator)
            ;

        }

        return featureSB.toString();
    }


    /**
     * feature字符串转换到map集合
     *
     * @param featureString the feature string
     * @return the map
     */
    public Map<String, String> toMap(final String featureString) {

        final Map<String, String> map = new HashMap<String, String>();

        if (isBlank(featureString)) {
            return map;
        }

        for (String kv : escapeSplit(featureString, kvSegmentSeparator)) {

            if (isBlank(kv)) {
                // 过滤掉为空的字符串片段
                continue;
            }

            final String[] ar = escapeSplit(kv, kvSeparator);
            if (ar.length != 2) {
                // 过滤掉不符合K:V单目的情况
                continue;
            }

            final String k = ar[0];
            final String v = ar[1];
            if (!isBlank(k)
                    && !isBlank(v)) {
                map.put(escapeDecode(k), escapeDecode(v));
            }

        }

        return map;
    }

    /**
     * 转义编码
     *
     * @param string 原始字符串
     * @return 转义编码后的字符串
     */
    private String escapeEncode(final String string) {
        final StringBuilder returnSB = new StringBuilder();
        for (final char c : string.toCharArray()) {
            if (isIn(c, kvSegmentSeparator, kvSeparator, ESCAPE_PREFIX_CHAR)) {
                returnSB.append(ESCAPE_PREFIX_CHAR);
            }
            returnSB.append(c);
        }

        return returnSB.toString();
    }

    /**
     * 转义解码
     *
     * @param string 编码字符串
     * @return 转义解码后的字符串
     */
    private String escapeDecode(String string) {

        final StringBuilder segmentSB = new StringBuilder();
        final int stringLength = string.length();

        for (int index = 0; index < stringLength; index++) {

            final char c = string.charAt(index);

            if (isEquals(c, ESCAPE_PREFIX_CHAR)
                    && index < stringLength - 1) {

                final char nextChar = string.charAt(++index);

                // 下一个字符是转义符
                if (isIn(nextChar, kvSegmentSeparator, kvSeparator, ESCAPE_PREFIX_CHAR)) {
                    segmentSB.append(nextChar);
                }

                // 如果不是转义字符，则需要两个都放入
                else {
                    segmentSB.append(c);
                    segmentSB.append(nextChar);
                }
            } else {
                segmentSB.append(c);
            }

        }

        return segmentSB.toString();
    }

    /**
     * 编码字符串拆分
     *
     * @param string          编码字符串
     * @param splitEscapeChar 分割符
     * @return 拆分后的字符串数组
     */
    private String[] escapeSplit(String string, char splitEscapeChar) {

        final ArrayList<String> segmentArrayList = new ArrayList<String>();
        final Stack<Character> decodeStack = new Stack<Character>();
        final int stringLength = string.length();

        for (int index = 0; index < stringLength; index++) {

            boolean isArchive = false;

            final char c = string.charAt(index);

            // 匹配到转义前缀符
            if (isEquals(c, ESCAPE_PREFIX_CHAR)) {

                decodeStack.push(c);
                if (index < stringLength - 1) {
                    final char nextChar = string.charAt(++index);
                    decodeStack.push(nextChar);
                }

            }

            // 匹配到分割符
            else if (isEquals(c, splitEscapeChar)) {
                isArchive = true;
            }

            // 匹配到其他字符
            else {
                decodeStack.push(c);
            }

            if (isArchive
                    || index == stringLength - 1) {
                final StringBuilder segmentSB = new StringBuilder(decodeStack.size());
                while (!decodeStack.isEmpty()) {
                    segmentSB.append(decodeStack.pop());
                }

                segmentArrayList.add(
                        segmentSB
                                .reverse()  // 因为堆栈中是逆序的,所以需要对逆序的字符串再次逆序
                                .toString() // toString
                                .trim()     // 考虑到字符串片段可能会出现首尾空格的场景，这里做一个过滤
                );
            }

        }

        return segmentArrayList.toArray(new String[segmentArrayList.size()]);
    }


}