/*
 * ====================================================================
 * Copyright (c) 2004-2012 TMate Software Ltd.  All rights reserved.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution.  The terms
 * are also available at http://svnkit.com/license.html
 * If newer versions of this license are posted there, you may use a
 * newer version instead, at your option.
 * ====================================================================
 */
package org.tmatesoft.svn.core.internal.wc;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.text.DateFormat;
import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.internal.io.svn.ISVNConnector;
import org.tmatesoft.svn.core.internal.io.svn.SVNTunnelConnector;
import org.tmatesoft.svn.core.internal.util.SVNHashMap;
import org.tmatesoft.svn.core.wc.ISVNConflictHandler;
import org.tmatesoft.svn.core.wc.ISVNMerger;
import org.tmatesoft.svn.core.wc.ISVNMergerFactory;
import org.tmatesoft.svn.core.wc.ISVNOptions;
import org.tmatesoft.svn.core.wc.SVNWCUtil;

/**
 * @version 1.3
 * @author  TMate Software Ltd.
 */
public class DefaultSVNOptions implements ISVNOptions, ISVNMergerFactory {

    private static final String MISCELLANY_GROUP = "miscellany";
    private static final String AUTH_GROUP = "auth";
    private static final String AUTOPROPS_GROUP = "auto-props";
    private static final String SVNKIT_GROUP = "svnkit";
    private static final String OLD_SVNKIT_GROUP = "javasvn";
    private static final String HELPERS_GROUP = "helpers";

    private static final String USE_COMMIT_TIMES = "use-commit-times";
    private static final String GLOBAL_IGNORES = "global-ignores";
    private static final String ENABLE_AUTO_PROPS = "enable-auto-props";
    private static final String STORE_AUTH_CREDS = "store-auth-creds";
    private static final String KEYWORD_TIMEZONE = "keyword_timezone";
    private static final String KEYWORD_LOCALE = "keyword_locale";
    private static final String EDITOR_CMD = "editor-cmd";
    private static final String DIFF_CMD = "diff-cmd";
    private static final String MERGE_TOOL_CMD = "merge-tool-cmd";
    private static final String NO_UNLOCK = "no-unlock";
    private static final String LOG_ENCODING = "log-encoding";
    private static final String PRESERVED_CONFLICT_FILE_EXTENSIONS = "preserved-conflict-file-exts";
    private static final String INTERACTIVE_COFLICTS = "interactive-conflicts";
    private static final String MIME_TYPES_FILE = "mime-types-file";
    private static final String GLOBAL_CHARSET = "global-charset";
    private static final String PASSWORD_STORES = "password-stores";
    private static final String DEFAULT_IGNORES = "*.o *.lo *.la *.al .libs *.so *.so.[0-9]* *.a *.pyc *.pyo *.rej *~ #*# .#* .*.swp .DS_Store";
    private static final String YES = "yes";

    private static final String NO = "no";
    private static final String DEFAULT_LOCALE = Locale.getDefault().toString();

    private static final String DEFAULT_TIMEZONE = TimeZone.getDefault().getID();
    private static final String[] DEFAULT_PASSWORD_STORE_TYPES = new String[]{
            DefaultSVNPersistentAuthenticationProvider.WINDOWS_CRYPTO_API_PASSWORD_STORAGE,
            DefaultSVNPersistentAuthenticationProvider.MAC_OS_KEYCHAIN_PASSWORD_STORAGE,
            DefaultSVNPersistentAuthenticationProvider.GNOME_KEYRING_PASSWORD_STORAGE
    };

    private boolean myIsReadonly;
    private File myConfigDirectory;
    private SVNCompositeConfigFile myConfigFile;
    private ISVNMergerFactory myMergerFactory;
    private ISVNConflictHandler myConflictResolver;

