package net.i2p.android.i2ptunnel.util;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;

import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;

import net.i2p.I2PAppContext;
import net.i2p.android.router.R;
import net.i2p.android.wizard.model.Page;
import net.i2p.i2ptunnel.I2PTunnelConnectClient;
import net.i2p.i2ptunnel.I2PTunnelHTTPClient;
import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase;
import net.i2p.i2ptunnel.I2PTunnelIRCClient;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.util.ConcurrentHashSet;
import net.i2p.util.PasswordManager;

public class TunnelConfig {
    protected final I2PAppContext _context;

    private String _type;
    private String _name;
    private String _description;
    private String _i2cpHost;
    private String _i2cpPort;
    private String _tunnelDepth;
    private String _tunnelQuantity;
    private String _tunnelVariance;
    private String _tunnelBackupQuantity;
    private boolean _connectDelay;
    private String _customOptions;
    private String _proxyList;
    private String _port;
    private String _reachableBy;
    private String _targetDestination;
    private String _targetHost;
    private String _targetPort;
    private String _spoofedHost;
    private String _privKeyFile;
    private String _profile;
    private boolean _startOnLoad;
    private boolean _sharedClient;
    private final Set<String> _booleanOptions;
    private final Map<String, String> _otherOptions;
    private String _newProxyUser;
    private String _newProxyPW;

    static final String CLIENT_NICKNAME = "shared clients";

