/* 
 Copyright 2005-2010 Xmarks Inc.
 
 settings.jsm: implements FoxmarksSettings, an object that wraps
 access to persistent settings, both user settings and internal stored values.
   
 */


var EXPORTED_SYMBOLS = ["gSettings", "LogWrite", "LogTimer", "Bundle",
    "UpdateToXMarks", "Notify", "SetProgressComponentStatus", 
    "SetProgressMessage", "Alert", "MapError", "MapErrorUrl", "MapErrorMessage",
    "hex_md5", "FoxmarksVersion", "FOXMARKS_SYNC_REALM",
    "FOXMARKS_SYNC_REALM_PIN", "SYNC_REALM", "SYNC_REALM_PIN"];

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
var CCon = Components.Constructor;
var Xmarks;

if (Xmarks === undefined) {
    Xmarks = {};
}


var FOXMARKS_SYNC_REALM = "Foxmarks Sync Login";
var FOXMARKS_SYNC_REALM_PIN = "Foxmarks Sync PIN";
var SYNC_REALM = "Xmarks Sync Login";
var SYNC_REALM_PIN = "Xmarks Sync PIN";

var LogWrite = function(str) {
    if (gSettings.enableLogging) {
        logWrite(str);
    }
}


var LogTimer = function(str, startTime){
    if(gSettings.getDebugOption("timing")){
        var now = Date.now();
        startTime = startTime || 0;
        if(startTime){
            LogWrite(str +
                " - time=" + (now - startTime ) + "ms");
        } else {
            LogWrite(str + " -  time=0");
        }
        return now;
    }

    return 0;
}

var Bundle = function() {
  var sb = Components.classes["@mozilla.org/intl/stringbundle;1"]
      .getService(Components.interfaces.nsIStringBundleService)
      .createBundle("chrome://foxmarks/locale/foxmarks.properties");
  return sb;
};

var UpdateToXMarks = function(){
    var ps = Cc["@mozilla.org/preferences-service;1"].
            getService(Ci.nsIPrefService);
    var foxmarks = ps.getBranch("foxmarks.");
    var doupdate = false;

    try {
        doupdate = foxmarks.getIntPref("majorVersion") == 2;
    } catch(e){
        // do nothing
    }

    if(doupdate){
        var xmarks = ps.getBranch("extensions.xmarks.");
        var obj = { value: 0 };
        var list = xmarks.getChildList("",obj);
        obj = { value: 0 };
        list = foxmarks.getChildList("",obj);

        var len = list.length;
        while(len--){
            var name = list[len];
            var valtype = foxmarks.getPrefType(name);
            switch(valtype){
                case xmarks.PREF_STRING:
                    xmarks.setCharPref(name, foxmarks.getCharPref(name));
                    break;
                case xmarks.PREF_INT:
                    xmarks.setIntPref(name, foxmarks.getIntPref(name));
                    break;
                case xmarks.PREF_BOOL:
                    xmarks.setBoolPref(name, foxmarks.getBoolPref(name));
                    break;
            }
        }
        foxmarks.deleteBranch("");
        ps.savePrefFile(null);     // ensure it gets flushed
        gSettings.correctHashes();
    }
};
// Notify takes the given args object and passes it to observers.
// By convention, args contains at least "status", an integer with
// the following interpretation:
//   0: operation completed successfully.
//   1: operation continues; msg is status update only.
//   2: operation was cancelled by user.
//   3: component finished
//   other: operation failed.
// Similarly, "msg" contains a user-displayable message.

var Notify = function(args) {
    var os = Cc["@mozilla.org/observer-service;1"]
        .getService(Ci.nsIObserverService);

    var str = args.toSource();        
    os.notifyObservers(null, "foxmarks-service", str);
};

var SetProgressComponentStatus = function(id, phase){
    Notify({status: 1, component: id, phase: phase} );
};
var SetProgressMessage = function(msgname) {
    var msg;
    try {
        msg = Bundle().GetStringFromName(msgname);
    } catch(e) {
        msg = "untranslated(" + msgname + ")";
    }

    Notify({status: 1, msg: msg} );
};

var Alert = function(str){
    var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
        .getService(Components.interfaces.nsIPromptService);
    ps.alert(null,"Xmarks", str);
};

var MapErrorUrl = function(status) {
    var error = "";

    status = status & 0x0000ffff;

    try {
        error = Bundle().GetStringFromName("errorurl." + status);
    } catch (e) {
        if(UnknownError(status)){
            error = Bundle().GetStringFromName("errorurl.unknown");
        } else {
            error = "";
        }
    }

    return error;
};

var UnknownError = function(status){
    var result = false;
    status = status & 0x0000ffff;

    try {
        Bundle().GetStringFromName("error." + status);
        result = false;
    } catch (e) {
        result = true;
    }

    return result;
};
var MapErrorMessage = function(status) {
    var error = "";

    status = status & 0x0000ffff;

    try {
        error = Bundle().GetStringFromName("errormsg." + status);
    } catch (e) {
        if(UnknownError(status)){
            error = Bundle().formatStringFromName(
                "errormsg.unknown", [status], 1);
        } else {
            error = "";
        }
    }

    return error;
}; 
var MapError = function(status) {
    var error = "";

    status = status & 0x0000ffff;

    try {
        error = Bundle().GetStringFromName("error." + status);
    } catch (e) {
        error = Bundle().formatStringFromName("error.unknown", [status], 1);
    }

    return error;
}; 

/**
* Convert a string containing binary values to hex.
* Shamelessly stolen from nsUpdateService.js
*/
function binaryToHex(input) {
    var result = "";
    for (var i = 0; i < input.length; ++i) {
        var hex = input.charCodeAt(i).toString(16);
        if (hex.length == 1)
            hex = "0" + hex;
        result += hex;
    }
    return result;
}

var hex_md5 = function(string) {
    var arr = new Array();
    
    for (var i = 0; i < string.length; ++i)
        arr[i] = string.charCodeAt(i);
        
    var hash = Components.classes["@mozilla.org/security/hash;1"].
                createInstance(Components.interfaces.nsICryptoHash);
    hash.initWithString("md5");
    hash.update(arr, arr.length);
    return  binaryToHex(hash.finish(false));
};