    private String myKeywordLocale = DEFAULT_LOCALE;
    private String myKeywordTimezone = DEFAULT_TIMEZONE;
    private SimpleDateFormat myKeywordDateFormat = new SimpleDateFormat("yyyy-MM-dd' 'HH:mm:ss' 'ZZZZ' ('E', 'dd' 'MMM' 'yyyy')'");
    private Map myConfigOptions;

    public DefaultSVNOptions() {
        this(null, true);
    }

    public DefaultSVNOptions(File directory, boolean readOnly) {
        myConfigDirectory = directory == null ? getDefaultConfigDir() : directory;
        myIsReadonly = readOnly;
    }

    public boolean isUseCommitTimes() {
        String value = getConfigFile().getPropertyValue(MISCELLANY_GROUP, USE_COMMIT_TIMES);
        return getBooleanValue(value, false);
    }

    public void setInMemoryConfigOptions(Map configOptions) {
        myConfigOptions = configOptions;
        if (myConfigFile != null) {
            myConfigFile.setGroupsToOptions(myConfigOptions);
        }
    }

    /**
     * Enables or disables the commit-times option.
     *
     * <p>
     * The commit-times option makes checkout/update/switch/revert operations put
     * last-committed timestamps on every file they touch.
     *
     * <p>
     * This option corresponds to
     * the <i>'use-commit-times'</i> option that can be found in the
     * SVN's <i>config</i> file under the <i>[miscellany]</i> section.
     *
     * @param useCommitTimes  <span class="javakeyword">true</span> to
     *                        enable commit-times, <span class="javakeyword">false</span>
     *                        to disable
     * @see                   #isUseCommitTimes()
     */
    public void setUseCommitTimes(boolean useCommitTimes) {
        getConfigFile().setPropertyValue(MISCELLANY_GROUP, USE_COMMIT_TIMES, useCommitTimes ? YES : NO, !myIsReadonly);
    }

    /**
     * Determines if the autoproperties option is enabled.
     *
     * <p>
     * Autoproperties are the properties that are automatically set
     * on files when they are added or imported.
     *
     * <p>
     * This option corresponds to the <i>'enable-auto-props'</i> option
     * that can be found in the SVN's <i>config</i> file under the
     * <i>[miscellany]</i> section.
     *
     * @return  <span class="javakeyword">true</span> if autoproperties
     *          are enabled, otherwise <span class="javakeyword">false</span>
     */
    public boolean isUseAutoProperties() {
        String value = getConfigFile().getPropertyValue(MISCELLANY_GROUP, ENABLE_AUTO_PROPS);
        return getBooleanValue(value, false);
    }

    /**
     * Enables or disables the autoproperties option.
     *
     * <p>
     * Autoproperties are the properties that are automatically set
     * on files when they are added or imported.
     *
     * <p>
     * This option corresponds to the <i>'enable-auto-props'</i> option
     * that can be found in the SVN's <i>config</i> file under the
     * <i>[miscellany]</i> section.
     *
     * @param useAutoProperties  <span class="javakeyword">true</span> to
     *                           enable autoproperties, <span class="javakeyword">false</span>
     *                           to disable
     * @see                      #isUseAutoProperties()
     */
    public void setUseAutoProperties(boolean useAutoProperties) {
        getConfigFile().setPropertyValue(MISCELLANY_GROUP, ENABLE_AUTO_PROPS, useAutoProperties ? YES : NO, !myIsReadonly);
    }

    /**
     * Determines if the authentication storage is enabled.
     *
     * <p>
     * The auth storage is used for disk-caching of all
     * authentication information: usernames, passwords, server certificates,
     * and any other types of cacheable credentials.
     *
     * <p>
     * This option corresponds to the
     * <i>'store-auth-creds'</i> option that can be found
     * in the SVN's <i>config</i> file under the <i>[auth]</i> section.
     *
     * @return  <span class="javakeyword">true</span> if auth storage
     *          is enabled, otherwise <span class="javakeyword">false</span>
     */
    public boolean isAuthStorageEnabled() {
        String value = getConfigFile().getPropertyValue(AUTH_GROUP, STORE_AUTH_CREDS);
        return getBooleanValue(value, true);
    }