    public static TunnelConfig createFromWizard(
            Context ctx, TunnelControllerGroup tcg, Bundle data) {
        // Get the Bundle keys
        Resources res = ctx.getResources();

        String kClientServer = res.getString(R.string.i2ptunnel_wizard_k_client_server);
        String kType = res.getString(R.string.i2ptunnel_wizard_k_type);

        String kName = res.getString(R.string.i2ptunnel_wizard_k_name);
        String kDesc = res.getString(R.string.i2ptunnel_wizard_k_desc);
        String kDest = res.getString(R.string.i2ptunnel_wizard_k_dest);
        String kOutproxies = res.getString(R.string.i2ptunnel_wizard_k_outproxies);
        String kTargetHost = res.getString(R.string.i2ptunnel_wizard_k_target_host);
        String kTargetPort = res.getString(R.string.i2ptunnel_wizard_k_target_port);
        String kReachableOn = res.getString(R.string.i2ptunnel_wizard_k_reachable_on);
        String kBindingPort = res.getString(R.string.i2ptunnel_wizard_k_binding_port);
        String kAutoStart = res.getString(R.string.i2ptunnel_wizard_k_auto_start);

        // Create the TunnelConfig
        TunnelConfig cfg = new TunnelConfig();

        // Get/set the tunnel wizard settings
        String clientServer = data.getBundle(kClientServer).getString(Page.SIMPLE_DATA_KEY);
        String typeName = data.getBundle(clientServer + ":" + kType).getString(Page.SIMPLE_DATA_KEY);
        String type = TunnelUtil.getTypeFromName(typeName, ctx);
        cfg.setType(type);

        String name = data.getBundle(kName).getString(Page.SIMPLE_DATA_KEY);
        cfg.setName(name);

        String desc = data.getBundle(kDesc).getString(Page.SIMPLE_DATA_KEY);
        cfg.setDescription(desc);

        String dest = null;
        Bundle pageData = data.getBundle(kDest);
        if (pageData != null) dest = pageData.getString(Page.SIMPLE_DATA_KEY);
        cfg.setTargetDestination(dest);

        String outproxies = null;
        pageData = data.getBundle(kOutproxies);
        if (pageData != null) outproxies = pageData.getString(Page.SIMPLE_DATA_KEY);
        cfg.setProxyList(outproxies);

        String targetHost = null;
        pageData = data.getBundle(kTargetHost);
        if (pageData != null) targetHost = pageData.getString(Page.SIMPLE_DATA_KEY);
        cfg.setTargetHost(targetHost);

        String targetPort = null;
        pageData = data.getBundle(kTargetPort);
        if (pageData != null) targetPort = pageData.getString(Page.SIMPLE_DATA_KEY);
        cfg.setTargetPort(targetPort);

        String reachableOn = null;
        pageData = data.getBundle(kReachableOn);
        if (pageData != null) reachableOn = pageData.getString(Page.SIMPLE_DATA_KEY);
        cfg.setReachableBy(reachableOn);

        String bindingPort = null;
        pageData = data.getBundle(kBindingPort);
        if (pageData != null) bindingPort = pageData.getString(Page.SIMPLE_DATA_KEY);
        cfg.setPort(bindingPort);

        boolean autoStart = data.getBundle(kAutoStart).getBoolean(Page.SIMPLE_DATA_KEY);
        cfg.setStartOnLoad(autoStart);

        // Set sensible defaults for a new tunnel
        cfg.setTunnelDepth("3");
        cfg.setTunnelVariance("0");
        cfg.setTunnelQuantity("2");
        cfg.setTunnelBackupQuantity("0");
        cfg.setClientHost("internal");
        cfg.setClientport("internal");
        cfg.setCustomOptions("");
        if (!"streamrclient".equals(type)) {
            cfg.setProfile("bulk");
            cfg.setReduceCount("1");
            cfg.setReduceTime("20");
        }
        if (TunnelUtil.isClient(type)) { /* Client-only defaults */
            if (!"streamrclient".equals(type)) {
                cfg.setNewDest("0");
                cfg.setCloseTime("30");
            }
            if ("httpclient".equals(type) ||
                    "connectclient".equals(type) ||
                    "sockstunnel".equals(type) |
                    "socksirctunnel".equals(type)) {
                cfg.setProxyUsername("");
                cfg.setProxyPassword("");
                cfg.setOutproxyUsername("");
                cfg.setOutproxyPassword("");
            }
            if ("httpclient".equals(type))
                cfg.setJumpList("http://i2host.i2p/cgi-bin/i2hostjump?\nhttp://stats.i2p/cgi-bin/jump.cgi?a=");
        } else { /* Server-only defaults */
            cfg.setPrivKeyFile(TunnelUtil.getPrivateKeyFile(tcg, -1));
            cfg.setEncrypt("");
            cfg.setEncryptKey("");
            cfg.setAccessMode("0");
            cfg.setAccessList("");
            cfg.setLimitMinute("0");
            cfg.setLimitHour("0");
            cfg.setLimitDay("0");
            cfg.setTotalMinute("0");
            cfg.setTotalHour("0");
            cfg.setTotalDay("0");
            cfg.setMaxStreams("0");
        }

        return cfg;
    }

    public TunnelConfig() {
        _context = I2PAppContext.getGlobalContext();
        _booleanOptions = new ConcurrentHashSet<String>(4);
        _otherOptions = new ConcurrentHashMap<String,String>(4);
    }

    /**
     * What type of tunnel (httpclient, ircclient, client, or server).  This is 
     * required when adding a new tunnel.
     *
     */
    public void setType(String type) { 
        _type = (type != null ? type.trim() : null);   
    }
    String getType() { return _type; }

