/*
 * This file is part of the DITA Open Toolkit project hosted on
 * Sourceforge.net. See the accompanying license.txt file for
 * applicable licenses.
 */

/*
 * (c) Copyright IBM Corp. 2004, 2005 All Rights Reserved.
 */
package org.dita.dost.util;

import static org.dita.dost.util.Constants.*;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

import org.dita.dost.log.DITAOTJavaLogger;

/**
 * String relevant utilities.
 * 
 * @author Wu, Zhi Qiang
 */
public final class StringUtils {

    //Edited by william on 2009-11-8 for ampbug:2893664 start
    private static final String NOT_RESOLVE_ENTITY_LIST = "|lt|gt|quot|amp|";
    private static final String NOT_RESOLVE_ENTITY_CHAR = "|#38|";
    //Edited by william on 2009-11-8 for ampbug:2893664 end

    private StringUtils() {
    }

    /**
     * Assemble all elements in collection to a string.
     * 
     * @param coll -
     *            java.util.List
     * @param delim -
     *            Description of the Parameter
     * @return java.lang.String
     */
    @SuppressWarnings("rawtypes")
    public static String assembleString(final Collection coll, final String delim) {
        final StringBuffer buff = new StringBuffer(INT_256);
        Iterator iter = null;

        if ((coll == null) || coll.isEmpty()) {
            return "";
        }

        iter = coll.iterator();
        while (iter.hasNext()) {
            buff.append(iter.next().toString());

            if (iter.hasNext()) {
                buff.append(delim);
            }
        }

        return buff.toString();
    }

    /**
     * Escape XML characters.
     * Suggested by hussein_shafie
     * @param s value needed to be escaped
     * @return escaped value
     */
    public static String escapeXML(final String s){
        final char[] chars = s.toCharArray();
        return escapeXML(chars, 0, chars.length);
    }

    /**
     * Escape XML characters.
     * Suggested by hussein_shafie
     * @param chars char arrays
     * @param offset start position
     * @param length arrays lenth
     * @return escaped value
     */
    public static String escapeXML(final char[] chars, final int offset, final int length){
        final StringBuffer escaped = new StringBuffer();

        final int end = offset + length;
        for (int i = offset; i < end; ++i) {
            final char c = chars[i];

            switch (c) {
            case '\'':
                escaped.append("&apos;");
                break;
            case '\"':
                escaped.append("&quot;");
                break;
            case '<':
                escaped.append("&lt;");
                break;
            case '>':
                escaped.append("&gt;");
                break;
            case '&':
                escaped.append("&amp;");
                break;
            default:
                escaped.append(c);
            }
        }

        return escaped.toString();
    }

    /**
     * Get entity.
     * 
     * @param name entity name
     * @return entity
     */
    public static String getEntity(final String name) {

        return (name.startsWith("%")) ? (name + ";") : ("&" + name + ";");
    }

    /**
     * Check entity.
     * 
     * @param name entity name
     * @return ture if this entity needs to be resolved
     */
    public static boolean checkEntity(final String name) {
        // check whether this entity need resolve
        if (NOT_RESOLVE_ENTITY_LIST.indexOf(STICK + name.trim()
                + STICK) != -1 ||
                //Edited by william on 2009-11-8 for ampbug:2893664 start
                NOT_RESOLVE_ENTITY_CHAR.indexOf(STICK + name.trim()
                        + STICK) != -1 ) {
            //Edited by william on 2009-11-8 for ampbug:2893664 end
            return false;
        }
        return true;

    }

    /**
     * Replaces each substring of this string that matches the given string
     * with the given replacement. Differ from the JDK String.replaceAll function,
     * this method does not support regular expression based replacement on purpose.
     * 
     * @param input input string
     * @param pattern This pattern is recognized as it is. It will not solve
     *        as an regular expression.
     * @param replacement string used to replace with
     * @return replaced string
     * 
     */
    public static String replaceAll(final String input,
            final String pattern, final String replacement) {
        final StringBuffer result = new StringBuffer();
        int startIndex = 0;
        int newIndex = 0;

        while ((newIndex = input.indexOf(pattern, startIndex)) >= 0) {
            result.append(input.substring(startIndex, newIndex));
            result.append(replacement);
            startIndex = newIndex + pattern.length();
        }

        result.append(input.substring(startIndex));

        return result.toString();
    }