    public boolean isKeepLocks() {
        String value = getConfigFile().getPropertyValue(MISCELLANY_GROUP, NO_UNLOCK);
        return getBooleanValue(value, false);
    }

    /**
     * Enables or disables the authentication storage.
     *
     * <p>
     * The auth storage is used for disk-caching of all
     * authentication information: usernames, passwords, server certificates,
     * and any other types of cacheable credentials.
     *
     * <p>
     * This option corresponds to the
     * <i>'store-auth-creds'</i> option that can be found
     * in the SVN's <i>config</i> file under the <i>[auth]</i> section.
     *
     * @param storeAuth  <span class="javakeyword">true</span> to
     *                   enable the auth storage, <span class="javakeyword">false</span>
     *                   to disable
     * @see              #isAuthStorageEnabled()
     */
    public void setAuthStorageEnabled(boolean storeAuth) {
        getConfigFile().setPropertyValue(AUTH_GROUP, STORE_AUTH_CREDS, storeAuth ? YES : NO, !myIsReadonly);
    }

    public void setKeepLocks(boolean keep) {
        getConfigFile().setPropertyValue(MISCELLANY_GROUP, NO_UNLOCK, keep ? YES : NO, !myIsReadonly);
    }

    public static boolean isIgnored(ISVNOptions options, String name) {
        String[] patterns = options.getIgnorePatterns();
        for (int i = 0; patterns != null && i < patterns.length; i++) {
            String pattern = patterns[i];
            if (matches(pattern, name)) {
                return true;
            }
        }
        return false;
    }

    public String[] getIgnorePatterns() {
        String value = getConfigFile().getPropertyValue(MISCELLANY_GROUP, GLOBAL_IGNORES);
        if (value == null) {
            value = DEFAULT_IGNORES;
        }
        Collection tokensList = new ArrayList();
        for (StringTokenizer tokens = new StringTokenizer(value, " \t"); tokens.hasMoreTokens();) {
            String token = tokens.nextToken();
            if ("".equals(token)) {
                continue;
            }
            tokensList.add(token);
        }
        return (String[]) tokensList.toArray(new String[tokensList.size()]);
    }

    /**
     * Sets global ignore patterns.
     *
     * <p>
     * The global ignore patterns describe the names of
     * files and directories that SVNKit should ignore during status, add and
     * import operations. Similar to the
     * <i>'global-ignores'</i> option that can be found in the SVN's <i>config</i>
     * file under the <i>[miscellany]</i> section.
     *
     * <p>
     * For example, to set all <code>.exe</code> files to be ignored include
     * <code>"*.exe"</code> pattern into <code>patterns</code>.
     *
     * <p>
     * If <code>patterns</code> is <span class="javakeyword">null</span> or
     * empty then all the patterns will be removed.
     *
     * @param patterns  an array of patterns (that usually contain wildcards)
     *                  that specify file and directory names to be ignored until
     *                  they are versioned
     * @see             #getIgnorePatterns()
     */
    public void setIgnorePatterns(String[] patterns) {
        if (patterns == null || patterns.length == 0) {
            getConfigFile().setPropertyValue(MISCELLANY_GROUP, GLOBAL_IGNORES, null, !myIsReadonly);
            return;
        }
        StringBuffer value = new StringBuffer();
        for (int i = 0; i < patterns.length; i++) {
            String pattern = patterns[i];
            if (pattern != null && !"".equals(pattern.trim())) {
                value.append(pattern);
                value.append(" ");
            }
        }
        String valueStr = value.toString().trim();
        if ("".equals(valueStr)) {
            valueStr = null;
        }
        getConfigFile().setPropertyValue(MISCELLANY_GROUP, GLOBAL_IGNORES, valueStr, !myIsReadonly);
    }