// Return the version string associated with the currently installed version
var ExtensionManagerVersion = function() {
    var rdfs = Components.classes["@mozilla.org/rdf/rdf-service;1"].
        getService(Components.interfaces.nsIRDFService);
    var ds = Components.classes["@mozilla.org/extensions/manager;1"].
        getService(Components.interfaces.nsIExtensionManager).datasource;
    var s = rdfs.GetResource("urn:mozilla:item:foxmarks@kei.com");
    var p = rdfs.GetResource("http://www.mozilla.org/2004/em-rdf#version");
    var t = ds.GetTarget(s, p, true);
    if (t instanceof Components.interfaces.nsIRDFLiteral)
        return t.Value;
    else
        return "unknown";
};

var version;
try {
    // Fx3
    version = ExtensionManagerVersion();
} catch(e) {
    // Fx4
    Cu.import("resource://gre/modules/AddonManager.jsm");
    AddonManager.getAddonByID("foxmarks@kei.com", function(a) {
            version = a.version;
        });
}

var FoxmarksVersion = function() {
    return version;
};


function XmarksSettings() {
    // upgrade to xmarks if possible
    var ps = Cc["@mozilla.org/preferences-service;1"].
            getService(Ci.nsIPrefService);
        
    this.prefs = ps.getBranch("extensions.xmarks.");
    this.ps = ps;
}

