/*
 * RHQ Management Platform
 * Copyright (C) 2005-2008 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2, as
 * published by the Free Software Foundation, and/or the GNU Lesser
 * General Public License, version 2.1, also as published by the Free
 * Software Foundation.
 *
 * This program 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 General Public License and the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License
 * and the GNU Lesser General Public License along with this program;
 * if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.rhq.core.clientapi.util;

import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

public class StringUtil {
    /**
     * @param  source  The source string to perform replacements on.
     * @param  find    The substring to find in source.
     * @param  replace The string to replace 'find' within source
     *
     * @return The source string, with all occurrences of 'find' replaced with 'replace'
     */
    public static String replace(String source, String find, String replace) {
        if ((source == null) || (find == null) || (replace == null)) {
            return source;
        }

        int sourceLen = source.length();
        int findLen = find.length();
        if ((sourceLen == 0) || (findLen == 0)) {
            return source;
        }

        StringBuilder buffer = new StringBuilder();

        int idx;
        int fromIndex;

        for (fromIndex = 0; (idx = source.indexOf(find, fromIndex)) != -1; fromIndex = idx + findLen) {
            buffer.append(source.substring(fromIndex, idx));
            buffer.append(replace);
        }

        if (fromIndex == 0) {
            return source;
        }

        buffer.append(source.substring(fromIndex));

        return buffer.toString();
    }

    /**
     * @param  source The source string to perform replacements on.
     * @param  find   The substring to find in source.
     *
     * @return The source string, with all occurrences of 'find' removed
     */
    public static String remove(String source, String find) {
        if ((source == null) || (find == null)) {
            return source;
        }

        String retVal = null;
        int sourceLen = source.length();
        int findLen = find.length();
        StringBuilder remove = new StringBuilder(source);

        try {
            if ((sourceLen > 0) && (findLen > 0)) {
                int fromIndex;
                int idx;

                for (fromIndex = 0, idx = 0; (fromIndex = source.indexOf(find, idx)) != -1; idx = fromIndex + findLen) {
                    remove.delete(fromIndex, findLen + fromIndex);
                }

                retVal = remove.toString();
            }
        } catch (Exception e) {
            // XXX This should never happen
            e.printStackTrace();
            retVal = null;
        }

        return retVal;
    }

    /**
     * Print out everything in an Iterator in a user-friendly string format.
     *
     * @param  i     An iterator to print out.
     * @param  delim The delimiter to use between elements.
     *
     * @return The Iterator's elements in a user-friendly string format.
     */
    public static String iteratorToString(Iterator i, String delim) {
        return iteratorToString(i, delim, "");
    }

    /**
     * Print out everything in an Iterator in a user-friendly string format.
     *
     * @param  i         An iterator to print out.
     * @param  delim     The delimiter to use between elements.
     * @param  quoteChar The character to quote each element with.
     *
     * @return The Iterator's elements in a user-friendly string format.
     */
    public static String iteratorToString(Iterator i, String delim, String quoteChar) {
        Object elt = null;
        StringBuilder rstr = new StringBuilder();
        String s;

        while (i.hasNext()) {
            if (rstr.length() > 0) {
                rstr.append(delim);
            }

            elt = i.next();
            if (elt == null) {
                rstr.append("NULL");
            } else {
                s = elt.toString();
                if (quoteChar != null) {
                    rstr.append(quoteChar).append(s).append(quoteChar);
                } else {
                    rstr.append(s);
                }
            }
        }

        return rstr.toString();
    }

    /**
     * Print out a List in a user-friendly string format.
     *
     * @param  list  A List to print out.
     * @param  delim The delimiter to use between elements.
     *
     * @return The List in a user-friendly string format.
     */
    public static String listToString(List list, String delim) {
        if (list == null) {
            return "NULL";
        }

        Iterator i = list.iterator();
        return iteratorToString(i, delim, null);
    }

    public static String collectionToString(Collection collection, String delim) {
        if (collection == null) {
            return "NULL";
        }

        Iterator i = collection.iterator();
        return iteratorToString(i, delim, null);
    }

    /**
     * Print out a List in a user-friendly string format.
     *
     * @param  list A List to print out.
     *
     * @return The List in a user-friendly string format.
     */
    public static String listToString(List list) {
        return listToString(list, ",");
    }

    public static String collectionToString(Collection collection) {
        return collectionToString(collection, ",");
    }

    /**
     * Print out an array as a String
     */
    public static String arrayToString(Object[] array) {
        return arrayToString(array, ',');
    }

    /**
     * Print out an array as a String
     */
    public static String arrayToString(boolean[] array) {
        if (array == null) {
            return "null";
        }

        String rstr = "";
        char delim = ',';
        for (int i = 0; i < array.length; i++) {
            if (i > 0) {
                rstr += delim;
            }

            rstr += array[i];
        }

        return rstr;
    }

    /**
     * Print out an array as a String
     *
     * @param array The array to print out
     * @param delim The delimiter to use between elements.
     */
    public static String arrayToString(Object[] array, char delim) {
        if (array == null) {
            return "null";
        }

        StringBuilder rstr = new StringBuilder();
        for (int i = 0; i < array.length; i++) {
            if (i > 0) {
                rstr.append(delim);
            }

            rstr.append(array[i]);
        }

        return rstr.toString();
    }

    /**
     * Print out an array as a String
     */
    public static String arrayToString(int[] array) {
        if (array == null) {
            return "null";
        }

        StringBuilder rstr = new StringBuilder();
        for (int i = 0; i < array.length; i++) {
            if (i > 0) {
                rstr.append(",");
            }

            rstr.append(array[i]);
        }

        return rstr.toString();
    }

    /**
     * Create a string formulated by inserting a delimiter in between consecutive array elements.
     *
     * @param  objs  List of objects to implode (elements may not be null)
     * @param  delim String to place inbetween elements
     *
     * @return A string with objects in the list seperated by delim
     */
    public static String implode(List objs, String delim) {
        StringBuilder buf = new StringBuilder();
        int size = objs.size();

        for (int i = 0; i < (size - 1); i++) {
            buf.append(objs.get(i).toString());
            buf.append( delim);
        }

        if (size != 0) {
            buf.append(objs.get(size - 1).toString());
        }

        return buf.toString();
    }

    /**
     * Split a string on delimiter boundaries, and place each element into a List.
     *
     * @param  s     String to split up
     * @param  delim Delimiting token, ala StringTokenizer
     *
     * @return a List comprised of elements split by the tokenizing
     */

    public static List<String> explode(String s, String delim) {
        List<String> res = new ArrayList<String>();
        if (s == null)
            return res;

        StringTokenizer tok = new StringTokenizer(s, delim);

        while (tok.hasMoreTokens()) {
            res.add(tok.nextToken());
        }

        return res;
    }

    /**
     * Split a string on delimiter boundaries, and place each element into an Array.
     *
     * @param  toExplode String to split up
     * @param  delim     Delimiting token, ala StringTokenizer
     *
     * @return an Array comprised of elements split by the tokenizing
     */
    public static String[] explodeToArray(String toExplode, String delim) {
        List<String> strings = explode(toExplode, delim);
        String[] ret;
        ret = strings.toArray(new String[strings.size()]);
        return ret;
    }

    /**
     * Split a string up by whitespace, taking into account quoted subcomponents. If there is an uneven number of
     * quotes, a parse error will be thrown.
     *
     * @param  arg String to parse
     *
     * @return an array of elements, the argument was split into
     *
     * @throws IllegalArgumentException indicating there was a quoting error
     */

    public static String[] explodeQuoted(String arg) throws IllegalArgumentException {
        List<String> res = new ArrayList<String>();
        StringTokenizer quoteTok;
        boolean inQuote = false;

        arg = arg.trim();
        quoteTok = new StringTokenizer(arg, "\"", true);

        while (quoteTok.hasMoreTokens()) {
            String elem = (String) quoteTok.nextElement();

            if (elem.equals("\"")) {
                inQuote = !inQuote;
                continue;
            }

            if (inQuote) {
                res.add(elem);
            } else {
                StringTokenizer spaceTok = new StringTokenizer(elem.trim());

                while (spaceTok.hasMoreTokens()) {
                    res.add(spaceTok.nextToken());
                }
            }
        }

        if (inQuote) {
            throw new IllegalArgumentException("Unbalanced quotation marks");
        }

        return res.toArray(new String[res.size()]);
    }

    /**
     * Remove a prefix from a string. If value starts with prefix, it will be removed, the resultant string is trimmed
     * and returned.
     *
     * @return If value starts with prefix, then this method returns value with the prefix removed, and the resultant
     *         string trimmed. If value does not start with prefix, value is returned as-is.
     */
    public static String removePrefix(String value, String prefix) {
        if (!value.startsWith(prefix)) {
            return value;
        }

        return value.substring(prefix.length()).trim();
    }

    /**
     * @return the plural of word. This is done by applying a few rules. These cover most (but not all) cases: 1. If the
     *         word ends in s, ss, x, o, or ch, append es 2. If the word ends in a consonant followed by y, drop the y
     *         and add ies 3. Append an s and call it a day. The ultimate references is at
     *         http://en.wikipedia.org/wiki/English_plural
     */
    public static String pluralize(String word) {
        if (word.endsWith("s") || word.endsWith("x") || word.endsWith("o") || word.endsWith("ch")) {
            return word + "es";
        }

        if (word.endsWith("y")) {
            // Odd case to avoid StringIndexOutOfBounds later
            if (word.length() == 1) {
                return word;
            }

            // Check next-to-last letter
            char next2last = word.charAt(word.length() - 2);
            if ((next2last != 'a') && (next2last != 'e') && (next2last != 'i') && (next2last != 'o')
                && (next2last != 'u') && (next2last != 'y')) {
                return word.substring(0, word.length() - 1) + "ies";
            }
        }

        return word + "s";
    }

    /**
     * @return The stack trace for the given Throwable as a String.
     */
    public static String getStackTrace(Throwable t) {
        if (t == null) {
            return "THROWABLE-WAS-NULL (at " + getStackTrace(new Exception()) + ")";
        }

        try {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);

            t.printStackTrace(pw);

            Throwable cause = t.getCause();
            if (cause != null) {
                return sw.toString() + getStackTrace(cause);
            }

            return sw.toString();
        } catch (Exception e) {
            return "\n\nStringUtil.getStackTrace " + "GENERATED EXCEPTION: '" + e.toString() + "' \n\n";
        }
    }

    /**
     * @return The stack trace for the given Throwable as a String.
     */
    public static String getFirstStackTrace(Throwable t) {
        if (t == null) {
            return null;
        }

        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        t.printStackTrace(pw);

        return sw.toString();
    }

    /**
     * @param  s A string that might contain unix-style path separators.
     *
     * @return The correct path for this platform (i.e, if win32, replace / with \).
     */
    public static String normalizePath(String s) {
        return StringUtil.replace(s, "/", File.separator);
    }

    public static String formatDuration(long duration) {
        return formatDuration(duration, 0, false);
    }

    public static String formatDuration(long duration, int scale, boolean minDigits) {
        long hours;
        long mins;
        int digits;
        double millis;

        hours = duration / 3600000;
        duration -= hours * 3600000;

        mins = duration / 60000;
        duration -= mins * 60000;

        millis = (double) duration / 1000;

        StringBuilder buf = new StringBuilder();

        if ((hours > 0) || (minDigits == false)) {
            buf.append(((hours < 10) && (minDigits == false)) ? ("0" + hours) : String.valueOf(hours)).append(':');
            minDigits = false;
        }

        if ((mins > 0) || (minDigits == false)) {
            buf.append(((mins < 10) && (minDigits == false)) ? ("0" + mins) : String.valueOf(mins)).append(':');
            minDigits = false;
        }

        // Format seconds and milliseconds
        NumberFormat fmt = NumberFormat.getInstance();
        digits = (((minDigits == false) || ((scale == 0) && (millis >= 9.5))) ? 2 : 1);
        fmt.setMinimumIntegerDigits(digits);
        fmt.setMaximumIntegerDigits(2); // Max of 2
        fmt.setMinimumFractionDigits(0); // Don't need any
        fmt.setMaximumFractionDigits(scale);

        buf.append(fmt.format(millis));

        return buf.toString();
    }

    public static String repeatChars(char c, int nTimes) {
        char[] arr = new char[nTimes];

        for (int i = 0; i < nTimes; i++) {
            arr[i] = c;
        }

        return new String(arr);
    }

    /**
     * Capitalizes the first letter of str.
     *
     * @param  str The string to capitalize.
     *
     * @return A new string that is <code>str</code> capitalized. Returns <code>null</code> if str is null.
     */
    public static String capitalize(String str) {
        if (str == null) {
            return null;
        } else if (str.trim().equals("")) {
            return str;
        }

        String result = str.substring(0, 1).toUpperCase() + str.substring(1, str.length());

        return result;
    }

    public static String truncate(String s, int truncLength, boolean removeWhiteSpace) {
        String temp = ((s.length() > truncLength) ? (s.substring(0, truncLength) + "...") : s);
        if (removeWhiteSpace) {
            temp = temp.replaceAll("\\s+", " ");
        }

        return temp;
    }
}