    /**
     * Removes a particular global ignore pattern.
     *
     * @param pattern a patterna to be removed
     * @see           #addIgnorePattern(String)
     */
    public void deleteIgnorePattern(String pattern) {
        if (pattern == null) {
            return;
        }
        String[] patterns = getIgnorePatterns();
        Collection newPatterns = new ArrayList();
        for (int i = 0; i < patterns.length; i++) {
            String s = patterns[i];
            if (!s.equals(pattern)) {
                newPatterns.add(s);
            }
        }
        patterns = (String[]) newPatterns.toArray(new String[newPatterns.size()]);
        setIgnorePatterns(patterns);
    }

    /**
     * Adds a new particular ignore pattern to global
     * ignore patterns.
     *
     * @param pattern an ignore pattern to be added
     * @see           #deleteIgnorePattern(String)
     */
    public void addIgnorePattern(String pattern) {
        if (pattern == null) {
            return;
        }
        String[] patterns = getIgnorePatterns();
        Collection oldPatterns = new ArrayList(Arrays.asList(patterns));
        if (!oldPatterns.contains(pattern)) {
            oldPatterns.add(pattern);
            patterns = (String[]) oldPatterns.toArray(new String[oldPatterns.size()]);
            setIgnorePatterns(patterns);
        }
    }

    /**
     * Returns autoproperties as a {@link java.util.Map}
     * where each key is a file name pattern and the corresponding
     * value is a string in the form of <code>"propName=propValue"</code>.
     *
     * @return a {@link java.util.Map} containing autoproperties
     */
    public Map getAutoProperties() {
        return getConfigFile().getProperties(AUTOPROPS_GROUP);
    }

    /**
     * Sets autoproperties that will be automatically put on all files
     * that will be added or imported.
     *
     * <p>
     * There can be several properties specified for one file pattern -
     * they should be delimited by ";".
     *
     * @param autoProperties  a {@link java.util.Map} which keys are file
     *                        name patterns and their values are strings
     *                        in the form of <code>"propName=propValue"</code>
     * @see                   #getAutoProperties()
     */
    public void setAutoProperties(Map autoProperties) {
        autoProperties = autoProperties == null ? Collections.EMPTY_MAP : autoProperties;
        Map existingProperties = getAutoProperties();
        for (Iterator names = existingProperties.keySet().iterator(); names.hasNext();) {
            String pattern = (String) names.next();
            String value = (String) existingProperties.get(pattern);
            if (value.equals(autoProperties.get(pattern))) {
                continue;
            }
            getConfigFile().setPropertyValue(AUTOPROPS_GROUP, pattern, null, false);
            names.remove();
        }
        // add all new
        for (Iterator names = autoProperties.keySet().iterator(); names.hasNext();) {
            String pattern = (String) names.next();
            String value = (String) autoProperties.get(pattern);
            if (value.equals(existingProperties.get(pattern))) {
                continue;
            }
            getConfigFile().setPropertyValue(AUTOPROPS_GROUP, pattern, value, false);
        }
        if (!myIsReadonly) {
            getConfigFile().save();
        }
    }

    public String getEditor() {
        return getConfigFile().getPropertyValue(HELPERS_GROUP, EDITOR_CMD);
    }

    public String getMergeTool() {
        return getConfigFile().getPropertyValue(HELPERS_GROUP, MERGE_TOOL_CMD);
    }

    /**
     * Removes a particular autoproperty by specifying a file name
     * pattern.
     *
     * @param pattern a file name pattern
     * @see           #setAutoProperty(String, String)
     *
     */
    public void deleteAutoProperty(String pattern) {
        getConfigFile().setPropertyValue(AUTOPROPS_GROUP, pattern, null, !myIsReadonly);
    }

    /**
     * Sets an autoproperty - binds a file name pattern with a
     * string in the form of <code>"propName=propValue"</code>.
     *
     * @param pattern      a file name pattern (usually containing
     *                     wildcards)
     * @param properties   a property for <code>pattern</code>
     */
    public void setAutoProperty(String pattern, String properties) {
        getConfigFile().setPropertyValue(AUTOPROPS_GROUP, pattern, properties, !myIsReadonly);
    }