XmarksSettings.prototype = {
    prefs: null,

    // Only call this for uninstalls (it nukes all prefs)
    clearAllPrefs: function(){
        this.pin = "";
        this.password = "";
        this.prefs.deleteBranch("");
    },
    
    getCharPref: function(string, def) {
        var result;
        
        try {
            result = this.prefs.getCharPref(string);
        } catch (e) {
            result = def;
        }
        
        return result;
    },

    getIntPref: function(string, def) {
        var result;
        
        try {
            result = this.prefs.getIntPref(string);
        } catch (e) {
            result = def;
        }
        
        return result;
    },


    getBoolPref: function(string, def) {
        var result;
        
        try {
            result = this.prefs.getBoolPref(string);
        } catch (e) {
            result = def;
        }
        
        return result;
    },


    formatDate: function(d) {
        return d.toLocaleDateString() + " " + d.toLocaleTimeString();
    },

    // get fundamental settings

    get username() {
        return this.getCharPref("username", "");
    },

    get httpProtocol() {
        return this.securityLevel == 1 ? "https://" : "http://";
    },

    get auth() {
        return this.rememberPassword ? 
            this.getCharPref(this.hash + "authToken", "") : 
            this._authToken || "";
    },

    set auth(authToken) {
        this._authToken = authToken;
        this.prefs.setCharPref(this.hash + "authToken", 
            (this.rememberPassword ? authToken : ""));
    },

    findLogin: function(host, realm, username) {
        var logins = [];
        try {
            var lm = Cc["@mozilla.org/login-manager;1"].
                getService(Ci.nsILoginManager);
            logins = lm.findLogins({}, host, null, realm);
        } catch (e) {
            LogWrite("Failed retrieving passwords from login manager." +
                    "Files corrupted? Reported error is " + e);
        }
        for (var i = 0; i < logins.length; ++i) {
            if (logins[i].username == username)
                return logins[i].password;
        }
        return null;
    },

    removePIN: function(){
        var lm = Cc["@mozilla.org/login-manager;1"].
            getService(Ci.nsILoginManager);
        var nsli = new CCon("@mozilla.org/login-manager/loginInfo;1",
            Ci.nsILoginInfo, "init");
        var oldli = new nsli(this.host, null, SYNC_REALM_PIN, 
            this.username, this.pinNoPrompt, "", "");

        lm.removeLogin(oldli);
        this.sessionPin = "";
    },

    set pin(pin) {
        if (!this.rememberPin) {
            this.sessionPin = pin;
        } else {
            if ("@mozilla.org/login-manager;1" in Cc) {
                // Can't set password without username.
                if (!this.username)
                    return;
                var lm = Cc["@mozilla.org/login-manager;1"].
                    getService(Ci.nsILoginManager);
                var nsli = new CCon("@mozilla.org/login-manager/loginInfo;1",
                    Ci.nsILoginInfo, "init");
                var newli = new nsli(this.host, null, SYNC_REALM_PIN, 
                    this.username, pin, "", "");
                var oldli = new nsli(this.host, null, SYNC_REALM_PIN, 
                    this.username, this.pinNoPrompt, "", "");
                try {
                    lm.modifyLogin(oldli, newli);
                } catch (e) {
                    lm.addLogin(newli);
                }
            }
        }
    },

    get pin() {

        var pw = this.pinNoPrompt;

        if (pw != null)
            return pw;

        var pin = { value: "" };
        var remember = { value: this.rememberPin };

        var sb = Bundle().GetStringFromName;
        var rv = Cc["@mozilla.org/embedcomp/prompt-service;1"].
             getService(Ci.nsIPromptService).
             promptPassword(null, sb("appname.long"), 
                 sb("prompt.pin"),
                  pin,
                 sb("prompt.rememberpin"),
                 remember);

        if (!rv) {
            throw 2;
        }

        this.pin = pin.value;
        this.rememberPin = remember.value;

        return pin.value;
    },

    get pinNoPrompt() {
        if (!this.rememberPin && this.sessionPin) {
            return this.sessionPin;
        }

        if (this.rememberPin) {
            var pin = this.findLogin(this.host, SYNC_REALM_PIN, 
                this.username);
            if (pin) return pin;

            var oldhost = this.host == "sync.xmarks.com"
                ? "sync.foxmarks.com" : this.host;
            pin = this.findLogin(oldhost, FOXMARKS_SYNC_REALM_PIN, 
                this.username);
            if (pin) return pin;
        }
        return null;    // Couldn't fetch pin.
    },

    // get/set password methods now used only for ownserver and
    // password migration (see uitools.js).
    get password() {
        var pw = this.passwordNoPrompt;

        if (pw != null)
            return pw;

        var username = { value: this.username };
        var password = { value: "" };
        var remember = { value: this.rememberPassword };

        var sb = Bundle().GetStringFromName;
        var rv = Cc["@mozilla.org/embedcomp/prompt-service;1"].
             getService(Ci.nsIPromptService).
             promptUsernameAndPassword(null, sb("appname.long"), 
                 sb("prompt.usernamepassword"),
                 username, password,
                 sb("prompt.rememberpassword"),
                 remember);

        if (!rv) {
            throw 2;
        }

        this.rememberPassword = remember.value;
        this.username = username.value;
        this.password = password.value;

        return password.value;
    },

    get passwordNoPrompt() {
        if (!this.rememberPassword && this.sessionPassword) {
            return this.sessionPassword;
        }

        if (!this.rememberPassword) {
            return null;
        }

        var pw = this.findLogin(this.host, SYNC_REALM, this.username);
        if (pw) return pw;

        // Next try foxmarks realm.
        var oldhost = this.host == "sync.xmarks.com" ? 
            "sync.foxmarks.com" : this.host;
        pw = this.findLogin(oldhost, FOXMARKS_SYNC_REALM, this.username);
        if (pw) return pw;

        return null;    // couldn't fetch password
    },

    set password(password) {
        if (this.passwordNoPrompt != password) {
            this.Logout();
        }
        if (!this.rememberPassword) {
            this.sessionPassword = password;
        } else {
            if(!password)
                password = "";
            if ("@mozilla.org/login-manager;1" in Cc) {
                // Can't set password without username.
                if (!this.username)
                    return;
                var lm = Cc["@mozilla.org/login-manager;1"].
                    getService(Ci.nsILoginManager);
                var nsli = new CCon("@mozilla.org/login-manager/loginInfo;1",
                    Ci.nsILoginInfo, "init");
                var newli = new nsli(this.host, null, SYNC_REALM, 
                    this.username, password, "", "");
                var oldli = new nsli(this.host, null, SYNC_REALM, 
                    this.username, this.passwordNoPrompt, "", "");
                try {
                    if(password.length == 0){
                        lm.removeLogin(oldli);
                    }
                    else {
                        lm.modifyLogin(oldli, newli);
                    }
                } catch (e) {
                    if(password.length > 0)
                        lm.addLogin(newli);
                }
            }
        }
    },

    _calcHash: function(host){
        Cu.import("resource://xmarks/service.jsm", Xmarks);
        return hex_md5((this.useOwnServer ? 
                this.url : host + this.username) + 
            Xmarks.fms.getStorageEngine("bookmarks")).slice(16)
            + ".";
    },
    get hash() {
        return this._calcHash(this.host);
    },
            
    get lastSynchDate() {
        return this.getCharPref(this.hash + "lastSynchDate", "");
    },

    get haveSynced() {
        return this.lastSynchDate != "";
    },

    getLastSyncDate: function(syncType){
        if(syncType == "bookmarks")
            return this.getCharPref(this.hash + "lastSynchDate", "");
        else
            return this.getCharPref(this.hash + syncType + "-lastSynchDate",
                    "");
    },
    
    get historyTimestamp() {
        return this.getIntPref(this.hash + "historyTimestamp", 0);
    },

    set historyTimestamp(val) {
        this.prefs.setIntPref(this.hash + "historyTimestamp", val);
    },

    get historyUrlLimit() {
        return this.getIntPref("historyUrlLimit", 200);
    },

    set historyUrlLimit(val) {
        this.prefs.setIntPref("historyUrlLimit", val);
    },

    getHaveSynced: function(syncType){
        return this.getLastSyncDate(syncType) != "";

    },

    setLastSyncDate: function(syncType, string){
        if(syncType == "bookmarks")
            return this.prefs.setCharPref(this.hash + "lastSynchDate", string);
        else
            return this.prefs.setCharPref(this.hash + syncType + "-lastSynchDate",
                    string);
    },

    get minutesSinceLastSync() {
        if (!this.haveSynced)
          return 0;
      
        var syncMS = new Date(this.lastSynchDate).getTime();
        var nowMS = Date.now();
        return (nowMS - syncMS) / 60000;
  
    },
  
    get daysSinceLastUpdateNag() {
        if (!this.lastNagDate)
            return Infinity;
        var updateMS = new Date(this.lastNagDate).getTime();
        var nowMS = Date.now();
        return (nowMS - updateMS) / (60000 * 60 * 24);
    },
  
    get lastNagDate() {
        return this.getCharPref("lastNagDate", null);
    },
    
    set lastNagDate(string) {
        this.prefs.setCharPref("lastNagDate", string);
    },
    
    get lastSynchDisplayDate() {
        if (!this.haveSynced) {
            return Bundle().GetStringFromName("msg.neversynced");
        } else {
            return this.formatDate(new Date(this.lastSynchDate));
        }
    },

    setEtag: function(syncType, string){
        if(syncType == "bookmarks"){
            this.prefs.setCharPref(this.hash + "etag", string);
        }
        else {
            this.prefs.setCharPref(this.hash + 
                    "-" + syncType + "-etag", string);
        }
    },
    getEtag: function(syncType){
        if(syncType == "bookmarks"){
            return this.getCharPref(this.hash + "etag", "");
        }
        else {
            return this.getCharPref(this.hash + "-" + syncType + "-etag", "");
        }
    },
    
    
    setToken: function(syncType, string){
        if(syncType == "bookmarks"){
            return this.prefs.setCharPref(this.hash + "token", string);
        }
        else {
            return this.prefs.setCharPref(this.hash + "-" + syncType + "-token", string);
        }

    },
    getToken: function(syncType){
        if(syncType == "bookmarks"){
            return this.getCharPref(this.hash + "token", "");
        }
        else {
            return this.getCharPref(this.hash + "-" + syncType + "-token", "");
        }

    },
    
    get writeCount() {
        return this.getIntPref("writeCount", 0);
    },

    get lastError() {
        return this.getIntPref("lastError", 0);
    },
    set lastError(err){
        this.prefs.setIntPref("lastError", err);
    },
    get synchOnTimer() {
        return this.getBoolPref("synchOnTimer", true);
    },
    get tabSyncOnIdle() {
        return this.getBoolPref("tabSyncOnIdle", true);
    },
    get tabSyncOnSleep() {
        return this.getBoolPref("tabSyncOnSleep", false);
    },
    get historySyncOnIdle() {
        return this.getBoolPref("historySyncOnIdle", true);
    },
    get historySyncOnSleep() {
        return this.getBoolPref("historySyncOnSleep", false);
    },
    get useBaselineCache() {
        return this.getBoolPref("memory-useBaselineCache", true);
    },
    get forceGC() {
        return this.getBoolPref("memory-forceGC", false);
    },

    isSyncEnabled: function(syncType){
        if (this.useOwnServer && 
            (syncType == 'tabs' || syncType == 'history')) {
            return false;
        }
        return this.getBoolPref("sync-"+syncType, syncType == "bookmarks");
    },

    setSyncEnabled: function(syncType, val){
        this.prefs.setBoolPref("sync-"+syncType, val);
        // If password sync is being disabled, clear our sync status
        // so that if we're reenabled we start from scratch. This prevents
        // problems that might occur if the user changed their PIN during
        // the hiatus.
        if (syncType == "passwords" && val == false) {
            this.setLastSyncDate("passwords", "");
        }
    },
    
    mustMerge: function(syncType){
        return this.getBoolPref("mergereq-"+syncType, false);
    },
    setMustMerge: function(syncType, val){
        this.prefs.setBoolPref("mergereq-"+syncType, val);
    },
    mustUpload: function(syncType){
        return this.getBoolPref("uploadreq-"+syncType, false);
    },
    setMustUpload: function(syncType, val){
        this.prefs.setBoolPref("uploadreq-"+syncType, val);
    },
    get autoSynchFreq() {
        return this.getIntPref("autoSynchFreq", 60);
    },
    
    get syncOnShutdown() {
        return this.getIntPref("syncOnShutdown", true) != 0;
    },

    get syncOnShutdownAsk() {
        return this.getBoolPref("syncOnShutdownAsk", true);
    },

    get syncOnShutdownAskTabs() {
        return this.getBoolPref("syncOnShutdownAskTabs", false);
    },
    
    get syncOnShutdownAskHistory() {
        return this.getBoolPref("syncOnShutdownAskHistory", false);
    },

    get debugUI() {
        return this.getBoolPref("debugUI", false);
    },
    
    get wizardPrefs() {
        return this.getCharPref("wizardPrefURL", "/FX/wiz.json");
    },
    set wizardPrefs(val) {
        this.prefs.setCharPref("wizardPrefURL", val);
    },
    get serpPrefs() {
        return this.getCharPref("serpPrefs", "");
    },
    set serpPrefs(val) {
        this.prefs.setCharPref("serpPrefs", val);
    },
    get serpPrefix() {
        return this.getCharPref("serpPrefix", "xmarks.serp");
    },
    set serpPrefix(val) {
        this.prefs.setCharPref("serpPrefix", val);
    },
    get iframePrefix() {
        return this.getCharPref("iframePrefix", "xmarksas");
    },
    set iframePrefix(val) {
        this.prefs.setCharPref("iframePrefix", val);
    },
    get iframeregex() {
        return this.getCharPref("iframeregex", "");
    },
    set iframeregex(val) {
        this.prefs.setCharPref("iframeregex", val);
    },
    get abgroup() {
        return this.getCharPref("abgroup", "*:C");
    },
    set abgroup(val) {
        this.prefs.setCharPref("abgroup", val);
    },
    get wizardWarning() {
        return this.getBoolPref("wizardDoWarning", true);
    },
    set wizardWarning(v){
        this.prefs.setBoolPref("wizardDoWarning", v);
    },
    get wizardRetriesLeft(){
        return this.getIntPref("wizardRetriesLeft", 5);
    },

    set wizardRetriesLeft(v){
        this.prefs.setIntPref("wizardRetriesLeft", v);
    },

    get lastWizardBubble(){
        return this.getCharPref("wizardLastBubble", "");
    },

    set lastWizardBubble(v){
        this.prefs.setCharPref("wizardLastBubble", v);
    },
    
    get wizardMinTimeExpired(){
        var nowMS = Date.now();
        var lastMS = this.lastWizardBubble == "" ? 0 : new Date(this.lastWizardBubble).getTime();
        return (nowMS - lastMS) >= (24 * 3600 * 1000);
    },

    wizardResetMinTime: function(){
        this.lastWizardBubble = this.NowAsGMT;
    },

    get wizardSuppress() {
        return this.getBoolPref("wizardNoShow", false);
    },
  
    set wizardSuppress(bool) {
       this.prefs.setBoolPref("wizardNoShow", bool);
    },

    get disableIfMatchOnPut() {
        return this.getBoolPref("disableIfMatchOnPut", false);
    },

    set enableLogging(bool) {
        this.prefs.setBoolPref("enableLogging", bool);
    },
    get enableLogging() {
        return this.getBoolPref("enableLogging", true);
    },

    get rememberPassword() {
        return this.getBoolPref("rememberPassword", true);
    },

    set rememberPassword(bool) {
        this.prefs.setBoolPref("rememberPassword", bool);
    },


    get rememberPin() {
        return this.getBoolPref("rememberPin", true);
    },

    set rememberPin(bool) {
        this.prefs.setBoolPref("rememberPin", bool);
    },

    set username(string) {
        string = string.replace(/^\s+|\s+$/g, '')
        if (string != this.username) {
            this.prefs.setCharPref("username", string);
        }
    },

    set user(obj) {
        this._user = obj;
        this.prefs.setCharPref("user", obj.toSource());
        var os = Cc["@mozilla.org/observer-service;1"]
            .getService(Ci.nsIObserverService);
        this.suspended = false;

        os.notifyObservers(null, "xmarks-loginchange", true);
    },

    get user() {
        if (!this._user) {
            try {
                this._user = eval("(" + this.getCharPref("user") + ")");
            } catch(e) {}
        }
        return this._user;
    },
    
    /**
     * Returns true if the user has a master password set and false otherwise.
     */

    // Adapted from 
    // mxr.mozilla.org/mozilla/source/browser/components/preferences/security.js

    get masterPasswordSet() {
        var secmodDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].
            getService(Ci.nsIPKCS11ModuleDB);
        var slot = secmodDB.findSlotByName("");
        if (slot) {
            var status = slot.status;
            var hasMP = status != Ci.nsIPKCS11Slot.SLOT_UNINITIALIZED &&
                status != Ci.nsIPKCS11Slot.SLOT_READY;
            return hasMP;
        } else {
            // XXX I have no bloody idea what this means
            return false;
        }
    },

    Logout: function(suspend) {
        var cm = Cc["@mozilla.org/cookiemanager;1"].
            getService(Ci.nsICookieManager);
        cm.remove(".staging.xmarks.com", "SYNCD_AUTH", "/", false);
        cm.remove(".xmarks.com", "SYNCD_AUTH", "/", false);
        this.auth = "";
        if (suspend) {
            this.suspended = true;
        }
        var os = Cc["@mozilla.org/observer-service;1"]
            .getService(Ci.nsIObserverService);

        os.notifyObservers(null, "xmarks-loginchange", false);
    },

    set lastSynchDate(string) {
        this.prefs.setCharPref(this.hash + "lastSynchDate", string);
    },

    set writeCount(integer) {
        this.prefs.setIntPref("writeCount", integer);
    },
        
    set autoSynchFreq(integer) {
        this.prefs.setIntPref("autoSynchFreq", integer);
    },
    
    set synchOnTimer(bool) {
        this.prefs.setBoolPref("synchOnTimer", bool);
    },
    
    set syncOnShutdown(integer) {
        this.prefs.setIntPref("syncOnShutdown", integer);
    },
    
    set syncOnShutdownAsk(bool) {
        this.prefs.setBoolPref("syncOnShutdownAsk", bool);
    },

    set debugUI(bool) {
        this.prefs.setBoolPref("debugUI", bool);
    },

    set serpEnabled(v){
        this.prefs.setBoolPref("enableSERP", v);
    },

    get serpEnabled(){
        return this.getBoolPref("enableSERP", true);
    },

    set simsiteEnabled(v){
        this.prefs.setBoolPref("enableSimSite", v);
    },

    get simsiteEnabled(){
        return this.getBoolPref("enableSimSite", true);
    },

    get serpRegex(){
        return this.getCharPref("SERPRegEx", "(http:\/\/www\.google\..+\/.*[?&]q=([^&]+))|(http:\/\/[-a-zA-Z]+\.start3\.mozilla\.com\/search\?.*[?&]q=([^&]+))|(http:\/\/search\.yahoo\.com\/search?.*[?&]p=([^&]+))|(http:\/\/search\.(msn|live)\.com\/results\.aspx?.*[?&]q=([^&]+))");
    },
    set serpRegex(v){
        this.prefs.setCharPref("SERPRegEx", v);
    },
    get serpMaxItems(){
        return this.getIntPref("SERPMaxItems", 3);
    },

    set serpMaxItems(val) {
        this.prefs.setIntPref("SERPMaxItems", val);
    },
    set tagSuggestionsEnabled(v){
        this.prefs.setBoolPref("enableTagSuggestions", v);
    },

    get is_english(){
        var lang = this.lang;
        if(lang){
            return lang.split("-")[0] == 'en';
        }
        return true;
    },
    get lang(){
        var prefs = Components.classes["@mozilla.org/preferences-service;1"]
            .getService(Components.interfaces.nsIPrefBranch2);
        var lang = prefs.getComplexValue("intl.accept_languages",
                Ci.nsIPrefLocalizedString).data;

        if(lang){
            var a = lang.split(",");
            if(a.length){
                return a[0];
            }
        }
        return lang;
    },
    get tagSuggestionsEnabled() {
        var result;
        
        try {
            result = this.prefs.getBoolPref("enableTagSuggestions");
        } catch (e) {
            try {
                var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                                .getService(Components.interfaces.nsIPrefBranch2);
                var lang = prefs.getComplexValue("intl.accept_languages",
                    Ci.nsIPrefLocalizedString).data;
                result = lang.substr(0, 2) == "en";
                this.tagSuggestionsEnabled = result;
            } catch(f){
                result = false;
            }
        }
        
        return result;
    },
    
    // get calculated settings
    get calcPath() {
        return this.path.replace("{username}", this.username);
    },
       
    get NowAsGMT() {
        var d = new Date();
        return d.toGMTString();
    },
    

    SyncComplete: function(syncType) {
        this.setLastSyncDate(syncType, this.NowAsGMT);
        // TODO - remove the last sync by type; it's really unnecssary and causes issues if bookmarks are being synced
        if(!this.isSyncEnabled("bookmarks")){
            this.setLastSyncDate("bookmarks", this.NowAsGMT);
        }
    },

    // Additions to support Sync2
    get useOwnServer() {
        return this.getBoolPref("useOwnServer", false);
    },

    set useOwnServer(bool) {
        this.prefs.setBoolPref("useOwnServer", bool);
    },

    set url(u) {
        this.prefs.setCharPref("url-bookmarks", u);
    },

    get url() {
        return this.getCharPref("url-bookmarks",
            this.getCharPref("url", ""));
    },

    set passwordurl(u) {
        this.prefs.setCharPref("url-passwords", u);
    },

    get passwordurl() {
        return this.getCharPref("url-passwords", "");
    },

    set hideStatusIcon(b) {
        this.prefs.setBoolPref("hideStatusIcon", b);
        var os = Cc["@mozilla.org/observer-service;1"].
            getService(Ci.nsIObserverService);
        os.notifyObservers(null, "foxmarks-statechange", 
            gSettings.hideStatusIcon ?  "hide" : "show")
    },

    get hideStatusIcon() {
        return this.getBoolPref("hideStatusIcon", false);
    },

    set hideSBS(b) {
        this.prefs.setBoolPref("hideStatusBarStars", b);
        var os = Cc["@mozilla.org/observer-service;1"]
            .getService(Ci.nsIObserverService);
        os.notifyObservers(null, "foxmarks-settingschange","");
    },

    get hideSBS() {
        return this.getBoolPref("hideStatusBarStars", false);
    },

    getUrlWithUsernameAndPassword: function(syncType){
        var url;
        if(syncType == "bookmarks")
            url = this.url;
        else
            url = this.getCharPref("url-" + syncType, "");
            
        var user = this.username;
        var pw = this.password;
        
        if (pw.length) {
            user += ":" + pw;
        }

        if (user.length) {
            user += "@";
        }

        return url.replace("://", "://" + user);
    },

    get majorVersion() {
        return this.getIntPref("majorVersion", 1);
    },

    set majorVersion(ver) {
        this.prefs.setIntPref("majorVersion", ver);
    },

    get currVersion(){
        return this.getCharPref("lastUpdateVersion", "");
    },

    set currVersion(ver){
        this.prefs.setCharPref("lastUpdateVersion", ver);
    },
    get getRevisionLimit(){
        return this.getIntPref("getRevisionLimit", 50);
    },
    set getRevisionLimit(val){
        this.prefs.setIntPref("getRevisionLimit", val);
    },
    get host() {
        if (this.useOwnServer) {
            var exp = /(.*):\/\/([^\/]*)/;
            var result = this.url.match(exp);
            if (!result) {
                return null;
            } else {
                return result[1] + "://" + result[2];
            }
        } else {
            return this.getCharPref("host-bookmarks",
                       this.getCharPref("host", "sync.xmarks.com"));
        }
    },

    setDebugOption: function(opt, val){
        this.prefs.setBoolPref("debug-" + opt, val);
    },
    getDebugOption: function(opt){
        return this.getBoolPref("debug-" + opt, false);
    },
    getUnitTestOption: function(opt){
        return this.getIntPref("unittest-" + opt, 0);
    },
    getServerHost: function(syncType){
        if(syncType == "bookmarks" || this.useOwnServer)
            return this.host;
        else
            return this.getCharPref("host-" + syncType, "sync.xmarks.com");
    },
    
    get maxUserTags(){
        return this.getIntPref("maxusertags", 1000);
    },
    get numTurboTags(){
        return this.getIntPref("turbotagctr", 0);
    },
    set numTurboTags(num){
        this.prefs.setIntPref("turbotagctr", num);
    },


    get myHost() {
        return this.getCharPref("host-my", "my.xmarks.com");
    },
    get apiHost() {
        return this.getCharPref("host-api", "api.xmarks.com");
    },
    set apiHost(val) {
        this.prefs.setCharPref("host-api", val);
    },
    get driftHost() {
        return this.getCharPref("host-drift", "www.xmarks.com");
    },
    set driftHost(val) {
        this.prefs.setCharPref("host-drift", val);
    },
    get staticHost() {
        return this.getCharPref("host-static", "static.xmarks.com");
    },
    set staticHost(val) {
        this.prefs.setCharPref("host-static", val);
    },
    get trHost() {
        return this.getCharPref("host-tr", "tr.xmarks.com");
    },
    set trHost(val) {
        this.prefs.setCharPref("host-tr", val);
    },
    get cloudHost() {
        return this.getCharPref("host-cloud", "cloud.xmarks.com");
    },
    set cloudHost(val) {
        this.prefs.setCharPref("host-cloud", val);
    },
    get acctMgrHost() {
        return this.useOwnServer ? "" : 
            this.getCharPref("host-login",
                this.getCharPref("acctMgrHost",
                    this.host.replace("sync", "login")
                )
            );
    },

    get truncateLog(){
        return this.getBoolPref("truncateLog", true);

    },
    get webHost() {
        return this.useOwnServer ? "www.xmarks.com" : 
            this.getCharPref("host-www",
                this.getCharPref("acctMgrHost",
                    this.host.replace("sync", "www")
                )
            );
    },
    get authUrl() {
        return this.getCharPref("authUrl", 
            (this.securityLevel == -1 ?  "http://" : "https://") + 
            this.acctMgrHost + "/xmauth/frame");
    },

    // -1: never synced
    //  0: likely synced in prior installation
    //  > 0: definitely synced
    get currentRevision() {
        return this.getIntPref(this.hash + "currentRevision", -1);
    },

    set currentRevision(cv) {
        if (cv != this.currentRevision) {
            this.prefs.setIntPref(this.hash + "currentRevision", cv);
            this.ps.savePrefFile(null);     // ensure it gets flushed
        }
    },

    GetSyncRevision: function(syncType) {
        if(syncType == "bookmarks")
            return this.getIntPref(this.hash + "currentRevision", -1);
        else    
            return this.getIntPref(this.hash + syncType + "-currentRevision", -1);
    },

    SetSyncRevision: function(syncType,cv) {
        if (cv != this.GetSyncRevision(syncType)) {
            if(syncType == "bookmarks")
                this.prefs.setIntPref(this.hash + "currentRevision", cv);
            else
                this.prefs.setIntPref(this.hash + syncType + "-currentRevision", cv);
            this.ps.savePrefFile(null);     // ensure it gets flushed
        }
    },
    get securityLevel() {
        // -1: use cleartext throughout
        //  0: use SSL for auth, cleartext otherwise (default)
        //  1: use SSL everywhere
        return this.getIntPref("securityLevel", 0);
    },

    set securityLevel(level) {
        this.prefs.setIntPref("securityLevel", level);
    },

    get disableIconSync() {
        return this.getBoolPref("disableIconSync", false);
    },

    get disableDirtyOnBatch() {
        return this.getBoolPref("disableDirtyOnBatch", false);
    },

    set machineId(val){
        this.prefs.setCharPref("machineId", val);
    },
    get machineId() {
        var id = this.getCharPref("machineId", null);
        if (!id) {
            var cookieMgr = Components.classes["@mozilla.org/cookiemanager;1"]
                            .getService(Components.interfaces.nsICookieManager);

            for (var e = cookieMgr.enumerator; e.hasMoreElements();) {
                var cookie = e.getNext().QueryInterface(Components.interfaces.nsICookie); 
                if(cookie.host == ".xmarks.com" && cookie.name == "xplatid"){
                    LogWrite("using xplatid for mid");
                    id = cookie.value;
                    break;
                }
            }
            if(!id){
                id = Date.now().toString(36);
            }
            this.prefs.setCharPref("machineId", id);
        }
        return id;
    },

    get clientName() {
        var name = this.getCharPref("clientName", null);
        if (name) 
            return name;

        var es = Cc['@mozilla.org/process/environment;1'].
            getService(Ci.nsIEnvironment);
        var user = es.get("USER") || es.get("USERNAME");

        var si = Cc['@mozilla.org/system-info;1'].
            getService(Ci.nsIPropertyBag2);
        var host = si.get("host");

        var pp = Cc['@mozilla.org/file/directory_service;1'].
            getService(Ci.nsIProperties).get("ProfD",  Ci.nsIFile).path;

        var profile = pp.match(/(.*)\.(.*)$/)[2];
        var stringname = "settings.clientname";
        if (profile == 'default') {
            stringname = "settings.clientname.noprofile";
        }

        return this.clientName = Bundle().
            GetStringFromName(stringname).
            replace("%1", host).replace("%2", user).replace("%3", profile);
    },

    set clientName(val) {
        this.prefs.setCharPref("clientName", val);
    },

    get serverVersion() {
        return this.getIntPref("serverVersion", 0);
    },

    set serverVersion(val) {
        this.prefs.setIntPref("serverVersion", val);
    },
    get viewId() {
        return this.getIntPref(this.hash + "viewId", 0);
    },

    set viewId(vid) {
        this.prefs.setIntPref(this.hash + "viewId", vid);
    },

    get useView() {
        return this.getBoolPref("useView", true);
    },

    set useView(val) {
        this.prefs.setBoolPref("useView", val);
    },

    _stPref: function(undec, id, v){
        if(undec){
            return "UST-" + id + "-" + v;
        }
        return "ST-" + id + "-" + v;
    },
    getST: function(undec,id){
        var result = {};
        var val;

        val = this.getIntPref(this._stPref(undec,id, "d"), 0);
        if(val) result["d"] = val;
        val = this.getIntPref(this._stPref(undec,id, "dc"), 0);
        if(val) result["dc"] = val;
        val = this.getIntPref(this._stPref(undec,id, "u"), 0);
        if(val) result["u"] = val;
        val = this.getIntPref(this._stPref(undec,id, "uc"), 0);
        if(val) result["uc"] = val;
        val = this.getIntPref(this._stPref(undec,id, "na"), 0);
        if(val) result["na"] = val;
        val = this.getIntPref(this._stPref(undec,id, "h"), 0);
        if(val) result["h"] = val;

        return result;
    },
    incrST: function(id, key, undec){
        var name;

        name = this._stPref(undec,id, key);
        this.prefs.setIntPref(name, this.getIntPref(name, 0) + 1);

    },
    clearST: function(id){
        this.prefs.setIntPref(this._stPref(true, id, "d"),0);
        this.prefs.setIntPref(this._stPref(true, id, "dc"),0);
        this.prefs.setIntPref(this._stPref(true, id, "u"),0);
        this.prefs.setIntPref(this._stPref(true, id, "uc"),0);
        this.prefs.setIntPref(this._stPref(true, id, "na"),0);
        this.prefs.setIntPref(this._stPref(true, id, "h"),0);
        this.prefs.setIntPref(this._stPref(false, id, "d"),0);
        this.prefs.setIntPref(this._stPref(false, id, "dc"),0);
        this.prefs.setIntPref(this._stPref(false, id, "u"),0);
        this.prefs.setIntPref(this._stPref(false, id, "uc"),0);
        this.prefs.setIntPref(this._stPref(false, id, "na"),0);
        this.prefs.setIntPref(this._stPref(false, id, "h"),0);
    },

    get trSERP() {
        return this.getCharPref("trSERP", "");
    },

    set trSERP(val) {
        this.prefs.setCharPref("trSERP", val);
    },
    get bibBug() {
        return this.getCharPref("bibBug", "/FX/xmarks_bib.png");
    },

    set bibBug(val) {
        this.prefs.setCharPref("bibBug", val);
    },

    get viewName() {
        return this.getCharPref(this.hash + "viewName", this.viewId ?
            String(this.viewId) : 
            Bundle().GetStringFromName("profile.globalname"));
    },

    set viewName(name) {
        this.prefs.setCharPref(this.hash + "viewName", name);
    },

    get syncShortcutKey() {
        return this.getCharPref("shortcut.SyncNow", "");
    },

    get siteinfoDialogShortcutKey() {
        return this.getCharPref("shortcut.SiteInfo", "");
    },
    get openSettingsDialogShortcutKey() {
        return this.getCharPref("shortcut.OpenSettings", "");
    },
    get openTabsShortcutKey() {
        return this.getCharPref("shortcut.OpenTabs", "");
    },

    get privateBrowsing() {
        try {
            var pbs = Cc["@mozilla.org/privatebrowsing;1"]
                    .getService(Ci.nsIPrivateBrowsingService);
            return pbs.privateBrowsingEnabled;
        } catch (e) {
            return false;
        }
    },

    get postToFB() {
        return this.getBoolPref("postToFB", true);
    },

    set postToFB(v) {
        this.prefs.setBoolPref("postToFB", v);
    },

    correctHashes: function(){
        var oldhash = "";
        // if we are now using the default host, we must
        // have used sync.foxmarks.com before
        LogWrite("Upgrading to 3.0: correcting hashes");
        LogWrite("Current Host: " + this.host);
        if(this.host == "sync.xmarks.com"){
            oldhash = this._calcHash("sync.foxmarks.com");

            LogWrite("Hashes Need Correction");
            // change all the hosts
            this.prefs.setCharPref(this.hash + "lastSynchDate", 
                this.getCharPref(oldhash + "lastSynchDate", ""));
            this.prefs.setCharPref(this.hash + "passwords-lastSynchDate", 
                this.getCharPref(oldhash + "passwords-lastSynchDate", ""));
            this.prefs.setIntPref(this.hash + "currentRevision", 
                this.getIntPref(oldhash + "currentRevision", -1));
            this.prefs.setIntPref(this.hash + "passwords-currentRevision", 
                this.getIntPref(oldhash + "passwords-currentRevision", -1));
            this.prefs.setIntPref(this.hash + "viewId", 
                this.getIntPref(oldhash + "viewId", 0));
            this.prefs.setCharPref(this.hash + "viewName", 
                this.getCharPref(oldhash + "viewName", this.viewId ?
                    String(this.viewId) : 
                    Bundle().GetStringFromName("profile.globalname")
            ));
        } else {
            oldhash = this.hash;
        }

        // bookmark baseline 
        var dirname = Cc['@mozilla.org/file/directory_service;1']
            .getService(Ci.nsIProperties)
            .get('ProfD', Ci.nsIFile);
        var file = Cc['@mozilla.org/file/directory_service;1']
            .getService(Ci.nsIProperties)
            .get('ProfD', Ci.nsIFile);
        var fromname = "foxmarks-baseline-" + oldhash + "json";
        var toname = "xmarks-baseline-" + this.hash + "json";

        try {
            file.append(fromname);
            file.moveTo(dirname, toname);
        } catch(e){
            // don't do anything if file doesn't exist
        }

        // password baseline
        file = Cc['@mozilla.org/file/directory_service;1']
            .getService(Ci.nsIProperties)
            .get('ProfD', Ci.nsIFile);
        fromname = "foxmarks-password-baseline-" + oldhash + "json";
        toname = "xmarks-password-baseline-" + this.hash + "json";

        try {
            file.append(fromname);
            file.moveTo(dirname, toname);
        } catch(e){
            // don't do anything if file doesn't exist
        }

        this.ps.savePrefFile(null);     // ensure it gets flushed
    }
}