    /**
     * Get ASCII code of a string.
     * @param inStr input string
     * @return asscii code
     */
    public static String getAscii(final String inStr){
        final byte [] input = inStr.getBytes();
        /*byte [] output;
		ByteArrayInputStream byteIS = new ByteArrayInputStream(input);
		InputStreamReader reader = new InputStreamReader(byteIS,"UTF-8");
		char [] cbuf = new char[INT_128];
		int count = reader.read(cbuf);*/
        final StringBuffer ret = new StringBuffer(INT_1024);
        String strByte = null;
        for(int i = 0; i < input.length; i++){
            ret.append("\\\'");
            strByte = Integer.toHexString(input[i]);
            ret.append(strByte.substring(strByte.length()-2));
            //System.out.println(Integer.toHexString(input[i]));
            //System.out.println(strByte);
        }
        /*while(count > 0){
			output = (new String(cbuf, 0, count)).getBytes();
			for(int j = 0; j < output.length; j++){
				ret.append("\\\'");
				strByte = Integer.toHexString(output[j]);
				ret.append(strByte.substring(strByte.length()-2));
			}
			count = reader.read(cbuf);
		}*/

        return ret.toString();

    }

    /**
     * Get the props.
     * @param domains input domain
     * @return prop
     */
    public static String getExtProps (final String domains){
        final StringBuffer propsBuffer = new StringBuffer();
        int propsStart = domains.indexOf("a(props");
        int propsEnd = domains.indexOf(")",propsStart);
        while (propsStart != -1 && propsEnd != -1){
            propsBuffer.append(COMMA);
            propsBuffer.append(domains.substring(propsStart+2,propsEnd).trim());
            propsStart = domains.indexOf("a(props", propsEnd);
            propsEnd = domains.indexOf(")",propsStart);
        }
        return (propsBuffer.length() > 0) ? propsBuffer.substring(INT_1) : null;
    }



    /**
     * Restore map.
     * @param s input string
     * @return map created from string
     */
    public static Map<String, String> restoreMap(final String s) {
        final Map<String,String> copytoMap = new HashMap<String,String>();
        final StringTokenizer st = new StringTokenizer(s, COMMA);

        while (st.hasMoreTokens()) {
            final String entry = st.nextToken();
            final int index = entry.indexOf('=');
            copytoMap.put(entry.substring(0, index), entry.substring(index+1));
        }

        return copytoMap;
    }

    /**
     * Break down a string separated by commas into a string set.
     * @param s input string
     * @return string set
     */
    public static Set<String> restoreSet(final String s) {
        return restoreSet(s, COMMA);
    }

    /**
     * Break down a string separated by <code>delim</code> into a string set.
     * @param s String to be splitted
     * @param delim Delimiter to be used.
     * @return string set
     */
    public static Set<String> restoreSet(final String s, final String delim) {
        final Set<String> copytoSet = new HashSet<String>();

        if (StringUtils.isEmptyString(s)) {
            return copytoSet;
        }

        final StringTokenizer st = new StringTokenizer(s, delim);

        while (st.hasMoreTokens()) {
            final String entry = st.nextToken();
            if (!StringUtils.isEmptyString(entry)) {
                copytoSet.add(entry);
            }
        }
        return copytoSet;
    }

    /**
     * Return is the string is null or "".
     * @param s input string
     * @return true if the string is null or ""
     */
    public static boolean isEmptyString(final String s){
        return (s == null || s.trim().length() == 0);
    }

