/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.text;

import java.io.IOException;
import java.util.Iterator;
import java.util.Locale;
import java.util.Random;
import java.util.regex.Pattern;
import net.ranides.assira.collection.arrays.ArrayUtils;
import net.ranides.assira.math.MathUtils;
import net.ranides.benchmark.app.BenchmarkName;
import net.ranides.benchmark.app.BenchmarkRunner;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.runner.RunnerException;

/**
 * Benchmark for StringUtils.
 * 
 * uncamelize
 *  2C: CBuilder            =  61
 *  CC: CBuilder + count    = 100
 *  2B: StringBuilder       = 103
 *  RE: regex               = 870
 * 
 * uncamelize (tests on ZBook)
 *  2C: CBuilder            =  57
 *  CC: CBuilder + count    =  90
 *  2B: StringBuilder       =  92
 *  RE: regex               = 710
 *  SB: StrBuilder          =  85       builder performance: 92% StringBuilder
 *  SF: StrBuffer           =  58       builder performance: 63% StringBuilder
 * 
 * replace
 *    : assira              = 29
 *  AC: Apache Commons      = 37
 *  RE: String.replace      = 95
 *  
 * 
 * StringBuilder is slower because it checks (on every append) if reallocation is needed
 * 
 * StrBuilder is a bit faster than standard from JDK , but not significantly (92%)
 * 
 * StrBuffer is much faster (63%)
 *      It's almost in pair with hand-coded CBuilder. We don't use it in StringUtils because 
 *      it won't simplify code and tests showed that it would be a bit slower (65ns vs 57ns)
 * 
 * 
 */
public class StringUtilsBenchmark {
       
    @Benchmark
    @BenchmarkName("uncamelize: CBuilder")
    public int timeUncamelizeCBuilderCount(TextInput array) {
        int ret = 0;
        for(String s : array) {
            ret += uncamelizeCC(s).charAt(99);
        }
        return ret;
    }
    
    @Benchmark
    @BenchmarkName("uncamelize: CBuilder + 2N")
    public int timeUncamelizeCBuilder2N(TextInput array) {
        int ret = 0;
        for(String s : array) {
            ret += uncamelize2C(s).charAt(99);
        }
        return ret;
    }
    
    @Benchmark
    @BenchmarkName("uncamelize: regex")
    public int timeUncamelizeRE(TextInput array) {
        int ret = 0;
        for(String s : array) {
            ret += uncamelizeRE(s).charAt(99);
        }
        return ret;
    }
    
    @Benchmark
    @BenchmarkName("uncamelize: StringBuilder")
    public int timeUncamelizeStringBuilderCount(TextInput array) {
        int ret = 0;
        for(String s : array) {
            ret += uncamelize2B(s).charAt(99);
        }
        return ret;
    }
    
    @Benchmark
    @BenchmarkName("uncamelize: StrBuilder")
    public int timeUncamelizeStrBuilder2N(TextInput array) {
        int ret = 0;
        for(String s : array) {
            ret += uncamelize2StrBuilder(s).charAt(99);
        }
        return ret;
    }
    
    @Benchmark
    @BenchmarkName("uncamelize: StrBuffer")
    public int timeUncamelizeStrBuffer2N(TextInput array) {
        int ret = 0;
        for(String s : array) {
            ret += uncamelize2StrBuffer(s).charAt(99);
        }
        return ret;
    }
    
    @Benchmark
    @BenchmarkName("replace: StringBuilder")
    public int timeReplaceStringBuilder(TextInput array) {
        int ret = 0;
        for(String s : array) {
            ret += replaceSB(s, 'a', "abc").charAt(99);
        }
        return ret;
    }
    
    @Benchmark
    @BenchmarkName("replace: StrBuilder")
    public int timeReplaceStrBuilder(TextInput array) {
        int ret = 0;
        for(String s : array) {
            ret += replaceSB(s, 'a', "abc").charAt(99);
        }
        return ret;
    }
    
    @Benchmark
    @BenchmarkName("replace: regex")
    public int timeReplaceRE(TextInput array) {
        int ret = 0;
        for(String s : array) {
            ret += s.replace(String.valueOf('a'), "abc").charAt(99);
        }
        return ret;
    }
    
