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

import net.ranides.assira.annotations.Meta;
import net.ranides.assira.annotations.PMD;
import net.ranides.assira.reflection.GenericMethod;
import net.ranides.assira.reflection.inspect.MethodInspector;
import net.ranides.assira.reflection.util.ReflectUtils;
import net.ranides.assira.trace.StackInspector;

/**
 *
 * @author ranides
 */
@SuppressWarnings({"PMD.ShortVar"})
public final class Strings {

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

    /**
     * Returns first non-null, non-empty string.
     * If not, returns first empty string (by identity).
     * If not, returns null.
     * @param values
     * @return 
     */
    public static String or(Object... values) {
        String ret = null;
        for(Object value : values) {
            if( value != null) {
                String str = value.toString();
                if(!str.isEmpty()) {
                    return str;
                } 
                else if(null == ret) {
                    ret = str;
                }
            }
        }
        return ret;
    }
    
    /**
     * Returns first non-null, non-empty string.
     * If not, returns first empty string (by identity).
     * If not, returns null.
     * <p>
     * That specialization effectivelly returns value#toString or null
     * </p>
     * @param values
     * @return 
     */
    public static String or(Object value) {
        return null == value ? null : value.toString();
    }

    public static boolean isEmpty(CharSequence value) {
        return (null==value) || value.length() == 0;
    }

    public static boolean isAssigned(CharSequence value) {
        return (null!=value) && value.length() != 0;
    }

    public static boolean isEqual(Object valueA, Object valueB) {
        if(null == valueA) {
            return (null == valueB);
        } else {
            return (null == valueB) ? false : valueA.toString().equals(valueB.toString());
        }
    }

    public static int compareTo(String valueA, String valueB) {
        if(null == valueA) {
            return (null == valueB) ? 0 : -1;
        } else {
            return (null == valueB) ? 1 : valueA.compareTo(valueB);
        }
    }

    public static boolean contains(CharSequence text, char value) {
        return -1 != indexOf(text, value, 0);
    }

    public static boolean contains(CharSequence text, CharSequence value) {
        return -1 != indexOf(text, value, 0);
    }

    public static int length(String value) {
        return (null == value) ? 0 : value.length();
    }

    public static String substring(String text, int first) {
        return substring(text, first, Integer.MAX_VALUE);
    }

    @SuppressWarnings("PMD.AvoidReassigningParameters")
    public static String substring(String text, int first, int last) {
        if(null == text) { 
            return ""; 
        }
        if(first < 0) {
            first = 0;
        }
        final int count = text.length();
        if(first >= count) {
            return "";
        }
        if(last <= first) {
            return "";
        }
        if(last > count ) {
            last = count;
        }
        return text.substring(first, last);
    }

    /**
     * Tworzy char sequence zawierający podaną tablicą, bez kopiowania.
     * Mile widziane, aby tablica nie była modyfikowana w tle po utworzeniu
     * sekwencji. CharSequence nie ma immutable kontraktu, ale mimo wszystko 
     * byłoby to uprzejme.
     * @param data
     * @return 
     */
    public static CharSequence asSequence(char[] data) {
        return new ArrayWrapper(data);
    }

    /**
     * Very unsafe method. Creates new String object with specified array inside,
     * <b>without copying</b>. 
     * @param data
     * @return 
     */
    @Meta.Unsafe
    public static String wrap(char[] data) {
        // In JDK 8 we could avoid reflection:
        //      JavaLangAccess a = sun.misc.SharedSecrets.getJavaLangAccess();
        //      return a.newStringUnsafe(data);
        //
        // In JDK 7 we have to invoke private constructor:
        //      new String(data, true);
        //
        // WARN: Both methods can be prohibited by SecurityManager
        return StringWrapper.CTOR.call(data, true);
    }
    
    private static final class StringWrapper { // NOPMD lazy init idiom
        
        public static final GenericMethod<String> CTOR = MethodInspector.inclass(String.class).constructors().params(char[].class, boolean.class).first();
        
    }
    @SuppressWarnings("PMD.AvoidReassigningParameters")
    public static int indexOf(CharSequence text, char value, int offset) {    
        if(null == text) {
            return -1;
        }
        if (offset < 0) {
            offset = 0;
        }
        for(int i=offset,n=text.length(); i<n; i++) {
            if(value == text.charAt(i)) {
                return i;
            }
        }
        return -1;
    }
    
    @SuppressWarnings({"PMD", "empty-statement"})
    public static int indexOf(CharSequence text, CharSequence value, int offset) {
        // implementation copied from java.lang.String
        // we don't want to change tricky details
        
        if(null == text || null == value) {
            return -1;
        }
        
        int sourceCount = text.length();
        int targetCount = value.length();
        
        if (offset >= sourceCount) {
            return (targetCount == 0 ? sourceCount : -1);
        }
        if (offset < 0) {
            offset = 0;
        }
        if (targetCount == 0) {
            return offset;
        }

        char first = value.charAt(0);
        int max = (sourceCount - targetCount);

        for (int i = offset; i <= max; i++) {
            /* Look for first character. */
            if (text.charAt(i) != first) {
                while (++i <= max && text.charAt(i) != first);
            }

            /* Found first character, now look at the rest of v2 */
            if (i <= max) {
                int j = i + 1;
                int end = j + targetCount - 1;
                for (int k = 1; j < end && text.charAt(j)
                        == value.charAt(k); j++, k++);

                if (j == end) {
                    /* Found whole string. */
                    return i;
                }
            }
        }
        return -1;
    }
    
    /**
     * Cholernie przekombinowana metoda, która dodaje do javy pseudo-support dla
     * psuedo-raw-string-literals. Używa się jej prosto: <code>Strings.raw(/&#42raw text here;&#42;/)</code>
     * <p>
     * Komentarz wrzucony pomiędzy nawiasy jest przetwarzany i zwracany przez
     * funkcję. Żeby to działało, aplikacja musi mieć w runtime dostęp do plików
     * źródłowych za pomocą ClassLoadera.
     * </p>
     * @return 
     */
    public static String raw() {
        StackTraceElement frame = StackInspector.frame(StackInspector.CALLER);
        String source = ReflectUtils.getSource(frame.getClassName());
        int offset = 0;
        for (int i = 1, n = frame.getLineNumber(); i < n; i++) {
            offset = source.indexOf('\n', offset) + 1;
        }
        return source.substring(source.indexOf("/*", offset) + 2, source.indexOf("*/", offset));
    }

    private static final class SliceWrapper implements CharSequence {

        private final char[] data;
        private final int start;
        private final int end;

        public SliceWrapper(char[] data, int start, int end) { // NOPMD
            this.data = data;
            this.start = start;
            this.end = end;
        }

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

        @Override
        public char charAt(int index) {
            return data[index + start];
        }

        @Override
        public CharSequence subSequence(int start, int end) {
            return new SliceWrapper(data, this.start+start, this.start+end);
        }

        @Override
        public String toString() {
            return String.valueOf(data, start, length());
        }

    }

    @PMD.ArrayWrapper
    private static final class ArrayWrapper implements CharSequence {

        private final char[] data;

        public ArrayWrapper(char[] data) { // NOPMD
            this.data = data;
        }

        @Override
        public int length() {
            return data.length;
        }

        @Override
        public char charAt(int index) {
            return data[index];
        }

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

        @Override
        public String toString() {
            return String.valueOf(data);
        }

    }

}