    public boolean isInteractiveConflictResolution() {
        String value = getConfigFile().getPropertyValue(MISCELLANY_GROUP, INTERACTIVE_COFLICTS);
        return getBooleanValue(value, true);
    }

    public void setInteractiveConflictResolution(boolean interactive) {
        getConfigFile().setPropertyValue(MISCELLANY_GROUP, INTERACTIVE_COFLICTS, interactive ? YES : NO, !myIsReadonly);
    }

    public Map applyAutoProperties(File file, Map target) {
        String fileName = file.getName();
        target = target == null ? new SVNHashMap() : target;
        if (!isUseAutoProperties()) {
            return target;
        }
        Map autoProperties = getAutoProperties();
        for (Iterator names = autoProperties.keySet().iterator(); names.hasNext();) {
            String pattern = (String) names.next();
            String value = (String) autoProperties.get(pattern);
            if (value != null && !"".equals(value) && matches(pattern, fileName)) {
                StringBuffer token = new StringBuffer();
                for (int i = 0; i < value.length(); i++) {
                    char ch = value.charAt(i);
                    if (ch == ';' || i == value.length() - 1) {
                        if (i + 1 < value.length() && value.charAt(i + 1) == ';') {
                            // escaped ;
                            token.append(';');
                            i++;
                            if (i < value.length() - 1) {
                                continue;
                            }
                            // escaped at the end of the line.
                        }
                        if (ch != ';') {
                            // just last character.
                            token.append(ch);
                        }
                        // another token.
                        String t = token.toString().trim();
                        int index = t.indexOf('=');
                        if (index < 0) {
                            target.put(t, "");
                        } else {
                            String name = t.substring(0, index).trim();
                            String pValue = index == t.length() - 1 ? "" : t.substring(index + 1).trim();
                            if (!"".equals(name.trim())) {
                                if (pValue.startsWith("\"") && pValue.endsWith("\"") && pValue.length() > 1) {
                                    pValue = pValue.substring(1, pValue.length() - 1);
                                } else if (pValue.startsWith("\'") && pValue.endsWith("\'") && pValue.length() > 1) {
                                    pValue = pValue.substring(1, pValue.length() - 1);
                                }
                                target.put(name, pValue);
                            }
                        }
                        token = token.delete(0, token.length());
                    } else {
                        token.append(ch);
                    }
                }
            }
        }
        return target;
    }

    public ISVNMergerFactory getMergerFactory() {
        if (myMergerFactory == null) {
            return this;
        }
        return myMergerFactory;
    }

    /**
     * Sets a factory object which is responsible for creating
     * merger drivers.
     *
     * @param mergerFactory  a factory that produces merger drivers
     *                for merge operations
     * @see           #getMergerFactory()
     */
    public void setMergerFactory(ISVNMergerFactory mergerFactory) {
        myMergerFactory = mergerFactory;
    }

    /**
     * Returns the value of a property from the <i>[svnkit]</i> section
     * of the <i>config</i> file.
     *
     * @param   propertyName a SVNKit specific config property name
     * @return the value of the property
     */
    public String getPropertyValue(String propertyName) {
        if (propertyName == null) {
            return null;
        }
        String value = getConfigFile().getPropertyValue(SVNKIT_GROUP, propertyName);
        if (value == null) {
            value = getConfigFile().getPropertyValue(OLD_SVNKIT_GROUP, propertyName);
        }
        return value;
    }

    /**
     * Sets the value of a property from the <i>[svnkit]</i> section
     * of the <i>config</i> file.
     *
     * @param   propertyName   a SVNKit specific config property name
     * @param   propertyValue  a new value for the property; if
     *                         <span class="javakeyword">null</span> the
     *                         property is removed
     */
    public void setPropertyValue(String propertyName, String propertyValue) {
        if (propertyName == null || "".equals(propertyName.trim())) {
            return;
        }
        getConfigFile().setPropertyValue(SVNKIT_GROUP, propertyName, propertyValue, !myIsReadonly);
    }