    @Benchmark
    @BenchmarkName("replace: apache")
    public int timeReplaceApache(TextInput array) {
        int ret = 0;
        for(String s : array) {
            ret += replaceApache(s, String.valueOf('a'), "abc").charAt(99);
        }
        return ret;
    }
    
    // count + CBuilder
    private static String uncamelizeCC(String value) {
        int u = countUpper(value);
        if(u==0) {
            return value;
        }
        
        int max = value.length();
        char prv = value.charAt(0);
        boolean inu = Character.isUpperCase(prv);
        if(inu) {
            prv = Character.toLowerCase(prv);
        }
        
        StringUtils.CBuilder builder = new StringUtils.CBuilder(max+u, prv);
        
        for(int i=1; i<max; i++) {
            char c = value.charAt(i);
            if(Character.isUpperCase(c)) {
                if(inu) {
                    builder.next();
                } else {
                    builder.nexts();
                    inu = true;
                }
                builder.next(Character.toLowerCase(c));
            } else {
                if(inu) {
                    builder.separate();
                    inu = false;
                }
                builder.next();
                builder.next(c);
            }
            
        }
        return builder.end();
    }
       
    // CBuilder
    private static String uncamelize2C(String value) {
        if(value.isEmpty()) {
            return value;
        }
        int max = value.length();
        char prv = value.charAt(0);
        boolean inu = Character.isUpperCase(prv);
        if(inu) {
            prv = Character.toLowerCase(prv);
        }
        
        StringUtils.CBuilder builder = new StringUtils.CBuilder(2*max, prv);
        
        for(int i=1; i<max; i++) {
            char c = value.charAt(i);
            if(Character.isUpperCase(c)) {
                if(inu) {
                    builder.next();
                } else {
                    builder.nexts();
                    inu = true;
                }
                builder.next(Character.toLowerCase(c));
            } else {
                if(inu) {
                    builder.separate();
                    inu = false;
                }
                builder.next();
                builder.next(c);
            }
            
        }
        return builder.end();
    }
    
    private static final Pattern URE = Pattern.compile("(\\p{Lu}*)(\\p{Lu})");
    // regex
    private static String uncamelizeRE(String value) {
        return StringUtils.replace(value, URE, hit -> {
            String m1 = hit.group(1).toLowerCase(Locale.ROOT);
            String m2 = hit.group(2).toLowerCase(Locale.ROOT);
            String p1 = (0 == hit.start(2)) ? "" : "_";
            String p2 = m1.isEmpty() || hit.end(2)==value.length() ? "" : "_";
            return p1 + m1 + p2 + m2;
        });
    }
    
    // count + StringBuilder
    private static String uncamelize2B(String value) {
        if(value.isEmpty()) {
            return value;
        }
        
        int max = value.length();
        char prv = value.charAt(0);
        boolean sep = false;
        boolean inu = Character.isUpperCase(prv);
        if(inu) {
            prv = Character.toLowerCase(prv);
        }
        
        StringBuilder builder = new StringBuilder(2*max);
        
        for(int i=1; i<max; i++) {
            char c = value.charAt(i);
            if(Character.isUpperCase(c)) {
                if(inu) {
                    builder.append(prv);
                    sep = true;
                } else {
                    builder.append(prv);
                    if(sep) {
                        builder.append('_');
                        sep = false;
                    }
                    inu = true;
                }
                prv = Character.toLowerCase(c);
            } else {
                if(inu) {
                    if(sep) {
                        builder.append('_');
                    }
                    inu = false;
                }
                builder.append(prv);
                prv = c;
                sep = true;
            }
            
        }
        return builder.append(prv).toString();
    }
    
    private static String uncamelize2StrBuilder(String value) {
        if(value.isEmpty()) {
            return value;
        }
        
        int max = value.length();
        char prv = value.charAt(0);
        boolean sep = false;
        boolean inu = Character.isUpperCase(prv);
        if(inu) {
            prv = Character.toLowerCase(prv);
        }
        
        StrBuilder builder = new StrBuilder(2*max);
        
        for(int i=1; i<max; i++) {
            char c = value.charAt(i);
            if(Character.isUpperCase(c)) {
                if(inu) {
                    builder.append(prv);
                    sep = true;
                } else {
                    builder.append(prv);
                    if(sep) {
                        builder.append('_');
                        sep = false;
                    }
                    inu = true;
                }
                prv = Character.toLowerCase(c);
            } else {
                if(inu) {
                    if(sep) {
                        builder.append('_');
                    }
                    inu = false;
                }
                builder.append(prv);
                prv = c;
                sep = true;
            }
            
        }
        return builder.append(prv).toString();
    }
    
