/*
 * This file is part of muCommander, http://www.mucommander.com
 *
 * muCommander is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * muCommander is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.mucommander.commons.util;

import java.text.Collator;
import java.util.Locale;

/**
 * This class contains convenience methods for working with strings.
 * @author Maxence Bernard, Nicolas Rinaudo
 */
public final class StringUtils {

	public static final String EMPTY = "";
	private static final char[] HEX_CHAR_ARRAY = "0123456789ABCDEF".toCharArray();

    /**
     * Prevents instantiation of this class.
     */
    private StringUtils() {
    }

    /**
     * Returns <code>true</code> if <code>a</code> ends with <code>b</code> regardless of the case.
     * <p>
     * This method has a known bug under some alphabets with peculiar capitalisation rules such as the Georgian one,
     * where <code>Character.toUpperCase(a) == Character.toUpperCase(b)</code> doesn't necessarily imply that
     * <code>Character.toLowerCase(a) == Character.toLowerCase(b)</code>. The performance hit of testing for this
     * exceptions is so huge that it was deemed an acceptable issue.
     * </p>
     * <p>
     * Note that this method will return <code>true</code> if <code>b</code> is an empty string.
     * </p>
     * @param a string to test.
     * @param b suffix to test for.
     * @return <code>true</code> if <code>a</code> ends with <code>b</code> regardless of the case, <code>false</code> otherwise.
     */
    public static boolean endsWithIgnoreCase(String a, String b) {
        return matchesIgnoreCase(a, b, a.length());
    }

    /**
     * Returns <code>true</code> if the substring of <code>a</code> starting at <code>posA</code> matches <code>b</code> regardless of the case.
     * <p>
     * This method has a known bug under some alphabets with peculiar capitalisation rules such as the Georgian one,
     * where <code>Character.toUpperCase(a) == Character.toUpperCase(b)</code> doesn't necessarily imply that
     * <code>Character.toLowerCase(a) == Character.toLowerCase(b)</code>. The performance hit of testing for this
     * exceptions is so huge that it was deemed an acceptable issue.
     * </p>
     * <p>
     * Note that this method will return <code>true</code> if <code>b</code> is an empty string.
     * </p>
     * @param  a                              string to test.
     * @param  b                              suffix to test for.
     * @param  posA                           position in <code>a</code> at which to look for <code>b</code>
     * @return                                <code>true</code> if <code>a</code> ends with <code>b</code> regardless of the case, <code>false</code> otherwise.
     * @throws ArrayIndexOutOfBoundsException if <code>a.length</code> is smaller than <code>posA</code>.
     */
    public static boolean matchesIgnoreCase(String a, String b, int posA) {
        int  posB; // Position in b.
        char cA;   // Current character in a.
        char cB;   // Current character in b.

        // Checks whether there's any point in testing the strings.
        if(posA < (posB = b.length()))
            return false;

        // Loops until we've tested the whole of b.
        while(posB > 0) {

            // Works on lower-case characters only. 
            if(!Character.isLowerCase(cA = a.charAt(--posA)))
                cA = Character.toLowerCase(cA);
            if(!Character.isLowerCase(cB = b.charAt(--posB)))
                cB = Character.toLowerCase(cB);
            if(cA != cB)
                return false;
        }
        return true;
    }

    /**
     * Returns <code>true</code> if <code>a</code> ends with <code>b</code> regardless of the case.
     * <p>
     * This method has a known bug under some alphabets with peculiar capitalisation rules such as the Georgian one,
     * where <code>Character.toUpperCase(a) == Character.toUpperCase(b)</code> doesn't necessarily imply that
     * <code>Character.toLowerCase(a) == Character.toLowerCase(b)</code>. The performance hit of testing for this
     * exceptions is so huge that it was deemed an acceptable issue.
     * </p>
     * <p>
     * Note that this method will return <code>true</code> if <code>b</code> is an empty string.
     * </p>
     * @param a string to test.
     * @param b suffix to test for.
     * @return <code>true</code> if <code>a</code> ends with <code>b</code> regardless of the case, <code>false</code> otherwise.
     */
    public static boolean endsWithIgnoreCase(String a, char[] b) {return matchesIgnoreCase(a, b, a.length());}