    public void setConflictHandler(ISVNConflictHandler resolver) {
        myConflictResolver = resolver;
    }

    public ISVNConflictHandler getConflictResolver() {
        return myConflictResolver;
    }

    public static boolean matches(String pattern, String fileName) {
        if (pattern == null || fileName == null) {
            return false;
        }
        Pattern compiled = compileNamePatter(pattern);
        if (compiled != null) {
            return compiled.matcher(fileName).matches();
        }
        return false;
    }

    public ISVNMerger createMerger(byte[] conflictStart, byte[] conflictSeparator, byte[] conflictEnd) {
        return new DefaultSVNMerger(conflictStart, conflictSeparator, conflictEnd, myConflictResolver, SVNDiffConflictChoiceStyle.CHOOSE_MODIFIED_LATEST);
    }

    public ISVNConnector createTunnelConnector(SVNURL url) {
	    String subProtocolName = url.getProtocol().substring("svn+".length());
        if (subProtocolName == null) {
            return null;
        }
        Map tunnels = getConfigFile().getProperties("tunnels");
	    final String tunnel = (String)tunnels.get(subProtocolName);
	    if (tunnel == null) {
		    return null;
	    }
	    return new SVNTunnelConnector(subProtocolName, tunnel);
    }

    public DateFormat getKeywordDateFormat() {
        String localeID = getConfigFile().getPropertyValue(SVNKIT_GROUP, KEYWORD_LOCALE);
        if (localeID == null) {
            localeID = DEFAULT_LOCALE;
        }
        String tzID = getConfigFile().getPropertyValue(SVNKIT_GROUP, KEYWORD_TIMEZONE);
        if (tzID == null) {
            tzID = DEFAULT_TIMEZONE;
        }
        if (!myKeywordTimezone.equals(tzID)) {
            TimeZone tz = TimeZone.getTimeZone(tzID);
            myKeywordTimezone = tzID;
            synchronized (myKeywordDateFormat) {
               myKeywordDateFormat.setTimeZone(tz);
            }
        }
        if (!myKeywordLocale.equals(localeID)) {
            Locale newLocale = toLocale(localeID);
            if (newLocale == null) {
                newLocale = Locale.getDefault();
            }
            myKeywordLocale = localeID;
            synchronized (myKeywordDateFormat) {
               myKeywordDateFormat.setCalendar(Calendar.getInstance(myKeywordDateFormat.getTimeZone(), newLocale));
               myKeywordDateFormat.setDateFormatSymbols(new DateFormatSymbols(newLocale));
            }
        }
        return myKeywordDateFormat;
    }

    public String[] getPreservedConflictFileExtensions() {
        String value = getConfigFile().getPropertyValue(MISCELLANY_GROUP, PRESERVED_CONFLICT_FILE_EXTENSIONS);
        if (value == null) {
            value = "";
        }
        Collection tokensList = new ArrayList();
        for (StringTokenizer tokens = new StringTokenizer(value, " \n\r\t"); tokens.hasMoreTokens();) {
            String token = tokens.nextToken();
            if ("".equals(token)) {
                continue;
            }
            tokensList.add(token);
        }
        return (String[]) tokensList.toArray(new String[tokensList.size()]);
    }

    public boolean isAllowAllForwardMergesFromSelf() {
        return false;
    }

    public String getLogEncoding() {
        return getConfigFile().getPropertyValue(MISCELLANY_GROUP, LOG_ENCODING);
    }

    public String getGlobalCharset() {
        return getPropertyValue(GLOBAL_CHARSET);
    }

    public void setGlobalCharset(String charset) {
        setPropertyValue(GLOBAL_CHARSET, charset);
    }

    public String getNativeCharset() {
        return System.getProperty("file.encoding");
    }

    public byte[] getNativeEOL() {
        return System.getProperty("line.separator").getBytes();
    }

