package com.github.twitch4j.common.util;

import org.apache.commons.lang3.StringUtils;

public class EscapeUtils {

    /**
     * Escapes a value for use in an IRCv3 message tag.
     *
     * @param value the unescaped message tag value
     * @return the escaped tag value
     * @see <a href="https://ircv3.net/specs/extensions/message-tags.html">Offical spec</a>
     */
    public static String escapeTagValue(Object value) {
        if (value == null) return "";
        char[] unescaped = value.toString().toCharArray();
        StringBuilder sb = new StringBuilder(unescaped.length);
        for (char c : unescaped) {
            switch (c) {
                case ';':
                    sb.append("\\:");
                    break;
                case ' ':
                    sb.append("\\s");
                    break;
                case '\\':
                    sb.append("\\\\");
                    break;
                case '\r':
                    sb.append("\\r");
                    break;
                case '\n':
                    sb.append("\\n");
                    break;
                default:
                    sb.append(c);
                    break;
            }
        }
        return sb.toString();
    }

    /**
     * Unescapes a value used in a IRCv3 message tag.
     *
     * @param value the escaped message tag value
     * @return the unescaped value
     * @see <a href="https://ircv3.net/specs/extensions/message-tags.html">Offical spec</a>
     */
    public static String unescapeTagValue(String value) {
        return StringUtils.replaceEach(
            value,
            new String[] {
                "\\:",
                "\\s",
                "\\\\",
                "\\r",
                "\\n"
            },
            new String[] {
                ";",
                " ",
                "\\",
                "\r",
                "\n"
            }
        );
    }

}

@@ -437,17 +437,103 @@
     
-if (value
+final String unescapedString;%0A        if (value == null %7C%7C (unescapedString = value.toString())
  == 
@@ -549,24 +549,25 @@
 urn %22%22;%0A
+%0A
         
 char%5B%5D u
@@ -558,16 +558,22 @@
         
+final 
 char%5B%5D u
@@ -587,125 +587,1010 @@
 d = 
-value.toString().toCharArray();%0A        StringBuilder sb = new StringBuilder(unescaped.length);%0A        for (
+unescapedString.toCharArray();%0A        final int n = unescaped.length;%0A%0A        // Determine the index of the first replacement needed in the string%0A        int firstReplacement = -1;%0A        for (int i = 0; i %3C n; i++) %7B%0A            char c = unescaped%5Bi%5D;%0A            if (c == ';' %7C%7C c == ' ' %7C%7C c == '%5C%5C' %7C%7C c == '%5Cr' %7C%7C c == '%5Cn') %7B%0A                firstReplacement = i;%0A                break;%0A            %7D%0A        %7D%0A%0A        // When no replacements are needed, skip allocating the StringBuilder and copying over the chars%0A        if (firstReplacement %3C 0) return unescapedString;%0A%0A        // Otherwise: replacement(s) are needed%0A        final StringBuilder sb = new StringBuilder(n + 1); // Set capacity to length of string plus one replacement%0A        sb.append(unescaped, 0, firstReplacement); // Copy over the region of the string that requires no replacements%0A        for (int i = firstReplacement; i %3C n; i++) %7B // Perform replacements on the rest of the string as needed%0A            
 char c 
-:
+=
  une
@@ -1595,19 +1595,20 @@
 nescaped
-) %7B
+%5Bi%5D;
 %0A       

optimize escapeTagValue for no replacements case