    /** Short name of the tunnel */
    public void setName(String name) { 
        _name = (name != null ? name.trim() : null);
    }
    /** one line description */
    public void setDescription(String description) { 
        _description = (description != null ? description.trim() : null);
    }
    /** I2CP host the router is on, ignored when in router context */
    public void setClientHost(String host) {
        _i2cpHost = (host != null ? host.trim() : null);
    }
    /** I2CP port the router is on, ignored when in router context */
    public void setClientport(String port) {
        _i2cpPort = (port != null ? port.trim() : null);
    }
    /** how many hops to use for inbound tunnels */
    public void setTunnelDepth(String tunnelDepth) { 
        _tunnelDepth = (tunnelDepth != null ? tunnelDepth.trim() : null);
    }
    /** how many parallel inbound tunnels to use */
    public void setTunnelQuantity(String tunnelQuantity) { 
        _tunnelQuantity = (tunnelQuantity != null ? tunnelQuantity.trim() : null);
    }
    /** how much randomisation to apply to the depth of tunnels */
    public void setTunnelVariance(String tunnelVariance) { 
        _tunnelVariance = (tunnelVariance != null ? tunnelVariance.trim() : null);
    }
    /** how many tunnels to hold in reserve to guard against failures */
    public void setTunnelBackupQuantity(String tunnelBackupQuantity) { 
        _tunnelBackupQuantity = (tunnelBackupQuantity != null ? tunnelBackupQuantity.trim() : null);
    }
    /** what I2P session overrides should be used */
    public void setCustomOptions(String customOptions) { 
        _customOptions = (customOptions != null ? customOptions.trim() : null);
    }
    /** what HTTP outproxies should be used (httpclient specific) */
    public void setProxyList(String proxyList) { 
        _proxyList = (proxyList != null ? proxyList.trim() : null);
    }
    /** what port should this client/httpclient/ircclient listen on */
    public void setPort(String port) { 
        _port = (port != null ? port.trim() : null);
    }
    /** 
     * what interface should this client/httpclient/ircclient listen on
     */
    public void setReachableBy(String reachableBy) { 
        _reachableBy = (reachableBy != null ? reachableBy.trim() : null);
    }
    /** What peer does this client tunnel point at */
    public void setTargetDestination(String dest) { 
        _targetDestination = (dest != null ? dest.trim() : null);
    }
    /** What host does this server tunnel point at */
    public void setTargetHost(String host) { 
        _targetHost = (host != null ? host.trim() : null);
    }
    /** What port does this server tunnel point at */
    public void setTargetPort(String port) { 
        _targetPort = (port != null ? port.trim() : null);
    }
    /** What host does this http server tunnel spoof */
    public void setSpoofedHost(String host) { 
        _spoofedHost = (host != null ? host.trim() : null);
    }
    /** What filename is this server tunnel's private keys stored in */
    public void setPrivKeyFile(String file) { 
        _privKeyFile = (file != null ? file.trim() : null);
    }
    /**
     * If called with true, we want this tunnel to start whenever it is
     * loaded (aka right now and whenever the router is started up)
     */
    public void setStartOnLoad(boolean val) {
        _startOnLoad = val;
    }
    public void setShared(boolean val) {
        _sharedClient=val;
    }
    public void setConnectDelay(String moo) {
        _connectDelay = true;
    }
    public void setProfile(String profile) { 
        _profile = profile; 
    }

    public void setReduce(String moo) {
        _booleanOptions.add("i2cp.reduceOnIdle");
    }
    public void setClose(String moo) {
        _booleanOptions.add("i2cp.closeOnIdle");
    }
    public void setEncrypt(String moo) {
        _booleanOptions.add("i2cp.encryptLeaseSet");
    }

    /** @since 0.8.9 */
    public void setDCC(String moo) {
        _booleanOptions.add(I2PTunnelIRCClient.PROP_DCC);
    }

    protected static final String PROP_ENABLE_ACCESS_LIST = "i2cp.enableAccessList";
    protected static final String PROP_ENABLE_BLACKLIST = "i2cp.enableBlackList";

    public void setAccessMode(String val) {
        if ("1".equals(val))
            _booleanOptions.add(PROP_ENABLE_ACCESS_LIST);
        else if ("2".equals(val))
            _booleanOptions.add(PROP_ENABLE_BLACKLIST);
    }

    public void setDelayOpen(String moo) {
        _booleanOptions.add("i2cp.delayOpen");
    }
    public void setNewDest(String val) {
        if ("1".equals(val))
            _booleanOptions.add("i2cp.newDestOnResume");
        else if ("2".equals(val))
            _booleanOptions.add("persistentClientKey");
    }

