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

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.ranides.assira.generic.Function;
import net.ranides.assira.math.MathUtils;

/**
 *
 * @author ranides
 */
public final class StringUtils {

    public static final String ENDL = String.format("%n");

    private static final Pattern  REGEX_SPECIAL = Pattern.compile("([\\\\*+\\[\\](){}\\$.?\\^|])");

    private StringUtils() { /* utility class */ }

    public static String remove(String text, String fragment) {
        StringTokenizer tokenizer = new StringTokenizer(text, fragment, false);
        StringBuilder result = new StringBuilder( text.length() );
        while (tokenizer.hasMoreElements()) {
            result.append( tokenizer.nextElement() );
        }
        return result.toString();
    }

    public static String remove(String text, char value) {
        final int size = text.length();
        final char buffer[] = new char[size];
        char c;
        int position = 0;
        for(int i=0; i<size; i++) {
            c = text.charAt(i);
            if(c!=value) { buffer[position++] = c; }
        }
        return new String(buffer, 0, position);
    }

    public static String removePrefix(String text, String prefix) {
        if( Strings.isEmpty(text)) {
            return "";
        }
        if(text.startsWith(prefix)) {
            return text.substring(prefix.length());
        }
        return text;
    }

    public static String removeSuffix(String text, String suffix) {
        if( Strings.isEmpty(text)) {
            return "";
        }
        if(text.endsWith(suffix)) {
            return text.substring(0, text.length() - suffix.length());
        }
        return text;
    }

    public static String condense(String text, char value) {
        final int size = text.length();
        final char buffer[] = new char[size];
        char c;
        boolean pc = false;
        int position = 0;
        for(int i=0; i<size; i++) {
            c = text.charAt(i);
            if(c == value) {
                if(!pc) {
                    buffer[position++] = c;
                    pc = true;
                }
            } else {
                buffer[position++] = c;
                pc = false;
            }
        }
        return new String(buffer, 0, position);
    }

    public static String ltrim(String source) {
        int n = source.length(), offset = 0;
        while( offset<n && Character.isWhitespace(source.charAt(offset)) ) { offset++; }
        return source.substring(offset);
    }

    public static String rtrim(String source) {
        int offset = source.length() - 1;
        while(offset >= 0 && Character.isWhitespace(source.charAt(offset)) ) { offset--; }
        return source.substring(0, offset+1);
    }

    public static String trim(String source){
        return source.trim();
    }

    public static String replace(String text, Pattern pattern, Function<String,Matcher> converter) {
        Matcher matcher = pattern.matcher(text);
        StringBuffer buffer = new StringBuffer();
        while(matcher.find()) {
            String str = converter.apply(matcher);
            if( null!=str ) { 
				matcher.appendReplacement(buffer, ""); 
				buffer.append(str); 
			}
        }
        matcher.appendTail(buffer);
        return buffer.toString();
    }

    public static String replace(String text, char search, char newval) {
        final char[] buffer = text.toCharArray();
        for(int i=0; i<buffer.length; i++) {
            if(buffer[i]==search) { buffer[i] = newval; }
        }
        return new String(buffer);
    }

