/*
 *  PHEX - The pure-java Gnutella-servent.
 *  Copyright (C) 2001 - 2004 Gregor Koukkoullis ( phex <at> kouk <dot> de )
 *  Copyright (C) 2001 Konrad Haenel ( alterego1@gmx.at )
 *  Copyright (C) 2000 William W. Wong ( williamw@jps.net )
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  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 for more details.
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package phex.common;

import java.io.*;
import java.lang.reflect.*;
import java.util.*;

import phex.Res;
import phex.msg.GUID;
import phex.utils.*;

public class Cfg
{
    private static final String BACKUP_CONFIG_FILENAME = "phex.bk";
    public static final int DEFAULT_SOCKS5_PORT = 1080;
    public static final int DEFAULT_HTTP_PORT = 80;
    public static final int DEFAULT_MAX_MESSAGE_LENGTH = 65536;
    public static final short DEFAULT_LOGGER_VERBOSE_LEVEL = 6;
    public static final boolean DEFAULT_ENABLE_HIT_SNOOPING = true;
    public static final boolean DEFAULT_IS_CHAT_ENABLED = true;
    public static final boolean DEFAULT_ALLOW_TO_BECOME_LEAF = true;
    public static final boolean DEFAULT_ALLOW_TO_BECOME_ULTRAPEER = true;
    public static final boolean DEFAULT_FORCE_UP_CONNECTIONS = false;
    public static final boolean DEFAULT_IS_NOVENDOR_NODE_DISCONNECTED = false;
    public static final int DEFAULT_FREELOADER_FILES = 0;
    public static final int DEFAULT_FREELOADER_SHARE_SIZE = 0;
    public static final int DEFAULT_HOST_ERROR_DISPLAY_TIME = 1000;
    public static final int DEFAULT_TTL = 7;
    public static final int DEFAULT_MAX_NETWORK_TTL = 7;
    public static final int DEFAULT_UP_2_UP_CONNECTIONS = 16;
    public static final int DEFAULT_UP_2_LEAF_CONNECTIONS = 15;
    public static final int DEFAULT_UP_2_PEER_CONNECTIONS = 4;
    public static final int DEFAULT_LEAF_2_UP_CONNECTIONS = 5;
    public static final int DEFAULT_LEAF_2_PEER_CONNECTIONS = 2;
    public static final int DEFAULT_PEER_CONNECTIONS = 4;
    public static final int DEFAULT_MAX_CONNECTTO_HISTORY_SIZE = 10;
    public static final int DEFAULT_MAX_SEARCHTERM_HISTORY_SIZE = 10;
    public static final boolean DEFAULT_ARE_PARTIAL_FILES_SHARED = true;
    private static final char LIST_PREFIX = '_';
    
    public static final short DEFAULT_URN_CALCUATION_MODE = 2;
    
    /**
     * The default value of the X-Max-TTL header for dynamic queries. 
     */
    public static final int DEFAULT_DYNAMIC_QUERY_MAX_TTL = 4;

    /**
     * The default value to indicate if this node is forced to be ultrapeer.
     */
    public static final boolean DEFAULT_FORCE_TOBE_ULTRAPEER = false;

    /**
     * The default max downloads that are allowed per IP.
     */
    public static final int DEFAULT_MAX_DOWNLOADS_PER_IP = 1;

    /**
     * The default value to indicate whether upload queuing is activated or not.
     */
    public static final boolean DEFAULT_ALLOW_UPLOAD_QUEUING = true;

    /**
     * The default max upload queue slots available.
     */
    public static final int DEFAULT_MAX_UPLOAD_QUEUE_SIZE = 100;

    /**
     * The default min poll time for queued uploads.
     */
    public static final int DEFAULT_MIN_UPLOAD_QUEUE_POLL_TIME = 45;

    /**
     * The default max poll time for queued uploads.
     */
    public static final int DEFAULT_MAX_UPLOAD_QUEUE_POLL_TIME = 120;
    
    /**
     * The default setting if we accept deflate connections.
     */
    public static final boolean DEFAULT_IS_DEFLATE_CONNECTION_ACCEPTED = true;
    
    /**
     * The default setting of the last shown update info id from the Phex web
     * server.
     */
    public static final int DEFAULT_LAST_SHOWN_UPDATE_INGO_ID = 0;

    public static final int UNLIMITED_BANDWIDTH = Integer.MAX_VALUE;
    public static int MIN_SEARCH_TERM_LENGTH = 2;

    public final static String GENERAL_GNUTELLA_NETWORK = "<General Gnutella Network>";

    /**
     * The default segment size for a allocated segment.
     */
    public static final int INITIAL_SEGMENT_SIZE = 16 * 1024; // 16Kb

    /**
     * When a segment has been downloaded by a candidate, the next segment for this candidate
     * should be such that (at the same rate as the previous segment) the segment takes 90 seconds
     * to complete
     */
    public static final short SEGMENT_TRANSFER_TIME = 90;

    /**
     * If a segment is transferred at less than this speed (b/sec), refuse further downloads
     * from this candidate
     */
    public static final long MINIMUM_ALLOWED_TRANSFER_RATE = 1;
    
    /**
     * Do not allow segment to be larger than this value, regardless of the previous
     * segment's download rate
     */
    public static final long MAXIMUM_SEGMENT_SIZE = 10 * 1024 * 1024; // 10Mb
    
    
    /*
     * Default ordering method for the segments of newly downloaded files
     * 1: Order by position
     * 2: Order by rating
     * 3: Ordering for preview
     * First digit: for streamable files
     * Second digit: for unstreamable files
     * Third digit: for other files (ie: unmatched by above)
     */
    public static final int DEFAULT_ORDERING_METHOD = 322;
    
    public int initialSegmentSize;
    public int segmentTransferTime;
    public long minimumAllowedTransferRate;
    public long maximumSegmentSize;


    public GUID					mProgramClientID = new GUID();
    public String mMyIP = "";
    public int					mListeningPort = -1;
    public int					mMaxUpload = 4;
    public int					mMaxUploadPerIP = 1;
    public int					mUploadMaxBandwidth = 102400;
    public int                  mNetMaxHostToCatch = 1000;
    public int					mNetMaxSendQueue = 500;
    //public int mPingFrequency = 15000;
    public int					mSearchMaxConcurrent = 10;

    public int					mNetMaxRate = 50000;

    public int mDownloadMaxBandwidth = 102400;
    public boolean				mDownloadAutoRemoveCompleted = false;
    public String				mDownloadDir = ".";
    public int					mDownloadMaxRetry = 999;
    public int					mDownloadRetryWait = 4*1000;

    public boolean mAutoConnect = true;

    /**
     * @deprecated New variable called peerConnections. Only used to update for
     *             pre 0.8 versions.
     */
    public int mNetMinConn = 4;

    public boolean mAutoCleanup = true;

    /**
     * The max of this value should be 255. The protocol is not able to handle
     * more.
     */
    public int mUploadMaxSearch = 64;
    //public boolean mShareEnabled = true;
    public boolean mShareBrowseDir = true;
    public int mPushTransferTimeout = 30 * 1000;

    public String				mCurrentNetwork = GENERAL_GNUTELLA_NETWORK;
    public ArrayList mNetNetworkHistory = new ArrayList();
    public boolean				mAutoJoin = true;
    public boolean				mDisconnectApplyPolicy = true;
    public int					mDisconnectDropRatio = 70;
    public boolean				mProxyUse = false;
    public String				mProxyHost = "";
    public int mProxyPort = DEFAULT_SOCKS5_PORT;
    public boolean useProxyAuthentication = false;
    public String mProxyUserName = "";
    public String mProxyPassword = "";
    public String				mFindText = "";
    public boolean				mFindMatchCase = false;
    public boolean				mFindDown = true;
    public boolean				mUIDisplayTooltip = true;
    public String				mUploadDir = "";
    public String				mUploadFileExclusions = "";
    public String				mUploadFileInclusions = "*";
    public boolean				mUploadScanRecursively = true;
    public boolean				mUploadAutoRemoveCompleted = false;
    
    /**
     * Determines the urn calculation speed mode. Values should range
     * from 0 for high speed (full CPU) calculation up to maybe 10.
     * A good value is 2 which is also the default. The value states
     * the wait cycles between each 64K segment. A value of 2 means
     * wait twice as long as you needed to calculate the last 64K. 
     */
    public short urnCalculationMode;
    
    public boolean monitorSearchHistory = false;
    
    

    /**
     * The file into which searches should be monitored.
     */
    public String searchMonitorFile = "";
    public int searchHistoryLength = 10;

    /**
     * Indicates if the node is connected to a Local Area Network (LAN).
     */
    public boolean connectedToLAN = true;

    /**
     * Indicates whether Phex minimizes to the background on close of the GUI. If set to
     * false it will shutdown. If set to true on windows system it will go into the
     * sys tray, on all other system it will just minimize.
     */
    public boolean minimizeToBackground = true;

    /**
     * The status if a close options dialog should be displayed or not.
     */
    public boolean showCloseOptionsDialog = true;

    /**
     * The directory where incomplete files are stored.
     */
    public String incompleteDir = ".";

    /**
     * When the HostCatcher finds hosts it will first use the port filter
     * to see if it is allowed to use the host
     */
    public ArrayList filteredCatcherPorts = new ArrayList();

    /**
     * The max number of parallel workers per download file.
     */
    public short maxWorkerPerDownload = 3;

    /**
     * The max number of total parallel workers for all download files.
     */
    public short maxTotalDownloadWorker = 6;

    /**
     * Indicates whether upload queuing is allowed or not.
     */
    public boolean allowUploadQueuing;

    /**
     * The maximal number of upload queue slots available.
     */
    public int maxUploadQueueSize;

    /**
     * The minimum poll time for queued uploads.
     */
    public int minUploadQueuePollTime;

    /**
     * The maximum poll time for queued uploads.
     */
    public int maxUploadQueuePollTime;

    /**
     * The maximum number of downloads that are allowed per IP.
     */
    public int maxDownloadsPerIP;

    /**
     * The total speed in kilo bits per second of the network connection the
     * user has available. This is not the bandwidth the user has available for
     * Phex.
     * The default of 256 matches a DSL/Cable connection.
     */
    public int networkSpeedKbps = 256;

    /**
     * This is the maximal bandwidth in bytes per second Phex is allowed to use
     * in total. This means network, download and upload bandwidth combined.
     * The default of 16384 matches 50% of the bandwidth a 256kbs DSL/Cable connection
     * is able to offer.
     */
    public int maxTotalBandwidth = 16384;

    /**
     * This is introduced to maintain the current version of Phex.
     * After a update of Phex the version in the cfg and the Phex version differs.
     * In this case we know that we need to upgrade the cfg or other stuff to the
     * new Phex version
     */
    public String runningPhexVersion = "";
    /**
     * This is introduced to maintain the current build number of Phex.
     * After a update of Phex the build number in the cfg and the Phex build number
     * differs. In this case we know that we need to upgrade the cfg and maybe
     * also do some other stuff to reach the new Phex version.
     */
    public String runningBuildNumber = "";

    /**
     * Defines if a http proxy is used for HTTP connections (not Gnutella
     * connections.
     */
    public boolean isHttpProxyUsed = false;

    /**
     * Defines the name of the http proxy host.
     */
    public String httpProxyHost = "";

    /**
     * Defines the port of the http proxy host.
     */
    public int httpProxyPort = DEFAULT_HTTP_PORT;

    /**
     * Contains the version number of the last update check.
     */
    public String lastUpdateCheckVersion = "0";

    /**
     * Contains the version number of the last beta update check.
     */
    public String lastBetaUpdateCheckVersion = "0";

    /**
     * Contains the time in millis of the last update check.
     */
    public long lastUpdateCheckTime = 0;

    /**
     * The status if a update notification dialog should be displayed or not.
     */
    public boolean showUpdateNotification = true;

    /**
     * The status if a beta update notification dialog should be displayed or not.
     */
    public boolean showBetaUpdateNotification = false;

    // mSocketTimeout defaults to 20 sec
    public int mSocketTimeout = 60 * 1000;
    // timeout for private ip addresses.
    // 2 sec should be enough for a LAN
    //public int privateSocketTimeout = 2000;

    /**
     * Timeout for network host connections.
     */
    public int mNetConnectionTimeout = 4 * 1000;

    /**
     * the time after which a automatic candidate search times out
     */
    public int searchRetryTimeout = 30000;

    /**
     * The verbose level of the logger
     * Default value is Logger.SEVERE.value (6)
     * Can't use direct value since Cfg has not finished initialization.
     */
    public short loggerVerboseLevel;

    /**
     * The compact logging type
     */
    public short logType = 0x01;

    /**
     * Defines if log output should also be written to the console.
     */
    public boolean logToConsole = false;

    /**
     * The max length of a log file.
     */
    public long maxLogFileLength = 512 * 1024;

    /**
     * Enables QueryHit Snooping.
     */
    public boolean enableHitSnooping;

    /**
     * The max length a message is allowed to have to be accepted.
     */
    public int maxMessageLength;

    /**
     * Indicates if the chat feature is enabled.
     */
    public boolean isChatEnabled;

    /**
     * Indicates if the node is allowed to connect to ultrapeers.
     * @deprecated replaced by allowToBecomeLeaf in 0.8.1
     */
    public boolean allowUPConnections;

    /**
     * Indicates that the node is allowed to connect as a leaf to ultrapeers.
     */
    public boolean allowToBecomeLeaf;

    /**
     * Indicates if the node is only accepting ultrapeer connections (as a leaf).
     * This value must always be checked together with allowToBecomeLeaf. If
     * allowToBecomeLeaf is false, a forceUPConnections value of true must be
     * ignored.
     */
    public boolean forceUPConnections;

    /**
     * Indicates if this node is allowed to become a Ultrapeer.
     */
    public boolean allowToBecomeUP;

    /**
     * Indicates if this node force to be a Ultrapeer.
     * This value must always be checked together with allowToBecomeUP. If
     * allowToBecomeUP is false, a forceToBeUltrapeer value of true must be
     * ignored.
     */
    public boolean forceToBeUltrapeer;

    /**
     * The number of ultrapeer to ultrapeer connections the nodes is allowed to
     * have open.
     * TODO2 this value is used for the X-Degree header but to reach high out degree
     * for dynamic query the value should be maintained above 15. There is no
     * way to ensure this yet.
     */
    public int up2upConnections;

    /**
     * The number of ultrapeer to leaf connections the nodes is allowed to
     * have open.
     */
    public int up2leafConnections;

    /**
     * The number of ultrapeer to normal peer connections the nodes is allowed to
     * have open.
     */
    public int up2peerConnections;

    /**
     * The number of leaf to ultrapeer connections the nodes is allowed to
     * have open. The max should be 3.
     */
    public int leaf2upConnections;

    /**
     * The number of leaf to normal peer connections this node is allowed to have
     * open.
     */
    public int leaf2peerConnections;

    /**
     * The number of normal peers the node is allowed to have as a normal peer.
     */
    public int peerConnections;

    /**
     * Indicates if nodes with no vendor code are disconnected.
     */
    public boolean isNoVendorNodeDisconnected;

    /**
     * The number of files a node need to share to not be called a freeloader.
     */
    public int freeloaderFiles;

    /**
     * The number of MB a node need to share to not be called a freeloader.
     */
    public int freeloaderShareSize;

    /**
     * The number of milliseconds a error is displayed in the connection table.
     */
    public int hostErrorDisplayTime;

    /**
     * The TTL Phex uses for messages.
     */
    public int ttl;

    /**
     * The maximim number of hops allowed to be seen in messages otherwise a
     * message is dropped. Also the highest ttl allowed to be seen in messages
     * otherwise the ttl is limited to Cfg.maxNetworkTTL - hops.
     */
    public int maxNetworkTTL;

    /**
     * History of connectTo items.
     */
    public ArrayList connectToHistory;

    /**
     * The max size of the connectToHistory list.
     */
    public int maxConnectToHistorySize;

    /**
     * History of search items.
     */
    public ArrayList searchTermHistory;

    /*
     * List of suffixes which indicate a file is streamable (ie: can be used before it's fully downloaded)
     */
    public ArrayList streamableSuffixes;
    public static final String default_streamableSuffixes = new String("avi|mpe?g|wmv|ogg|mp3|mov|txt|html?");

    // if true, a request to preview the initial segment will first result
    // in a copy of it being made, and then the copy's filename passed
    // to the viewer. This is needed (I think) on windows platforms, at least
    public boolean copyBeforePreviewing;
    public static final boolean DEFAULT_COPY_BEFORE_PREVIEWING = true;

    /*
     * List of suffixes which indicate a file is not streamable (ie: it's useless until it's fully downloaded)
     */
    public ArrayList unstreamableSuffixes;
    public static final String default_unstreamableSuffixes = new String("iso|zip|rar|bin|r\\\\d\\\\d|exe|bmp|msi|doc|torrent|pdf");

    public HashMap previewMethod;
    public String fallbackPreviewMethod;
    public static final String DEFAULT_FALLBACK_PREVIEW_METHOD = "";

    /**
     * The max size of the searchTerm list.
     */
    public int maxSearchTermHistorySize;

    /**
     * Indicates whether partial downloaded files are offered to others for download.
     */
    public boolean arePartialFilesShared;

    /**
     * The total uptime of the last movingTotalUptimeCount starts.
     */
    public long movingTotalUptime;

    /**
     * The number of times the uptime was added to movingTotalUptime.
     */
    public int movingTotalUptimeCount;

    /**
     * The maximal uptime ever seen.
     */
    public long maximalUptime;
    
    /** 
     * Last time Phex was shutdown. Needed for avg. daily uptime calculation.
     */
    public long lastShutdownTime;
    
    /**
     * The last fractional uptime calculated. Needed for avg. daily uptime
     * calculation.
     */
    public float fractionalUptime;
    
    /**
     * Indicates if we accept deflated connections.
     */
    public boolean isDeflateConnectionAccepted;
    
    /**
     * The id of the last shown update info from the Phex web server.
     */
    public int lastShownUpdateInfoId;
    
    /// some persistent statistic values...
    
    /**
     * The total number of completed downloads tracked.
     */
    public int totalDownloadCount;
    
    /**
     * The total number of uploads.
     */
    public int totalUploadCount;
    
    // localization
    /**
     * The locale files to use.
     */
    public String usedLocale;

    private File configFile;
    private Properties mSetting;

    public int orderingMethod;

    public Cfg( File cfgFile )
    {
        configFile = cfgFile;
        mSetting = new SortedProperties();
    }

    public void load()
    {
        loadDefaultValues();
        try
        {
            FileInputStream is = new FileInputStream( configFile );
            mSetting.load(is);
            is.close();
        }
        catch ( FileNotFoundException exp )
        {
            // no config file found. Is there a backup?
            try 
            {
                FileInputStream is2 = new FileInputStream( new File(configFile.getParentFile(), BACKUP_CONFIG_FILENAME) );
                mSetting.load(is2);
                is2.close();
                Logger.logMessage( Logger.SEVERE, Logger.GLOBAL,
                        "Loading configuration from backup file" );
            }
            catch ( FileNotFoundException exp2 )
            {
                // no backup file either
            }
            catch (Exception exp2)
            {
                Logger.logError( exp2 );
            }
        }
        catch ( Exception exp )
        {
            Logger.logError( exp );
        }

        deserializeSimpleFields();
        deserializeComplexFields();

        handlePhexVersionAdjustments();

        // ListeningPort doesn't exist.  Randomly generate one on the first time.
        if (mListeningPort == -1)
        {
            Random random = new Random(System.currentTimeMillis());
            mListeningPort = random.nextInt();
            mListeningPort = mListeningPort < 0 ? -mListeningPort : mListeningPort;
            mListeningPort %= 20000;		// resolve: expand to 25000
            mListeningPort += 4000;
        }
        updateHTTPProxySettings();

        
        // make sure directories exists...
        File dir = new File( mDownloadDir );
        dir.mkdirs();
        dir = new File( incompleteDir );
        dir.mkdirs();
    }


    public void save()
    {
        Logger.logMessage( Logger.FINEST, Logger.GLOBAL, "Saving configuration." );
        mSetting.clear();
        serializeSimpleFields();
        serializeComplexField();

        try
        {
            File target = configFile.getCanonicalFile();
            File configDir = configFile.getParentFile();
            File backup = new File (configDir, BACKUP_CONFIG_FILENAME );

            // first save the new values in a temp file
            File tempfile = File.createTempFile("config", "temp", configDir );
            FileOutputStream os = new FileOutputStream ( tempfile );
            mSetting.store(os, "PHEX Config Values");
            os.close();

            // move old config to backup, deleting old backup first if necessary
            backup.delete();
            if ( configFile.exists() )
            {
                if ( configFile.renameTo( backup ) != true )
                {
                    Logger.logMessage( Logger.SEVERE, Logger.GLOBAL, "Could not rename configuration file to backup name!" );
                }
            }
            // rename new config to proper name
            if ( tempfile.renameTo( target ) != true )
            {
                Logger.logMessage( Logger.SEVERE, Logger.GLOBAL, "Could not rename new configuration file to proper name!" );
                // so we'll copy back the backup
                backup.renameTo( target );
            }
            tempfile.delete();
        }
        catch (IOException exp )
        {
            Logger.logError( exp );
        }
    }

    private void loadDefaultValues()
    {
        maxDownloadsPerIP = DEFAULT_MAX_DOWNLOADS_PER_IP;
        enableHitSnooping = DEFAULT_ENABLE_HIT_SNOOPING;
        maxMessageLength = DEFAULT_MAX_MESSAGE_LENGTH;
        isChatEnabled = DEFAULT_IS_CHAT_ENABLED;
        allowToBecomeLeaf = DEFAULT_ALLOW_TO_BECOME_LEAF;
        forceUPConnections = DEFAULT_FORCE_UP_CONNECTIONS;
        forceToBeUltrapeer = DEFAULT_FORCE_TOBE_ULTRAPEER;
        allowToBecomeUP = DEFAULT_ALLOW_TO_BECOME_ULTRAPEER;
        isNoVendorNodeDisconnected = DEFAULT_IS_NOVENDOR_NODE_DISCONNECTED;
        freeloaderFiles = DEFAULT_FREELOADER_FILES;
        freeloaderShareSize = DEFAULT_FREELOADER_SHARE_SIZE;
        hostErrorDisplayTime = DEFAULT_HOST_ERROR_DISPLAY_TIME;
        ttl = DEFAULT_TTL;
        maxNetworkTTL = DEFAULT_MAX_NETWORK_TTL;
        up2upConnections = DEFAULT_UP_2_UP_CONNECTIONS;
        up2leafConnections = DEFAULT_UP_2_LEAF_CONNECTIONS;
        up2peerConnections = DEFAULT_UP_2_PEER_CONNECTIONS;
        leaf2upConnections = DEFAULT_LEAF_2_UP_CONNECTIONS;
        leaf2peerConnections = DEFAULT_LEAF_2_PEER_CONNECTIONS;
        peerConnections = DEFAULT_PEER_CONNECTIONS;
        arePartialFilesShared = DEFAULT_ARE_PARTIAL_FILES_SHARED;
        allowUploadQueuing = DEFAULT_ALLOW_UPLOAD_QUEUING;
        maxUploadQueueSize = DEFAULT_MAX_UPLOAD_QUEUE_SIZE;
        minUploadQueuePollTime = DEFAULT_MIN_UPLOAD_QUEUE_POLL_TIME;
        maxUploadQueuePollTime = DEFAULT_MAX_UPLOAD_QUEUE_POLL_TIME;
        isDeflateConnectionAccepted = DEFAULT_IS_DEFLATE_CONNECTION_ACCEPTED;
        lastShownUpdateInfoId = DEFAULT_LAST_SHOWN_UPDATE_INGO_ID;
        initialSegmentSize = INITIAL_SEGMENT_SIZE;
        segmentTransferTime = SEGMENT_TRANSFER_TIME;
        minimumAllowedTransferRate = MINIMUM_ALLOWED_TRANSFER_RATE;
        maximumSegmentSize = MAXIMUM_SEGMENT_SIZE;
        urnCalculationMode = DEFAULT_URN_CALCUATION_MODE;
        orderingMethod = DEFAULT_ORDERING_METHOD;
        fallbackPreviewMethod = DEFAULT_FALLBACK_PREVIEW_METHOD;
        // TODO: detect OS and set initial value based on this
        copyBeforePreviewing = DEFAULT_COPY_BEFORE_PREVIEWING;


        // loggerVerboseLevel default value is Logger.SEVERE.value (6)
        // Can't use direct value since Cfg has not finished initialization.
        loggerVerboseLevel = DEFAULT_LOGGER_VERBOSE_LEVEL;

        maxConnectToHistorySize = DEFAULT_MAX_CONNECTTO_HISTORY_SIZE;

        maxSearchTermHistorySize = DEFAULT_MAX_SEARCHTERM_HISTORY_SIZE;
        
    }

    private String get(String key)
    {
        String value = (String)mSetting.get(key);
        if (value != null)
            value = value.trim();
        return value;
    }

    private String get(String key, String defaultVal)
    {
        String	value = get(key);

        if (value == null)
            return defaultVal;
        return value;
    }

    private void set(String key, String value)
    {
        if ( value != null )
        {
            mSetting.put(key, value);
        }
    }

    private void set(String key, long value)
    {
        mSetting.put(key, String.valueOf(value));
    }
    
    private void set(String key, double value)
    {
        mSetting.put(key, String.valueOf(value));
    }

    private void set(String key, boolean value)
    {
        mSetting.put(key, value ? "true" : "false");
    }

    private void serializeSimpleFields()
    {
        Field[] fields = this.getClass().getDeclaredFields();

        for (int i = 0; i < fields.length; i++)
        {
            String		name = fields[i].getName();
            int			modifiers = fields[i].getModifiers();
            Class		type = fields[i].getType();

            if (!Modifier.isPublic(modifiers) ||
                Modifier.isTransient(modifiers) ||
                Modifier.isStatic(modifiers))
            {
                continue;
            }

            try
            {
                if (type.getName().equals("int"))
                {
                    set(name, fields[i].getInt(this));
                }
                else if (type.getName().equals("short"))
                {
                    set(name, fields[i].getShort(this));
                }
                else if (type.getName().equals("long"))
                {
                    set(name, fields[i].getLong(this));
                }
                else if (type.getName().equals("float"))
                {
                    set(name, fields[i].getFloat(this));
                }
                else if (type.getName().equals("boolean"))
                {
                    set(name, fields[i].getBoolean(this));
                }
                else if (type.getName().equals("java.lang.String"))
                {
                    set(name, (String)fields[i].get(this));
                }
                else if (type.getName().equals("java.util.ArrayList"))
                {
                    List myList = (List)fields[i].get(this);
                    for (int j = 0; j < myList.size(); j++)
                    {
                        String key = new String( fields[i].getName() + LIST_PREFIX + (j + 1));
                        set(key, myList.get(j).toString());
                    }

                }
                else if (type.getName().equals("java.util.HashMap"))
                {
                    Map myMap = (Map) fields[i].get(this);
                    if ( myMap.keySet().size() == 0) continue; // nothing in this map to save.
                    for ( Iterator it = myMap.keySet().iterator(); it.hasNext(); )
                    {
                        String key = (String) it.next();
                        String entry = new String ( fields[i].getName() + LIST_PREFIX + key);
                        set (entry, (String) myMap.get(key) );
                    }
                }

 /*
                else
                {
                    throw new Exception( "Don't know how to serialize a " + type.getName() + " field!"  );
                }
  */
            }
            catch (Exception exp )
            {
                Logger.logError( exp, "Error in field: " + name );
            }
        }
    }


    private void serializeComplexField()
    {
        try
        {
            set("mProgramClientID", mProgramClientID.toHexString());
        }
        catch (Exception e)
        {
            Logger.logError( e );
        }
    }


    private void deserializeSimpleFields()
    {
        Field[] fields = this.getClass().getDeclaredFields();

        for (int i = 0; i < fields.length; i++)
        {
            String name = fields[i].getName();
            int modifiers = fields[i].getModifiers();
            Class type = fields[i].getType();
            String value = "";

            if (!Modifier.isPublic(modifiers) ||
                Modifier.isTransient(modifiers) ||
                Modifier.isStatic(modifiers))
            {
                continue;
            }

            try
            {
                // if this is a map, don't continue after deserialising
                if (deserialiseMap(fields[i]))
                    continue;
            } catch (Exception ex)
            {
                // exception may be thrown if it's not a map. Nothing to worry about.
            }

            try
            {
                // if this is a list, don't continue after deserialising
                if (deserialiseList(fields[i]))
                    continue;
            } catch (Exception ex)
            {
                // exception may be thrown if it's not a list. Nothing to worry about.
            }

            try
            {
                // Load value by field name.
                value = get(name);
                if (value == null)
                {
                    // Use default.
                    continue;
                }

                if (type.getName().equals("int"))
                {
                    fields[i].setInt(this, Integer.parseInt(value));
                }
                else if (type.getName().equals("short"))
                {
                    fields[i].setShort(this, Short.parseShort(value));
                }
                else if (type.getName().equals("long"))
                {
                    fields[i].setLong(this, Long.parseLong(value));
                }
                else if (type.getName().equals("float"))
                {
                    fields[i].setFloat(this, Float.parseFloat(value));
                }
                else if (type.getName().equals("boolean"))
                {
                    fields[i].setBoolean(this, value.equals("true"));
                }
                else if (type.getName().equals("java.lang.String"))
                {
                    fields[i].set(this, value);
                }
            }
            catch ( Exception exp )
            {
                Logger.logError( exp, "Error in field: " + name + ", value: " + value );
            }
        }
    }

    private boolean deserialiseMap(Field myField)
    {
        boolean isMap = false;
        try
        {
            if (myField.getType().getName().equals("java.util.HashMap"))
            {
                myField.set(this, new HashMap()); // create an empty list
                Map myMap = (Map) myField.get(this);

                String key;
                String value;
                String prefix = new String ( myField.getName() + LIST_PREFIX );
                for ( Enumeration e = mSetting.propertyNames(); e.hasMoreElements() ; )
                {
                    String foundName = (String) e.nextElement();
                    if ( foundName.startsWith(prefix) )
                    {
                        key = foundName.substring(prefix.length());
                        if ( key.length() > 0 && mSetting.getProperty(foundName).length() > 0 )
                        {
                            myMap.put(key, mSetting.getProperty(foundName));
                        }
                            
                    }
                }
                isMap = true; // successfully processed the list
            }
        }
        catch (IllegalAccessException ex)
        {
            ex.printStackTrace();
            isMap = false;
        }
        return isMap;
    }

    private boolean deserialiseList(Field myField)
    {
        boolean isList = false;
        try
        {
            if (myField.getType().getName().equals("java.util.ArrayList"))
            {
                myField.set(this, new ArrayList()); // create an empty list
                List myList = (List) myField.get(this);

                String value;
                int index;
                for (index = 1;;index++)
                {
                    value = get(myField.getName() + LIST_PREFIX + index);
                    if ( value != null && value.length() > 0 )
                        myList.add(value);
                    else
                        break;
                }
                if ( index == 1 ) // no values read in, so check for default value
                {
                    try
                    {
                        Object defaultValue = this.getClass().getDeclaredField("default_" + myField.getName()).get(this);
                        String buffer = (String)defaultValue;
                        StringTokenizer tokens = new StringTokenizer(buffer, "|");
                        // create the empty list object
                        while (tokens.hasMoreTokens())
                        {
                            myList.add(tokens.nextToken());
                        }
                    }
                    catch (Exception ex)
                    { // no defaults found
                    }
                }
                isList = true; // successfully processed the list
            }
        }
        catch (IllegalAccessException ex)
        {
            ex.printStackTrace();
            isList = false;
        }
        return isList;
    }

    private void deserializeComplexFields()
    {
        try
        {
            try
            {
                mProgramClientID.fromHexString(get("mProgramClientID"));
            }
            catch (Exception e)
            {
                // ignore.  take the default created value as new client ID.
            }
        }
        catch (Exception exp )
        {
            Logger.logError( exp );
        }
    }

    /**
     * For a HTTPURLConnection java uses configured proxy settings.
     */
    public void updateHTTPProxySettings()
    {
        System.setProperty( "http.agent", Environment.getPhexVendor() );
        if ( isHttpProxyUsed )
        {
            System.setProperty( "http.proxyHost", httpProxyHost );
            System.setProperty( "http.proxyPort", String.valueOf( httpProxyPort ) );
        }
        else
        {
            System.setProperty( "http.proxyHost", "" );
            System.setProperty( "http.proxyPort", "" );
        }
    }

    private void handlePhexVersionAdjustments()
    {
        // first find out if this is the first time Phex is running...
        if ( ( runningPhexVersion == null || runningPhexVersion.length() == 0 ) &&
             ( runningBuildNumber == null || runningBuildNumber.length() == 0 ) )
        {
            // this seems to be the first time phex is running...
            // in this case we are not updating... we use default values...
            
            // unfortunatly there is a bug causing no set version number to be available
            // between 56 and 78
            // since b78 updates can be performed on any version without problems we execute
            // them always if no version is set.
            updatesForBuild78();
        }
        else
        {
            // update from Phex <= 0.5.6 to Phex 0.6
            if ( runningPhexVersion == null || runningPhexVersion.length() == 0 )
            {
                // we are at 0.6 level now
                runningPhexVersion = "0.6";
            }
            // we have a phex version now check version updates
            if ( VersionUtils.compare( "0.7", runningPhexVersion ) > 0 )
            {
                updatesFor0_7();
            }
            if ( VersionUtils.compare( "0.8", runningPhexVersion ) > 0 )
            {
                updatesFor0_8();
            }
    
            // update from Phex build <= 35
            if ( runningBuildNumber == null || runningBuildNumber.length() == 0 )
            {
                runningBuildNumber = "35";
            }
            if ( VersionUtils.compare( "36", runningBuildNumber ) > 0 )
            {
                updatesForBuild36();
            }
            if ( VersionUtils.compare( "42", runningBuildNumber ) > 0 )
            {
                updatesForBuild42();
            }
            if ( VersionUtils.compare( "55", runningBuildNumber ) > 0 )
            {
                updatesForBuild56();
            }
            if ( VersionUtils.compare( "77", runningBuildNumber ) > 0 )
            {
                updatesForBuild78();
            }
        }
        
        runningBuildNumber = Environment.getInstance().getProperty(
            "build.number" );
        runningPhexVersion = Res.getStr( "Program.Version" );
        save();
    }

    private void updatesFor0_7()
    {
        // copy downloadlist.xml before changing it to the new format.
        try
        {
            File downloadListFile = Environment.getInstance().getPhexConfigFile(
                EnvironmentConstants.XML_DOWNLOAD_FILE_NAME );

            if ( downloadListFile.exists() )
            {
                FileUtils.copyFile( downloadListFile,
                    new File( downloadListFile.getAbsolutePath() + ".v0.6.4" ) );
            }
        }
        catch ( IOException exp )
        {
            Logger.logError( exp );
        }
        runningPhexVersion = "0.7";
    }

    private void updatesFor0_8()
    {
        peerConnections = mNetMinConn;
        runningPhexVersion = "0.8";
        // reduce this.. for better performance..
        mUploadMaxSearch = 64;
        mNetMaxHostToCatch = 1000;
    }

    private void updatesForBuild36()
    {
        runningBuildNumber = "36";
        allowToBecomeLeaf = allowUPConnections;
    }
    
    private void updatesForBuild42()
    {
        runningBuildNumber = "42";
        if ( up2upConnections < DEFAULT_UP_2_UP_CONNECTIONS )
        {
            up2upConnections = DEFAULT_UP_2_UP_CONNECTIONS;
        }
    }
    
    private void updatesForBuild56()
    {
        runningBuildNumber = "56";
        lastUpdateCheckTime = 0;
    }

    /*
     * Read in old ArrayLists with tokeniser
     */
    private void updatesForBuild78()
    {
        runningBuildNumber = "78";
        u78("mNetNetworkHistory", " ");
        u78("filteredCatcherPorts", " ");
        u78("connectToHistory", " ");
        u78("searchTermHistory", ",");
    }

    private void u78(String variable, String delim)
    {
        String buffer = get(variable);
        try
        {
            if (buffer != null)
            {
                List myList = (List) this.getClass().getDeclaredField(variable).get(this);
                StringTokenizer tokens = new StringTokenizer(buffer, delim);
                while (tokens.hasMoreTokens()) 
                {
                    myList.add(tokens.nextToken());
                }
            }
        }
        catch (NoSuchFieldException ex)
        {
            throw new Error("No such field: " + variable + "!");
        }
        catch (IllegalAccessException ex)
        {
            throw new Error("Cannot convert " + variable + " to a list!!");
        }
    }
}