    public void setReduceTime(String val) {
        if (val != null) {
            try {
                _otherOptions.put("i2cp.reduceIdleTime", "" + (Integer.parseInt(val.trim()) * 60*1000));
            } catch (NumberFormatException nfe) {}
        }
    }
    public void setReduceCount(String val) {
        if (val != null)
            _otherOptions.put("i2cp.reduceQuantity", val.trim());
    }
    public void setEncryptKey(String val) {
        if (val != null)
            _otherOptions.put("i2cp.leaseSetKey", val.trim());
    }

    public void setAccessList(String val) {
        if (val != null)
            _otherOptions.put("i2cp.accessList", val.trim().replace("\r\n", ",").replace("\n", ",").replace(" ", ","));
    }

    public void setJumpList(String val) {
        if (val != null)
            _otherOptions.put(I2PTunnelHTTPClient.PROP_JUMP_SERVERS, val.trim().replace("\r\n", ",").replace("\n", ",").replace(" ", ","));
    }

    public void setCloseTime(String val) {
        if (val != null) {
            try {
                _otherOptions.put("i2cp.closeIdleTime", "" + (Integer.parseInt(val.trim()) * 60*1000));
            } catch (NumberFormatException nfe) {}
        }
    }

    /** all proxy auth @since 0.8.2 */
    public void setProxyAuth(String s) {
        if (s != null)
            _otherOptions.put(I2PTunnelHTTPClientBase.PROP_AUTH, I2PTunnelHTTPClientBase.DIGEST_AUTH);
    }

    public void setProxyUsername(String s) {
        if (s != null)
            _newProxyUser = s.trim();
    }

    public void setProxyPassword(String s) {
        if (s != null)
            _newProxyPW = s.trim();
    }

    public void setOutproxyAuth(String s) {
        _otherOptions.put(I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH, I2PTunnelHTTPClientBase.DIGEST_AUTH);
    }

    public void setOutproxyUsername(String s) {
        if (s != null)
            _otherOptions.put(I2PTunnelHTTPClientBase.PROP_OUTPROXY_USER, s.trim());
    }

    public void setOutproxyPassword(String s) {
        if (s != null)
            _otherOptions.put(I2PTunnelHTTPClientBase.PROP_OUTPROXY_PW, s.trim());
    }

    /** all of these are @since 0.8.3 */
    protected static final String PROP_MAX_CONNS_MIN = "i2p.streaming.maxConnsPerMinute";
    protected static final String PROP_MAX_CONNS_HOUR = "i2p.streaming.maxConnsPerHour";
    protected static final String PROP_MAX_CONNS_DAY = "i2p.streaming.maxConnsPerDay";
    protected static final String PROP_MAX_TOTAL_CONNS_MIN = "i2p.streaming.maxTotalConnsPerMinute";
    protected static final String PROP_MAX_TOTAL_CONNS_HOUR = "i2p.streaming.maxTotalConnsPerHour";
    protected static final String PROP_MAX_TOTAL_CONNS_DAY = "i2p.streaming.maxTotalConnsPerDay";
    protected static final String PROP_MAX_STREAMS = "i2p.streaming.maxConcurrentStreams";

    public void setLimitMinute(String s) {
        if (s != null)
            _otherOptions.put(PROP_MAX_CONNS_MIN, s.trim());
    }

    public void setLimitHour(String s) {
        if (s != null)
            _otherOptions.put(PROP_MAX_CONNS_HOUR, s.trim());
    }

    public void setLimitDay(String s) {
        if (s != null)
            _otherOptions.put(PROP_MAX_CONNS_DAY, s.trim());
    }

    public void setTotalMinute(String s) {
        if (s != null)
            _otherOptions.put(PROP_MAX_TOTAL_CONNS_MIN, s.trim());
    }

