/*
 * @(#)StringTools.java
 *
 * Copyright (c) 2002-2003 JSCAPE
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JSCAPE. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 * the terms of the license agreement you entered into with JSCAPE.
 */

package com.jscape.util;

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.jscape.util.Parameters.assertNotNull;
import static com.jscape.util.Parameters.assertTrue;

/**
 * String utilites. Provides some string-related services.
 *
 * @author Alex
 */
@SuppressWarnings("unchecked")
public final class StringTools {

    /**
     * Splits the specified string.
     *
     * @param source           source string
     * @param separators       separators
     * @param allowEmptyTokens if <code>true</code> empty tokens if any will be added to resulting array
     *
     * @return an array of tokens
     */
    public static String[] split(String source, String separators, boolean allowEmptyTokens) {
        assertNotNull(source);

        List ret = new ArrayList();
        StringBuilder word = new StringBuilder();

        for (int i = 0, size = source.length(); i < size; ++i) {
            for (; i < size && separators.indexOf(source.charAt(i)) < 0; ++i) {
                word.append(source.charAt(i));
            }

            String token = word.toString();
            word.setLength(0);

            if (token.length() > 0 || allowEmptyTokens) {
                ret.add(token);
            }
        }

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

    /**
     * Splitts the specified string to words considering quotes.
     *
     * @param source       source string
     * @param separators   separators
     * @param openQuote    open quote sign
     * @param closingQuote closing quote sign
     *
     * @return array of words
     */
    public static String[] splitQuoted(String source,
                                       String separators,
                                       char openQuote,
                                       char closingQuote,
                                       boolean allowEmptyTokens) {
        assertTrue(separators.indexOf(openQuote) < 0 && separators.indexOf(closingQuote) < 0, "Bad data quotes/separators.");

        List ret = new ArrayList();
        StringBuilder word = new StringBuilder();
        boolean quoted = false;

        for (int i = 0, size = source.length(); i < size; ++i) {
            if (separators.indexOf(source.charAt(i)) >= 0 && !quoted) {
                if (word.length() > 0 || allowEmptyTokens) {
                    ret.add(word.toString());
                }

                word.setLength(0);
                continue;
            }

            if (source.charAt(i) == openQuote && !quoted) {
                quoted = true;
                continue;
            }

            if (source.charAt(i) == closingQuote && quoted) {
                quoted = false;
                continue;
            }

            word.append(source.charAt(i));
        }

        if (word.length() > 0 || allowEmptyTokens) {
            ret.add(word.toString());
        }

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

    public static String[] splitQuoted(String source, String separators, String openQuotes, String closingQuotes) {
        List ret = new ArrayList();
        StringBuilder word = new StringBuilder();

        for (int i = 0, size = source.length(); i < size; ++i) {
            // skip separators
            while (i < size && separators.indexOf(source.charAt(i)) >= 0) {
                ++i;
            }

            if (i >= size) {
                // no more data
                break;
            } else if (openQuotes.indexOf(source.charAt(i)) != -1) {
                // read quoted words
                for (++i; i < size; ++i) {
                    if (closingQuotes.indexOf(source.charAt(i)) != -1) {
                        // inner quote?
                        if (i < (size - 1) && closingQuotes.indexOf(source.charAt(i + 1)) != -1) {
                            ++i;
                        } else {
                            break;
                        }
                    }

                    word.append(source.charAt(i));
                }
            } else {
                // read word
                for (; i < size && separators.indexOf(source.charAt(i)) < 0; ++i) {
                    word.append(source.charAt(i));
                }
            }

            ret.add(word.toString());
            word.setLength(0);
        }

        return (String[]) ret.toArray(new String[ret.size()]);
    }
    
    public static String combine(String separator, String... entries) {
        if (entries == null) {
            return "";
        }
    
        StringBuilder buffer = new StringBuilder();
        
        for (int i = 0; i < entries.length; ++i) {
            if (i > 0) {
                buffer.append(separator);
            }
            buffer.append(entries[i]);
        }
    
        return buffer.toString();
    }

    public static String combine(String separator, Iterable entries) {
        if (entries == null) {
            return "";
        }
    
        StringBuilder buffer = new StringBuilder();
        int i = 0;
        for (Object entry : entries) {
            if (i > 0) {
                buffer.append(separator);
            }
            buffer.append(entry);
            ++i;
        }

        return buffer.toString();
    }
    
    public static String[] copyTrimmed(String... entries) {
        String[] result = new String[entries.length];
        for (int i = 0; i < entries.length; ++i) {
            result[i] = entries[i].trim();
        }
        
        return result;
    }

    /**
     * Returns first word in line.
     *
     * @param line       target line
     * @param separators possible separators
     *
     * @return first word in line
     */
    public static String getFirstWord(String line, String separators) {
        StringBuilder word = new StringBuilder();

        for (int i = 0; i < line.length() && separators.indexOf(line.charAt(i)) < 0; ++i) {
            word.append(line.charAt(i));
        }

        return word.toString();
    }

    /**
     * Returns first word in line.
     *
     * @param line      target line
     * @param separator possible separator
     *
     * @return first word in line
     */
    public static String getFirstWord(String line, char separator) {
        int separatorIndex = line.indexOf(separator);
        return separatorIndex >= 0 ? line.substring(0, separatorIndex) : line;
    }

    /**
     * Stripps the string enclosing quotes.
     *
     * @param source       source string
     * @param openQuote    open quote string
     * @param closingQuote closing quote string
     *
     * @return stripped string
     */
    public static String strip(String source, String openQuote, String closingQuote) {
        assertNotNull(source);

        if (source.startsWith(openQuote)) {
            source = source.substring(openQuote.length());
        }

        if (source.endsWith(closingQuote)) {
            source = source.substring(0, source.length() - closingQuote.length());
        }

        return source;
    }

    /**
     * Replaces old string for the new string
     *
     * @param source
     * @param oldValue
     * @param newValue
     *
     * @return String
     */
    public static String replace(String source, String oldValue, String newValue) {
        // @ by Alex
        // this code doesn't work properly
        // moreover it doesn't replace more than one entry
        // moreover it replaces strings properly only if source starts with oldValue and newValue.length() == oldValue.length()
        // or source doesn't start with oldValue and newValue.length() == (2 * oldValue.length())

//        int index = source.indexOf(oldValue);
//        int length = oldValue.length();

//        if (index != -1) {
//           StringBuffer buffer = new StringBuffer(source);
//           if (index > 0 ) { length += length; }  // why ??? how is it related with the new string length?
//           return buffer.replace(index, length, newValue).toString(); // rewrites chars independed of new string length!!!
//        } else {
//           return source;
//        }

        // @ by Alex
        // workable variant
        // equals to String.replaceAll() in JDK 1.4 (without regex)
    
        StringBuilder buffer = new StringBuilder();

        int index;
        while ((index = source.indexOf(oldValue)) >= 0) {
            buffer.append(source.substring(0, index));
            buffer.append(newValue);
            int nextPartIndex = index + oldValue.length();
            source = nextPartIndex < source.length() ? source.substring(nextPartIndex) : "";
        }

        buffer.append(source);

        return buffer.toString();
    }

    public static String replaceLast(String input, String regex, String replacement) {
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(input);

        if (!matcher.find()) {
            return input;
        }

        int lastMatchStart = 0;

        do {
            lastMatchStart = matcher.start();
        } while (matcher.find());

        matcher.find(lastMatchStart);

        StringBuffer buffer = new StringBuffer(input.length());
        matcher.appendReplacement(buffer, replacement);
        matcher.appendTail(buffer);

        return buffer.toString();
    }

    /**
     * Verifies that the specified string is not null and not empty.
     *
     * @param value string to check
     *
     * @return is valid string boolean value
     */
    public static boolean isValidString(String value) {
        return value != null && !value.trim().isEmpty();
    }

    public static byte[] encode(String[] data, String separator, Charset charset) {
        StringBuilder buffer = new StringBuilder();

        for (int i = 0; i < data.length; ++i) {
            if (i > 0) {
                buffer.append(separator);
            }
            buffer.append(data[i]);
        }

        return buffer.toString().getBytes(charset);
    }

    public static String insert(String src, String what, int chars) {
        StringBuilder buffer = new StringBuilder();

        for (int i = 0; i < src.length(); ++i) {
            buffer.append(src.charAt(i));
            if (i > 0 && i % chars == 0) {
                buffer.append(what);
            }
        }

        buffer.append(what);

        return buffer.toString();
    }

    public static Integer intValueOf(String data, Integer defaultValue) {
        try {
            return Integer.valueOf(data);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    public static Long longValueOf(String data, Long defaultValue) {
        try {
            return Long.valueOf(data);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    public static Boolean booleanValueOf(String data, Boolean defaultValue) {
        try {
            return Boolean.valueOf(data);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    /**
     * Private constructor to avoid unnecessary instantiation.
     */
    private StringTools() {
        // empty
    }

}