var logStream = null;
 
function removeTempLogFile(){
    var fileremoved = Cc['@mozilla.org/file/directory_service;1']
        .getService(Ci.nsIProperties)
        .get('ProfD', Ci.nsIFile);
    fileremoved.append("xmarks.temp.log");
    try {
        fileremoved.remove(false);
    } catch(e){}
}

function logMoveFile(){
    try {
        var file = Cc['@mozilla.org/file/directory_service;1']
            .getService(Ci.nsIProperties)
            .get('ProfD', Ci.nsIFile);
        file.append("xmarks.log");

        var dir = Cc['@mozilla.org/file/directory_service;1']
            .getService(Ci.nsIProperties)
            .get('ProfD', Ci.nsIFile);
        removeTempLogFile();
        file.moveTo(dir, "xmarks.temp.log");


        var fromstream = Cc["@mozilla.org/network/file-input-stream;1"]
            .createInstance(Ci.nsIFileInputStream);
        var tostream = Cc["@mozilla.org/network/file-output-stream;1"]
            .createInstance(Ci.nsIFileOutputStream);

        fromstream.init(file, -1, 0x01, 0);
        var logSeek = fromstream.QueryInterface(Ci.nsISeekableStream);
        var lread = fromstream.QueryInterface(Ci.nsILineInputStream);
        var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
            .createInstance(Ci.nsIScriptableUnicodeConverter);
        converter.charset = "UTF-8";
        var i =  100 * 1024;
        if(i > file.fileSize){
            i = file.fileSize;
        }

        var filenew = Cc['@mozilla.org/file/directory_service;1']
            .getService(Ci.nsIProperties)
            .get('ProfD', Ci.nsIFile);
        filenew.append("xmarks.log");
        tostream.init(filenew, 0x02 | 0x08 | 0x10, 0664, 0);

        logSeek.seek(logSeek.NS_SEEK_END, -i);

        var buf;
        var cont = true;
        var lineData = {};
        var ctr = 0;
        // throw out the first one; could be mid line
        cont = lread.readLine(lineData);
        while(cont){
            lineData = {};
            cont = lread.readLine(lineData);
            if(cont){
                buf = converter.ConvertToUnicode(lineData.value) + "\n";
                tostream.write(buf, buf.length);
            }
        }

     } catch(e){
        Components.utils.reportError(e);
     } finally {
        if(fromstream !== undefined)
            fromstream.close();
        if(tostream !== undefined)
            tostream.close();
     }
    removeTempLogFile();
}