    /**
     * If target is null, return the value; else append value to target.
     * If withSpace is true, insert a blank between them.
     * @param target target to be appended
     * @param value value to append
     * @param withSpace whether insert a blank
     * @return processed string
     */
    public static String setOrAppend(final String target, final String value, final boolean withSpace){
        if(target == null){
            return value;
        }if(value == null){
            return target;
        }else{
            if(withSpace && !target.endsWith(STRING_BLANK)){
                return target + STRING_BLANK + value;
            }else{
                return target + value;
            }
        }
    }

    /**
     * Init sax driver info.
     * 
     * @deprecated use {@link #getXMLReader} instead to get the preferred SAX parser
     */
    @Deprecated
    public static void initSaxDriver(){
        //The default sax driver is set to xerces's sax driver
        final DITAOTJavaLogger logger = new DITAOTJavaLogger();
        try {
            Class.forName(SAX_DRIVER_DEFAULT_CLASS);
            System.setProperty(SAX_DRIVER_PROPERTY,SAX_DRIVER_DEFAULT_CLASS);
            logger.logInfo("Using XERCES.");
        } catch (final ClassNotFoundException e){
            try{
                Class.forName(SAX_DRIVER_SUN_HACK_CLASS);
                System.setProperty(SAX_DRIVER_PROPERTY,SAX_DRIVER_SUN_HACK_CLASS);
                logger.logInfo("Using XERCES in SUN JDK 1.5");
            }catch (final ClassNotFoundException ex){
                try {
                    Class.forName(SAX_DRIVER_CRIMSON_CLASS);
                    System.setProperty(SAX_DRIVER_PROPERTY,SAX_DRIVER_CRIMSON_CLASS);
                    logger.logInfo("Using CRIMSON");
                }catch (final ClassNotFoundException exc){
                    logger.logException(e);
                    logger.logException(ex);
                    logger.logException(exc);
                }
            }
        }

    }

    /**
     * Get preferred SAX parser.
     * 
     * Preferred XML readers are in order:
     * 
     * <ol>
     *   <li>{@link org.dita.dost.util.Constants#SAX_DRIVER_DEFAULT_CLASS Xerces}</li>
     *   <li>{@link org.dita.dost.util.Constants#SAX_DRIVER_SUN_HACK_CLASS Sun's Xerces}</li>
     *   <li>{@link org.dita.dost.util.Constants#SAX_DRIVER_CRIMSON_CLASS Crimson}</li>
     * </ol>
     * 
     * @return XML parser instance.
     * @throws SAXException if instantiating XMLReader failed
     */
    public static XMLReader getXMLReader() throws SAXException {
        if (System.getProperty(SAX_DRIVER_PROPERTY) != null) {
            return XMLReaderFactory.createXMLReader();
        }
        try {
            Class.forName(SAX_DRIVER_DEFAULT_CLASS);
            return XMLReaderFactory.createXMLReader(SAX_DRIVER_DEFAULT_CLASS);
        } catch (final ClassNotFoundException e) {
            try {
                Class.forName(SAX_DRIVER_SUN_HACK_CLASS);
                return XMLReaderFactory.createXMLReader(SAX_DRIVER_SUN_HACK_CLASS);
            } catch (final ClassNotFoundException ex) {
                try {
                    Class.forName(SAX_DRIVER_CRIMSON_CLASS);
                    return XMLReaderFactory.createXMLReader(SAX_DRIVER_CRIMSON_CLASS);
                } catch (final ClassNotFoundException exc){
                    return XMLReaderFactory.createXMLReader();
                }
            }
        }
    }

    /**
     * Return a Java Locale object.
     * @param anEncoding encoding
     * @return locale
     */