    public static String replace(String text, char search, String newval) {
        if(null == text) {
            return "";
        }
        if(text.isEmpty()) {
            return text;
        }
        int start = 0;
        int end = text.indexOf(search, start);
        if (end == -1) {
            return text;
        }
        final int delta = (newval.length()-1);

        StringBuilder buf = new StringBuilder(text.length() + 16*delta );
        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 <T extends CharSequence> String join(Iterable<T> values, String separator) {
        if (values instanceof Collection) {
            Collection<?> collection = (Collection<?>)values;
            return join( collection.toArray(new CharSequence[collection.size()]), separator );
        }
        StringBuilder builder = new StringBuilder();
        boolean first = true;
        for(Object value : values) {
            if(!first) { builder.append(separator); }
            if(null != value) { builder.append(value.toString()); }
            first = false;
        }
        return builder.toString();
    }

    public static <T extends CharSequence> String join(Iterable<T> values) {
        if (values instanceof Collection) {
            Collection<?> collection = (Collection<?>)values;
            return join( collection.toArray(new CharSequence[collection.size()]));
        }
        StringBuilder builder = new StringBuilder();
        for(Object value : values) {
            builder.append( value.toString() );
        }
        return builder.toString();
    }

    public static <T extends CharSequence> String join(T[] list, String separator) {
        if(list.length == 0) {
            return "";
        }
        if(list.length == 1) {
            return list[0].toString();
        }

        int size = 0;
        for(int i=0; i<list.length; i++) {
            size+=list[i].length();
        }
        size += list.length * separator.length();

        StringBuilder result = new StringBuilder(size);
        result.append(list[0]);
        for(int i=1; i<list.length; i++) {
            result.append(separator).append(list[i]);
        }
        return result.toString();
    }

    public static <T extends CharSequence> String join(T[] list) {
        if(list.length == 0) {
            return "";
        }
        if(list.length == 1) {
            return list[0].toString();
        }

        int size = 0;
        for(int i=0; i<list.length; i++) {
            size+=list[i].length();
        }

        StringBuilder result = new StringBuilder(size);
        for(int i=0; i<list.length; i++) {
            result.append(list[i]);
        }
        return result.toString();
    }

    /**
     * Zamienia pierwszą literę w tekście na dużą. Nie uwzględnia lokalizacji.
     * @param value
     * @return
     */
    public static String capitalize(String value) {
        return capitalize(value, Locale.ROOT);
    }

    public static String uncapitalize(String value) {
        return uncapitalize(value, Locale.ROOT);
    }

    public static String capitalize(String value, Locale locale) {
        if( value.isEmpty() ) {
            return "";
        }
        if( 1==value.length() ) {
            return value.toUpperCase(locale);
        }
        return new StringBuilder(value.length())
            .append( value.substring(0, 1).toUpperCase(locale) )
            .append(value.substring(1))
            .toString();
    }

    public static String uncapitalize(String value, Locale locale) {
        if( value.isEmpty() ) {
            return "";
        }
        if( 1==value.length() ) {
            return value.toLowerCase(locale);
        }
        return new StringBuilder(value.length())
            .append( value.substring(0, 1).toLowerCase(locale) )
            .append(value.substring(1))
            .toString();
    }

    public static String camelize(String value) {
        String[] parts = value.split("_+");
        if(0==parts.length) {
            return "";
        }
        if(1==parts.length) {
            return parts[0].toLowerCase(Locale.ROOT);
        }

        StringBuilder result = new StringBuilder(value.length()-parts.length);
        boolean first = true;
        for (String part : parts) {
            if(part.isEmpty()) {
                continue;
            }
            if (first) {
                result.append(part.toLowerCase(Locale.ROOT));
                first = false;
            } else {
                result.append(part.substring(0, 1).toUpperCase(Locale.ROOT));
                result.append(part.substring(1).toLowerCase(Locale.ROOT));
            }
        }
        return result.toString();
    }

    public static String uncamelize(String value) {
        return replace(value, Pattern.compile("(\\p{Lu})"), new Function<String,Matcher>() {
            @Override
            public String apply(Matcher source) {
                if(0 == source.start(1)) {
                    return source.group(1).toLowerCase(Locale.ROOT);
                }
                return "_" + source.group(1).toLowerCase(Locale.ROOT);
            }
        });
    }

    public static String clip(String text, int size) {
        return text.substring(0, MathUtils.clip(size, 0, text.length()));
    }

    public static String lpadd(String text, char padd, int size) {
        int n = text.length();
        int d = size - n;
        if(d <= 0) {
            return text;
        }
        char[] buffer = new char[size];
        text.getChars(0, n, buffer, 0);
        for(int i=n; i<size; i++) { buffer[i] = padd; }
        return new String(buffer);
    }

    public static String rpadd(String text, char padd, int size) {
        int n = text.length();
        int d = size - n;
        if(d <= 0) {
            return text;
        }
        char[] buffer = new char[size];
        text.getChars(0, n, buffer, d);
        for(int i=0; i<d; i++) { buffer[i] = padd; }
        return new String(buffer);
    }

    public static String lcommon(String value1, String value2) {
        if( Strings.isEmpty(value1) || Strings.isEmpty(value2) ) { 
            return ""; 
        }
        int n = Math.min(value1.length(), value2.length());
        for(int i=0; i<n; i++) {
            if( value1.charAt(i)!=value2.charAt(i) ) { return value1.substring(0,i); }
        }
        return value1.substring(0,n);
    }

    public static String lcommon(List<String> values) {
        if(values.isEmpty() ) { 
            return ""; 
        }
        String text = values.get(0);
        for(int i=1, n=values.size(); i<n; i++) {
            text = lcommon(text, values.get(i));
        }
        return text;
    }

    public static List<String> lcommonTrim(List<String> values) {
        int n = lcommon(values).length();
        List<String> result = new ArrayList<>(values.size());
        for(String text : values) {
            result.add(text.substring(n));
        }
        return result;
    }

    public static String reverse(String value) {
        return Strings.isEmpty(value) ? value : new StringBuilder(value).reverse().toString();
    }

    public static String find(String text, Pattern pattern) {
        Matcher hit = pattern.matcher(text);
        if( hit.find() ) {
            return hit.groupCount()>0 ? hit.group(1) : hit.group();
        } else {
            return null;
        }
    }

    public static String find(String text, String pattern) {
        return find(text, Pattern.compile(pattern));
    }

    public static String between(String text, String start, String end) {
        if(start == null) {
            return before(text, end);
        }
        if(end == null) {
            return after(text, start);
        }
        int istart = text.indexOf(start);
        if( istart == -1 ) {
            return "";
        }
        istart += start.length();
        int iend = text.indexOf(end, istart);
        if( iend == -1 ) {
            return "";
        }
        return text.substring(istart, iend);
    }
    
    public static String before(String text, String end) {
        int iend = text.indexOf(end);
        return (-1 == iend) ? "" : text.substring(0, iend);
    }
    
    public static String after(String text, String start) {
        int istart = text.indexOf(start);
        return (-1 == istart) ? "" : text.substring(istart + start.length());
    }

    public static CharSequence nCopies(int n, String text) {
        return new NCopies(n, text);
    }

    public static CharSequence nCopies(int n, char chr) {
        return new NCCopies(n, chr);
    }

    private static class NCCopies implements CharSequence {
        private final int count;
        private final char chr;

        public NCCopies(int count, char chr) {
            this.count = count;
            this.chr = chr;
        }
        @Override
        public char charAt(int index) {
            if( index >= count ) {
                throw new IndexOutOfBoundsException(index + " out of bound " + count);
            }
            return chr;
        }
        @Override
        public int length() {
            return count;
        }
        @Override
        public CharSequence subSequence(int start, int end) {
            return new NCCopies(end-start, chr);
        }

        @Override
        public String toString() {
            int n = length();
            char[] data = new char[n];
            Arrays.fill(data, chr);
            return new String(data);
        }
    }

    private static class NCopies implements CharSequence {
        private final int start;
        private final int end;
        private final int mod;
        private final String text;

        public NCopies(int count, String text) {
            this(text, 0, count * text.length());
        }

        public NCopies(String text, int start, int end) {
            this.start  = start;
            this.end    = end;
            this.mod    = text.length();
            this.text   = text;
        }

        @Override
        public char charAt(int index) {
            if( index >= end ) {
                throw new IndexOutOfBoundsException(index + " out of bound " + end);
            }
            return text.charAt( (index+start) % mod );
        }

        @Override
        public int length() {
            return end-start;
        }

        @Override
        public CharSequence subSequence(int start, int end) {
            return new NCopies(text, start, end);
        }

        @Override
        public String toString() {
            int n = length();
            char[] data = new char[n];
            for(int i=0; i<n; i++) { data[i] = charAt(i); }
            return new String(data);
        }

    }

    public static String escapeRegex(String text) {
        return REGEX_SPECIAL.matcher(text).replaceAll("\\\\$1");
    }
    
    public static Pattern compileWildcard(String wildcard) {
        return Pattern.compile(StringUtils.escapeRegex(wildcard).replace("\\*", ".*"));
    }

}