    private static String uncamelize2StrBuffer(String value) {
        if(value.isEmpty()) {
            return value;
        }
        
        int max = value.length();
        char prv = value.charAt(0);
        boolean sep = false;
        boolean inu = Character.isUpperCase(prv);
        if(inu) {
            prv = Character.toLowerCase(prv);
        }
        
        StrBuffer builder = new StrBuffer(2*max);
        
        for(int i=1; i<max; i++) {
            char c = value.charAt(i);
            if(Character.isUpperCase(c)) {
                if(inu) {
                    builder.append(prv);
                    sep = true;
                } else {
                    builder.append(prv);
                    if(sep) {
                        builder.append('_');
                        sep = false;
                    }
                    inu = true;
                }
                prv = Character.toLowerCase(c);
            } else {
                if(inu) {
                    if(sep) {
                        builder.append('_');
                    }
                    inu = false;
                }
                builder.append(prv);
                prv = c;
                sep = true;
            }
            
        }
        return builder.append(prv).toString();
    }
    
    private static int countUpper(String value) {
        int r = 0;
        for(int i=0, n=value.length(); i<n; i++) {
            if(Character.isUpperCase(value.charAt(i))) {
                r++;
            }
        }
        return r;
    }
    
    private static String replaceApache(String text, String searchString, String replacement) {
        if (text.isEmpty() || searchString.isEmpty()) {
            return text;
        }
        int start = 0;
        int end = text.indexOf(searchString, start);
        if (end == -1) {
            return text;
        }
        int replLength = searchString.length();
        int increase = replacement.length() - replLength;
        increase = increase < 0 ? 0 : increase;
        increase *= 16;
        StringBuilder buf = new StringBuilder(text.length() + increase);
        while (end != -1) {
            buf.append(text.substring(start, end)).append(replacement);
            start = end + replLength;
            end = text.indexOf(searchString, start);
        }
        buf.append(text.substring(start));
        return buf.toString();
    }
    
    private static String replaceSB(String text, char search, String newval) {
        if(text.isEmpty()) {
            return text;
        }
        int start = 0;
        int end = text.indexOf(search, start);
        if (end == -1) {
            return text;
        }
        int delta = (newval.length()-1);

        StringBuilder buf = new StringBuilder(text.length() + MathUtils.clip(16*delta, 0, 128) );
        while (end != -1) {
            buf.append(text.substring(start, end)).append(newval);
            start = end + 1;
            end = text.indexOf(search, start);
        }
        buf.append(text.substring(start));
        return buf.toString();
    }
    
    public static String replaceStrBuilder(String text, char search, String newval) {
        if(text.isEmpty()) {
            return text;
        }
        int start = 0;
        int end = text.indexOf(search, start);
        if (end == -1) {
            return text;
        }
        int delta = (newval.length()-1);

        StrBuilder buf = new StrBuilder(text.length() + MathUtils.clip(16*delta, 0, 128) );
        while (end != -1) {
            buf.append(text.substring(start, end)).append(newval);
            start = end + 1;
            end = text.indexOf(search, start);
        }
        buf.append(text.substring(start));
        return buf.toString();
    }
    
    @State(Scope.Thread)
    public static class TextInput implements Iterable<String> {
        
        private final String[] data;
        
        public TextInput() {
            this(100,100);
        }
        
        public TextInput(int count, int size) {
            Random rand = new Random(777);
            data = new String[count];
            for(int i=0; i<count; i++) {
                char str[] = new char[size];
                for(int j=0; j<size; j++) {
                    char c = (char)('a' + rand.nextInt(20));
                    if(rand.nextInt(100) < 25) {
                        c = Character.toUpperCase(c);
                    }
                    str[j] = c;
                }
                data[i] = new String(str);
            }
        }

        @Override
        public Iterator<String> iterator() {
            return ArrayUtils.iterator(data);
        }
        
    }

    public static void main(String[] args) throws IOException, RunnerException {
        BenchmarkRunner.run(StringUtilsBenchmark.class);
    }
    
}