    public void setTotalHour(String s) {
        if (s != null)
            _otherOptions.put(PROP_MAX_TOTAL_CONNS_HOUR, s.trim());
    }

    public void setTotalDay(String s) {
        if (s != null)
            _otherOptions.put(PROP_MAX_TOTAL_CONNS_DAY, s.trim());
    }

    public void setMaxStreams(String s) {
        if (s != null)
            _otherOptions.put(PROP_MAX_STREAMS, s.trim());
    }

    /**
     * Based on all provided data, create a set of configuration parameters 
     * suitable for use in a TunnelController.  This will replace (not add to)
     * any existing parameters, so this should return a comprehensive mapping.
     *
     */
    public Properties getConfig() {
        Properties config = new Properties();
        updateConfigGeneric(config);
        
        if ((TunnelUtil.isClient(_type) && !"streamrclient".equals(_type)) || "streamrserver".equals(_type)) {
            // streamrserver uses interface
            if (_reachableBy != null)
                config.setProperty("interface", _reachableBy);
            else
                config.setProperty("interface", "");
        } else {
            // streamrclient uses targetHost
            if (_targetHost != null)
                config.setProperty("targetHost", _targetHost);
        }

        if (TunnelUtil.isClient(_type)) {
            // generic client stuff
            if (_port != null)
                config.setProperty("listenPort", _port);
            config.setProperty("sharedClient", _sharedClient + "");
            for (String p : _booleanClientOpts)
                config.setProperty("option." + p, "" + _booleanOptions.contains(p));
            for (String p : _otherClientOpts)
                if (_otherOptions.containsKey(p))
                    config.setProperty("option." + p, _otherOptions.get(p));
        } else {
            // generic server stuff
            if (_targetPort != null)
                config.setProperty("targetPort", _targetPort);
            for (String p : _booleanServerOpts)
                config.setProperty("option." + p, "" + _booleanOptions.contains(p));
            for (String p : _otherServerOpts)
                if (_otherOptions.containsKey(p))
                    config.setProperty("option." + p, _otherOptions.get(p));
        }

        // generic proxy stuff
        if ("httpclient".equals(_type) || "connectclient".equals(_type) || 
            "sockstunnel".equals(_type) ||"socksirctunnel".equals(_type)) {
            for (String p : _booleanProxyOpts)
                config.setProperty("option." + p, "" + _booleanOptions.contains(p));
            if (_proxyList != null)
                config.setProperty("proxyList", _proxyList);
        }

        // Proxy auth including migration to MD5
        if ("httpclient".equals(_type) || "connectclient".equals(_type)) {
            // Migrate even if auth is disabled
            // go get the old from custom options that updateConfigGeneric() put in there
            String puser = "option." + I2PTunnelHTTPClientBase.PROP_USER;
            String user = config.getProperty(puser);
            String ppw = "option." + I2PTunnelHTTPClientBase.PROP_PW;
            String pw = config.getProperty(ppw);
            if (user != null && pw != null && user.length() > 0 && pw.length() > 0) {
                String pmd5 = "option." + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_PREFIX +
                              user + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_SUFFIX;
                if (config.getProperty(pmd5) == null) {
                    // not in there, migrate
                    String realm = _type.equals("httpclient") ? I2PTunnelHTTPClient.AUTH_REALM
                                                              : I2PTunnelConnectClient.AUTH_REALM;
                    String hex = PasswordManager.md5Hex(realm, user, pw);
                    if (hex != null) {
                        config.setProperty(pmd5, hex);
                        config.remove(puser);
                        config.remove(ppw);
                    }
                }
            }
            // New user/password
            String auth = _otherOptions.get(I2PTunnelHTTPClientBase.PROP_AUTH);
            if (auth != null && !auth.equals("false")) {
                if (_newProxyUser != null && _newProxyPW != null &&
                    _newProxyUser.length() > 0 && _newProxyPW.length() > 0) {
                    String pmd5 = "option." + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_PREFIX +
                                  _newProxyUser + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_SUFFIX;
                    String realm = _type.equals("httpclient") ? I2PTunnelHTTPClient.AUTH_REALM
                                                              : I2PTunnelConnectClient.AUTH_REALM;
                    String hex = PasswordManager.md5Hex(realm, _newProxyUser, _newProxyPW);
                    if (hex != null)
                        config.setProperty(pmd5, hex);
                }
            }
        }

        if ("ircclient".equals(_type) || "client".equals(_type) || "streamrclient".equals(_type)) {
            if (_targetDestination != null)
                config.setProperty("targetDestination", _targetDestination);
        } else if ("httpserver".equals(_type) || "httpbidirserver".equals(_type)) {
            if (_spoofedHost != null)
                config.setProperty("spoofedHost", _spoofedHost);
        }
        if ("httpbidirserver".equals(_type)) {
            if (_port != null)
                config.setProperty("listenPort", _port);
            if (_reachableBy != null)
                config.setProperty("interface", _reachableBy);
            else if (_targetHost != null)
                config.setProperty("interface", _targetHost);
            else
                config.setProperty("interface", "");
        }

        if ("ircclient".equals(_type)) {
            boolean dcc = _booleanOptions.contains(I2PTunnelIRCClient.PROP_DCC);
            config.setProperty("option." + I2PTunnelIRCClient.PROP_DCC,
                               "" + dcc);
            // add some sane server options since they aren't in the GUI (yet)
            if (dcc) {
                config.setProperty("option." + PROP_MAX_CONNS_MIN, "3");
                config.setProperty("option." + PROP_MAX_CONNS_HOUR, "10");
                config.setProperty("option." + PROP_MAX_TOTAL_CONNS_MIN, "5");
                config.setProperty("option." + PROP_MAX_TOTAL_CONNS_HOUR, "25");
            }
        }

        return config;
    }
    