    public static Locale getLocale(final String anEncoding){
        Locale aLocale = null;
        String country = null;
        String language = null;
        String variant = null;

        //Tokenize the string using "-" as the token string as per IETF RFC4646 (superceeds RFC3066).

        final StringTokenizer tokenizer = new StringTokenizer(anEncoding, "-");

        //We need to know how many tokens we have so we can create a Locale object with the proper constructor.
        final int numberOfTokens = tokenizer.countTokens();

        if (numberOfTokens == 1) {
            final String tempString = tokenizer.nextToken().toLowerCase();

            //Note: Newer XML parsers should throw an error if the xml:lang value contains
            //underscore. But this is not guaranteed.

            //Check to see if some one used "en_US" instead of "en-US".
            //If so, the first token will contain "en_US" or "xxx_YYYYYYYY". In this case,
            //we will only grab the value for xxx.
            final int underscoreIndex = tempString.indexOf("_");

            if (underscoreIndex == -1){
                language = tempString;
            }else if (underscoreIndex == 2 || underscoreIndex == 3){
                //check is first subtag is two or three characters in length.
                language = tempString.substring(0, underscoreIndex);
            }

            aLocale = new Locale(language);
        } else if (numberOfTokens == 2) {

            language = tokenizer.nextToken().toLowerCase();

            final String subtag2 = tokenizer.nextToken();
            //All country tags should be three characters or less.
            //If the subtag is longer than three characters, it assumes that
            //is a dialect or variant.
            if (subtag2.length() <= 3){
                country = subtag2.toUpperCase();
                aLocale = new Locale(language, country);
            }else if (subtag2.length() > 3 && subtag2.length() <= 8){
                variant = subtag2;
                aLocale = new Locale(language, "", variant);
            }else if (subtag2.length() > 8){
                //return an error!
            }



        } else if (numberOfTokens >= 3) {

            language = tokenizer.nextToken().toLowerCase();
            final String subtag2 = tokenizer.nextToken();
            if (subtag2.length() <= 3){
                country = subtag2.toUpperCase();
            }else if (subtag2.length() > 3 && subtag2.length() <= 8){
                variant = subtag2;
            }else if (subtag2.length() > 8){
                //return an error!
            }
            variant = tokenizer.nextToken();

            aLocale = new Locale(language, country, variant);

        }else {
            //return an warning or do nothing.
            //The xml:lang attribute is empty.
            aLocale = new Locale(LANGUAGE_EN,
                    COUNTRY_US);

        }

        return aLocale;
    }

    //added by William on 2009-11-26 for bug:1628937 start
    /**
     * Get file's main name.
     * @param input input filename
     * @param marker delimiter
     * @return file's main name
     */
    public static String getFileName(final String input, final String marker){
        final int index = input.lastIndexOf(marker);
        if(index != -1){
            return input.substring(0, index);
        }else{
            return input;
        }
    }
    //added by William on 2009-11-26 for bug:1628937 end

    /**
     * Get max value.
     */
    public static Integer getMax(final String ul_depth, final String ol_depth, final String sl_depth,
            final String dl_depth, final String table_depth, final String stable_depth){

        final int unDepth = Integer.parseInt(ul_depth);
        final int olDepth = Integer.parseInt(ol_depth);
        final int slDepth = Integer.parseInt(sl_depth);
        final int dlDepth = Integer.parseInt(dl_depth);
        final int tableDepth = Integer.parseInt(table_depth);
        final int stableDepth = Integer.parseInt(stable_depth);

        int max = unDepth;
        if(olDepth > max){
            max = olDepth;
        }
        if(slDepth > max){
            max = slDepth;
        }
        if(dlDepth > max){
            max = dlDepth;
        }
        if(tableDepth > max){
            max = tableDepth;
        }
        if(stableDepth > max){
            max = stableDepth;
        }

        return max;

    }

    /**
     * Get max value.
     */
    public static Integer getMax(final String fn_depth, final String list_depth, final String dlist_depth, final String table_depth, final String stable_depth){

        final int fnDepth = Integer.parseInt(fn_depth);
        final int listDepth = Integer.parseInt(list_depth);
        final int dlistDepth = Integer.parseInt(dlist_depth);
        final int tableDepth = Integer.parseInt(table_depth);
        final int stableDepth = Integer.parseInt(stable_depth);

        int max = fnDepth;
        if(listDepth > max){
            max = listDepth;
        }
        if(dlistDepth > max){
            max = dlistDepth;
        }
        if(tableDepth > max){
            max = tableDepth;
        }
        if(stableDepth > max){
            max = stableDepth;
        }

        return max;

    }
}