    /**
     * Returns <code>true</code> if the substring of <code>a</code> starting at <code>posA</code> matches <code>b</code> regardless of the case.
     * <p>
     * This method has a known bug under some alphabets with peculiar capitalisation rules such as the Georgian one,
     * where <code>Character.toUpperCase(a) == Character.toUpperCase(b)</code> doesn't necessarily imply that
     * <code>Character.toLowerCase(a) == Character.toLowerCase(b)</code>. The performance hit of testing for this
     * exceptions is so huge that it was deemed an acceptable issue.
     * </p>
     * <p>
     * Note that this method will return <code>true</code> if <code>b</code> is an empty string.
     * </p>
     * @param  a                              string to test.
     * @param  b                              suffix to test for.
     * @param  posA                           position in <code>a</code> at which to look for <code>b</code>
     * @return                                <code>true</code> if <code>a</code> ends with <code>b</code> regardless of the case, <code>false</code> otherwise.
     * @throws ArrayIndexOutOfBoundsException if <code>a.length</code> is smaller than <code>posA</code>.
     */
    public static boolean matchesIgnoreCase(String a, char[] b, int posA) {
        int  posB; // Position in b.
        char cA;   // Current character in a.
        char cB;   // Current character in b.

        // Checks whether there's any point in testing the strings.
        if(posA < (posB = b.length))
            return false;

        while(posB > 0) {
            if(!Character.isLowerCase(cA = a.charAt(--posA)))
                cA = Character.toLowerCase(cA);
            if(!Character.isLowerCase(cB = b[--posB]))
                cB = Character.toLowerCase(cB);
            if(cA != cB)
                return false;
        }
        return true;
    }

    /**
     * Equivalent of <code>String.endsWith(String)</code> using a <code>char[]</code>.
     * @param  a String to test.
     * @param  b suffix to test.
     * @return   <code>true</code> if <code>a</code> ends with <code>b</code>.
     */
    public static boolean endsWith(String a, char[] b) {
        return matches(a, b, a.length());
    }

    /**
     * Returns <code>true</code> if the substring of <code>a</code> ending at <code>posA</code> matches <code>b</code>.
     * @param  a    String to test.
     * @param  b    substring to look for.
     * @param  posA position in <code>a</code> at which to look for <code>b</code>
     * @return      <code>true</code> if <code>a</code> contains <code>b</code> at position <code>posA - b.length()</code>, <code>false</code> otherwise..
     */
    public static boolean matches(String a, char[] b, int posA) {
        int posB;

        if(posA < (posB = b.length))
            return false;
        while(posB > 0)
            if(a.charAt(--posA) != b[--posB])
                return false;
        return true;
    }

    /**
     * Returns <code>true</code> if <code>a</code> starts with <code>b</code> regardless of the case.
     * <p>
     * Note that this method will return <code>true</code> if <code>b</code> is an empty string.
     * </p>
     * @param a string to test.
     * @param b prefix to test for.
     * @return <code>true</code> if <code>a</code> starts with <code>b</code> regardless of the case, <code>false</code> otherwise..
     */
    public static boolean startsWithIgnoreCase(String a, String b) {
        return a.regionMatches(true, 0, b, 0, b.length());
    }

    /**
     * This method is a locale-aware version of <code>java.lang.String#equals(Object)</code>. It returns
     * <code>true</code> if the two given <code>String</code> are equal in the specified <code>Locale</code>.
     *
     * <p>This method is useful for testing text expressed in a language where two strings with an identical
     * written representation can have a different <code>String</code> representation according to
     * <code>java.lang.String#equals(Object)</code>. Japanese is such a language for instance.
     * This method uses the <code>java.text.Collator</code> class under the hood.
     *
     * @param s1 a String to compare
     * @param s2 a String to compare
     * @param locale the Locale to consider for testing the String
     * @return true if the two given String are equal in the specified Locale
     */
    public static boolean equals(String s1, String s2, Locale locale) {
        return Collator.getInstance(locale).equals(s1, s2);
    }
    