function logFileOpen() {
    var file = Cc['@mozilla.org/file/directory_service;1']
    .getService(Ci.nsIProperties)
    .get('ProfD', Ci.nsIFile);

    var needsTruncate = false;
    var filesize = 0;
    file.append("xmarks.log");

    // check the file size
    try {
       if(file.isFile()){
            filesize = file.fileSize;
       }

       if(filesize > 500 * 1024 && gSettings.truncateLog ){
            logMoveFile();
       }
    } catch(e) {
   //     Components.utils.reportError(e);
    }
    
    try {
        logStream = Cc["@mozilla.org/network/file-output-stream;1"]
        .createInstance(Ci.nsIFileOutputStream);

        // use write, append, create
        logStream.init(file, 0x02 | 0x08 | 0x10, 0664, 0);
    } catch (e) {
        // We failed to open. Close and try again next time.
        logFileClose();
    }
}

function logFileClose() {
    try {
        logStream.close();
    } catch (e) {}
    logStream = null;
}

function logWrite(msg) {
    if (!logStream)
        logFileOpen();

    var d = new Date();
    var year = d.getFullYear();
    var month = d.getMonth() + 1; if (month < 10) month = "0" + month;
    var day = d.getDate(); if (day < 10) day = "0" + day;
    var hour = d.getHours(); if (hour < 10) hour = "0" + hour;
    var minute = d.getMinutes(); if (minute < 10) minute = "0" + minute;
    var sec = d.getSeconds(); if (sec < 10) sec = "0" + sec;

    // format is [YYYY-MM-DD HH:MM:SS] msg\n
    var string = "[" + year + "-" + month + "-" + day + " " + hour + ":" +
        minute + ":" + sec + "] " + msg + "\n";

    if(gSettings.getDebugOption("dumplog"))
        dump("Xmarks: " + string + "\n");
    logStream.write(string, string.length)
}

var gSettings = new XmarksSettings();