    private static final String _noShowOpts[] = {
        "inbound.length", "outbound.length", "inbound.lengthVariance", "outbound.lengthVariance",
        "inbound.backupQuantity", "outbound.backupQuantity", "inbound.quantity", "outbound.quantity",
        "inbound.nickname", "outbound.nickname", "i2p.streaming.connectDelay", "i2p.streaming.maxWindowSize",
        I2PTunnelIRCClient.PROP_DCC
        };
    private static final String _booleanClientOpts[] = {
        "i2cp.reduceOnIdle", "i2cp.closeOnIdle", "i2cp.newDestOnResume", "persistentClientKey", "i2cp.delayOpen"
        };
    private static final String _booleanProxyOpts[] = {
        I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH
        };
    private static final String _booleanServerOpts[] = {
        "i2cp.reduceOnIdle", "i2cp.encryptLeaseSet", PROP_ENABLE_ACCESS_LIST, PROP_ENABLE_BLACKLIST
        };
    private static final String _otherClientOpts[] = {
        "i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.closeIdleTime",
        "outproxyUsername", "outproxyPassword",
        I2PTunnelHTTPClient.PROP_JUMP_SERVERS,
        I2PTunnelHTTPClientBase.PROP_AUTH
        };
    private static final String _otherServerOpts[] = {
        "i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.leaseSetKey", "i2cp.accessList",
         PROP_MAX_CONNS_MIN, PROP_MAX_CONNS_HOUR, PROP_MAX_CONNS_DAY,
         PROP_MAX_TOTAL_CONNS_MIN, PROP_MAX_TOTAL_CONNS_HOUR, PROP_MAX_TOTAL_CONNS_DAY,
         PROP_MAX_STREAMS
        };

    /**
     *  do NOT add these to noShoOpts, we must leave them in for HTTPClient and ConnectCLient
     *  so they will get migrated to MD5
     *  TODO migrate socks to MD5
     */
    private static final String _otherProxyOpts[] = {
        "proxyUsername", "proxyPassword"
        };