    public Map getFileExtensionsToMimeTypes() {
        String mimeTypesFile = getConfigFile().getPropertyValue(MISCELLANY_GROUP, MIME_TYPES_FILE);
        if (mimeTypesFile == null) {
            return null;
        }

        BufferedReader reader = null;
        Map extensionsToMimeTypes = new SVNHashMap();
        try {
            reader = new BufferedReader(new FileReader(mimeTypesFile));
            LinkedList tokensList = new LinkedList();
            String line;
            while ((line = reader.readLine()) != null) {
                if (line.startsWith("#")) {
                    continue;
                }

                tokensList.clear();
                for (StringTokenizer tokens = new StringTokenizer(line, " \t"); tokens.hasMoreTokens();) {
                    String token = tokens.nextToken();
                    if ("".equals(token)) {
                        continue;
                    }
                    tokensList.add(token);
                }
                if (tokensList.size() < 2) {
                    continue;
                }

                String mimeType = (String) tokensList.get(0);
                for (int i = 1; i < tokensList.size(); i++) {
                    String extension = (String) tokensList.get(i);
                    extensionsToMimeTypes.put(extension, mimeType);
                }
            }
        } catch (IOException e) {
            return null;
        } finally {
            SVNFileUtil.closeFile(reader);
        }

        return extensionsToMimeTypes;
    }

    public String getDiffCommand() {
        return getConfigFile().getPropertyValue(HELPERS_GROUP, DIFF_CMD);
    }

    public void setDiffCommand(String diffCmd) {
        getConfigFile().setPropertyValue(HELPERS_GROUP, DIFF_CMD, diffCmd, !myIsReadonly);
    }

    public String[] getPasswordStorageTypes() {
        String storeTypesOption = getConfigFile().getPropertyValue(AUTH_GROUP, PASSWORD_STORES);
        if (storeTypesOption == null) {
            return DEFAULT_PASSWORD_STORE_TYPES;
        }
        List storeTypes = new ArrayList();
        for (StringTokenizer types = new StringTokenizer(storeTypesOption, " ,"); types.hasMoreTokens();) {
            String type = types.nextToken();
            type = type == null ? null : type.trim();
            if (type != null && !"".equals(type)) {
                storeTypes.add(type);
            }
        }
        return (String[]) storeTypes.toArray(new String[storeTypes.size()]);
    }

    private String getDefaultSSHCommandLine() {
        Map tunnels = getConfigFile().getProperties("tunnels");
        if (tunnels == null || !tunnels.containsKey("ssh")) {
            return null;
        }
        return (String) tunnels.get("ssh");
    }

    private String getDefaultSSHOptionValue(String optionName, String systemProperty, String fallbackSystemProperty) {
        if (optionName != null) {
            String sshCommandLine = getDefaultSSHCommandLine();
            if (sshCommandLine != null) {
                String value = getOptionValue(sshCommandLine, optionName);
                if (value != null) {
                    return value;
                }
            }
        }
        return System.getProperty(systemProperty, System.getProperty(fallbackSystemProperty));
    }

    public int getDefaultSSHPortNumber() {
        String sshCommandLine = getDefaultSSHCommandLine();
        if (sshCommandLine == null) {
            return -1;
        }
        final String portOption = sshCommandLine.toLowerCase().trim().startsWith("plink") ? "-p" : "-P";
        String port = getDefaultSSHOptionValue(portOption, "svnkit.ssh2.port", "javasvn.ssh2.port");
        if (port != null) {
            try {
                return Integer.parseInt(port);
            } catch (NumberFormatException e) {
                //
            }
        }
        return -1;
    }

    public String getDefaultSSHUserName() {
        String userName = getDefaultSSHOptionValue("-l", "svnkit.ssh2.username", "javasvn.ssh2.username");
        if (userName == null) {
            userName = System.getProperty("user.name");
        }
        return userName;
    }