    /**
     * Compares the two specified strings and returns <code>true</code> if both strings are equal. This method handles
     * <code>null</code> values with no risk of a <code>NullPointerException</code>. The comparison is case-sensitive
     * only if requested.
     *
     * <p>In other words, this method returns <code>true</code> if strings are either both <code>null</code>
     * or equal according to <code>String#equals(String)</code> for case-sensitive comparison, or
     * <code>String#equalsIgnoreCase(String)</code> for case-insensitive comparison.</p> 
     *
     * @param s1 string to compare, potentially <code>null</code>
     * @param s2 string to compare, potentially <code>null</code>
     * @param caseSensitive <code>true</code> for case-sensitive comparison, <code>false</code> for case-insensitive comparison
     * @return <code>true</code> if strings are equal or both null
     */
    public static boolean equals(String s1, String s2, boolean caseSensitive) {
        if(s1 == null && s2 == null)
            return true;

        if(caseSensitive)
            return s1 != null && s1.equals(s2);
        return s1 != null && s1.equalsIgnoreCase(s2);
    }

    /**
     * Parses the string argument as a signed decimal integer. If the string cannot be
     * parsed a default value is returned.
     * @param s a String containing the int representation to be parsed
     * @param def a default value if string cannot be parsed
     * @return the integer value represented by the argument or default value if it cannot be parsed 
     */
    public static int parseIntDef(String s, int def) {
        try {
            return Integer.parseInt(s);
        } catch (NumberFormatException e) {
            return def;
        }
    }

    /**
     * Capitalizes the given string, making its first character upper case, and the rest of them lower case.
     * This method returns an empty string if <code>null</code> or an empty string is passed.
     *
     * @param s the string to capitalize
     * @return the capitalized string
     */
    public static String capitalize(String s) {
        if(isNullOrEmpty(s))
            return EMPTY;

        StringBuilder out;

        out = new StringBuilder(s.length());
        out.append(Character.toUpperCase(s.charAt(0)));
        if(s.length() > 1)
            out.append(s.substring(1).toLowerCase());
        return out.toString();
    }


    /**
     * Shorthand for {@link #flatten(String[], String)} invoked with a <code>" "</code> separator.
     *
     * @param s the string array to flatten
     * @return the flattened string array
     */
    public static String flatten(String s[]) {
        return flatten(s, " ");
    }


    /**
     * Concatenates all of the given string array's elements into a single string, separating each element
     * by the specified separator string. <code>null</code> and empty string values are simply skipped and not
     * reflected in the returned string. If the string array is <code>null</code>, the returned string will also be
     * <code>null</code>.
     *
     * @param s the string array to flatten
     * @param separator the String that separates each
     * @return the flattened string array
     */
    public static String flatten(String s[], String separator) {
        if(s==null)
            return null;

        StringBuilder sb = new StringBuilder();
        int sLen = s.length;
        boolean first = true;
        String el;

        for(int i=0; i<sLen; i++) {
            el = s[i];
            if (isNullOrEmpty(el))
                continue;

            if(first)
                first = false;
            else
                sb.append(separator);

            sb.append(el);
        }

        return sb.toString();
    }

    /**
     * Returns true if the given string is null or empty (i.e. it's length is 0)
     *
     * @param string - the given String to check
     * @return true if the given string is null or empty, false otherwise
     */
    public static boolean isNullOrEmpty(String string) {
    	return string == null || string.isEmpty();
    }

    public static String bytesToHexStr(byte[] bytes, int offset, int size) {
        char[] hexChars = new char[size * 2];
        for (int i = offset; i < offset + size; i++) {
            int v = bytes[i] & 0xFF;
            hexChars[i * 2] = HEX_CHAR_ARRAY[v >>> 4];
            hexChars[i * 2 + 1] = HEX_CHAR_ARRAY[v & 0x0F];
        }
        return new String(hexChars);
    }
}