    protected static final Set<String> _noShowSet = new HashSet<String>(64);
    protected static final Set<String> _nonProxyNoShowSet = new HashSet<String>(4);
    static {
        _noShowSet.addAll(Arrays.asList(_noShowOpts));
        _noShowSet.addAll(Arrays.asList(_booleanClientOpts));
        _noShowSet.addAll(Arrays.asList(_booleanProxyOpts));
        _noShowSet.addAll(Arrays.asList(_booleanServerOpts));
        _noShowSet.addAll(Arrays.asList(_otherClientOpts));
        _noShowSet.addAll(Arrays.asList(_otherServerOpts));
        _nonProxyNoShowSet.addAll(Arrays.asList(_otherProxyOpts));
    }

    private void updateConfigGeneric(Properties config) {
        config.setProperty("type", _type);
        if (_name != null)
            config.setProperty("name", _name);
        if (_description != null)
            config.setProperty("description", _description);
        if (!_context.isRouterContext()) {
            if (_i2cpHost != null)
                config.setProperty("i2cpHost", _i2cpHost);
            if ( (_i2cpPort != null) && (_i2cpPort.trim().length() > 0) ) {
                config.setProperty("i2cpPort", _i2cpPort);
            } else {
                config.setProperty("i2cpPort", "7654");
            }
        }
        if (_privKeyFile != null)
            config.setProperty("privKeyFile", _privKeyFile);
        
        if (_customOptions != null) {
            StringTokenizer tok = new StringTokenizer(_customOptions);
            while (tok.hasMoreTokens()) {
                String pair = tok.nextToken();
                int eq = pair.indexOf('=');
                if ( (eq <= 0) || (eq >= pair.length()) )
                    continue;
                String key = pair.substring(0, eq);
                if (_noShowSet.contains(key))
                    continue;
                // leave in for HTTP and Connect so it can get migrated to MD5
                // hide for SOCKS until migrated to MD5
                if ((!"httpclient".equals(_type)) &&
                    (! "connectclient".equals(_type)) &&
                    _nonProxyNoShowSet.contains(key))
                    continue;
                String val = pair.substring(eq+1);
                config.setProperty("option." + key, val);
            }
        }

        config.setProperty("startOnLoad", _startOnLoad + "");

        if (_tunnelQuantity != null) {
            config.setProperty("option.inbound.quantity", _tunnelQuantity);
            config.setProperty("option.outbound.quantity", _tunnelQuantity);
        }
        if (_tunnelDepth != null) {
            config.setProperty("option.inbound.length", _tunnelDepth);
            config.setProperty("option.outbound.length", _tunnelDepth);
        }
        if (_tunnelVariance != null) {
            config.setProperty("option.inbound.lengthVariance", _tunnelVariance);
            config.setProperty("option.outbound.lengthVariance", _tunnelVariance);
        }
        if (_tunnelBackupQuantity != null) {
            config.setProperty("option.inbound.backupQuantity", _tunnelBackupQuantity);
            config.setProperty("option.outbound.backupQuantity", _tunnelBackupQuantity);
        }
        if (_connectDelay)
            config.setProperty("option.i2p.streaming.connectDelay", "1000");
        else
            config.setProperty("option.i2p.streaming.connectDelay", "0");
        if (TunnelUtil.isClient(_type) && _sharedClient) {
            config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
            config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
        } else if (_name != null) {
            config.setProperty("option.inbound.nickname", _name);
            config.setProperty("option.outbound.nickname", _name);
        }
        if ("interactive".equals(_profile))
            // This was 1 which doesn't make much sense
            // The real way to make it interactive is to make the streaming lib
            // MessageInputStream flush faster but there's no option for that yet,
            // Setting it to 16 instead of the default but not sure what good that is either.
            config.setProperty("option.i2p.streaming.maxWindowSize", "16");
        else
            config.remove("option.i2p.streaming.maxWindowSize");
    }
}