    public String getDefaultSSHPassword() {
        return getDefaultSSHOptionValue("-pw", "svnkit.ssh2.password", "javasvn.ssh2.password");
    }

    public String getDefaultSSHKeyFile() {
        return getDefaultSSHOptionValue("-i", "svnkit.ssh2.key", "javasvn.ssh2.key");
    }

    public String getDefaultSSHPassphrase() {
        return getDefaultSSHOptionValue(null, "svnkit.ssh2.passphrase", "javasvn.ssh2.passphrase");
    }

    private SVNCompositeConfigFile getConfigFile() {
        if (myConfigFile == null) {
            SVNConfigFile.createDefaultConfiguration(myConfigDirectory);
            SVNConfigFile userConfig = new SVNConfigFile(new File(myConfigDirectory, "config"));
            SVNConfigFile systemConfig = new SVNConfigFile(new File(SVNFileUtil.getSystemConfigurationDirectory(), "config"));
            myConfigFile = new SVNCompositeConfigFile(systemConfig, userConfig);
            myConfigFile.setGroupsToOptions(myConfigOptions);
        }
        return myConfigFile;
    }

    private static String getOptionValue(String commandLine, String optionName) {
        if (commandLine == null || optionName == null) {
            return null;
        }
        for (StringTokenizer options = new StringTokenizer(commandLine, " \r\n\t"); options.hasMoreTokens();) {
            String option = options.nextToken().trim();
            if (optionName.equals(option) && options.hasMoreTokens()) {
                return options.nextToken();
            } else if (option.startsWith(optionName)) {
                return option.substring(optionName.length());
            }
        }
        return null;
    }

    private static Pattern compileNamePatter(String wildcard) {
        if (wildcard == null) {
            return null;
        }
        StringBuffer result = new StringBuffer();
        for (int i = 0; i < wildcard.length(); i++) {
            char ch = wildcard.charAt(i);
            switch (ch) {
            case '?':
                result.append(".");
                break;
            case '*':
                result.append(".*");
                break;
                
            case '\\':                
                if (i + 1 < wildcard.length()) {
                    ch = wildcard.charAt(i + 1);
                    if (ch == '\\') {
                        i++;
                    } else {
                        break;
                    }
                }
            case '.':
            case '!':
            case '$':
            case '(':
            case ')':
            case '+':
            case '<':
            case '>':
            case '|':
            case '^':
            case '{':
            case '}':
                result.append("\\");
            default:
                result.append(ch);
            }
        }
        try {
            return Pattern.compile(result.toString());
        } catch (PatternSyntaxException e) {
            return null;
        }
    }

    private static Locale toLocale(String str) {
        if (str == null) {
            return null;
        }
        int len = str.length();
        if (len != 2 && len != 5 && len < 7) {
            return null;
        }
        char ch0 = str.charAt(0);
        char ch1 = str.charAt(1);
        if (ch0 < 'a' || ch0 > 'z' || ch1 < 'a' || ch1 > 'z') {
            return null;
        }
        if (len == 2) {
            return new Locale(str, "");
        }
        if (str.charAt(2) != '_') {
            return null;
        }
        char ch3 = str.charAt(3);
        char ch4 = str.charAt(4);
        if (ch3 < 'A' || ch3 > 'Z' || ch4 < 'A' || ch4 > 'Z') {
            return null;
        }
        if (len == 5) {
            return new Locale(str.substring(0, 2), str.substring(3, 5));
        }
        if (str.charAt(5) != '_') {
            return null;
        }
        return new Locale(str.substring(0, 2), str.substring(3, 5), str.substring(6));
    }

    private static File getDefaultConfigDir() {
        return SVNWCUtil.getDefaultConfigurationDirectory();
    }

    public static boolean getBooleanValue(String value, boolean defaultValue) {
        if (value == null) {
            return defaultValue;
        }
        value = value.trim();
        return YES.equalsIgnoreCase(value) || "true".equalsIgnoreCase(value)
                || "on".equalsIgnoreCase(value);
    }

}
