var EXPORTED_SYMBOLS = ["YOONO_YEXTIF"];

Components.utils.import("resource://yoono/yoonoKeyValueDB.js");
Components.utils.import("resource://yoono/yoonoPrefs.js");

// Globals
const CI = Components.interfaces;
const CL = Components.classes;

/////
const YOONO_DIRSERVICE = Components.classes['@mozilla.org/file/directory_service;1'].getService(Components.interfaces.nsIProperties);
const YOONO_SOUND = Components.classes["@mozilla.org/sound;1"].createInstance(Components.interfaces.nsISound);
const COOKIE_MAN = CL["@mozilla.org/cookiemanager;1"].getService(CI.nsICookieManager).QueryInterface(CI.nsICookieManager2);
const COOKIE_PERM = Components.classes['@mozilla.org/cookie/permission;1'].getService(Components.interfaces.nsICookiePermission);
const WMED = Components.classes['@mozilla.org/appshell/window-mediator;1'].getService(Components.interfaces.nsIWindowMediator);

const YNPREFSSERVICE = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
const YNPREFS = YNPREFSSERVICE.getBranch("extensions.yoono.");

const YOONO_ID = "{d9284e50-81fc-11da-a72b-0800200c9a66}";

var yoono = {};

function YoonoExternalInterfaces() {
  this.wrappedJSObject=this;
  
  this.methods = {};
  this.singletonPopup = null;
  this.popouted = {}; // store popouted yoodgets
  this.yoodgetInstances = {}; // store all yoodget instances
  this._ignoredPartners = {};
  this.inited = false;
}

YoonoExternalInterfaces.prototype.init = function (y) {
  
  yoono = y;
  
  if (this.inited) return;
  this.inited = true;
  
  // Wait for yoono var before setting methods
  this.setMethods();
  
  this.initCookies();
  var ip = YOONO_PREFS.get("ignoredPartners") || "";
  var ips = ip.split(',');
  for(var idx=0; idx < ips.length; idx++) {
     this._ignoredPartners[ips[idx]] = true;
   }
}

YoonoExternalInterfaces.prototype.initCookies = function () {
try {
  
  if (!this.methods.isStandalone()) {
    var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
                          .getService(Components.interfaces.nsIXULAppInfo);
    var versionChecker = Components.classes["@mozilla.org/xpcom/version-comparator;1"]
                                   .getService(Components.interfaces.nsIVersionComparator);
    if(versionChecker.compare(appInfo.version, "3.5") < 0) {
      // Disable cookies hacking on firefox 3.0
      return;
    }
  }
  
  Components.utils.import("resource://yoono/yoonoCookies.js");
  
  var listener;
  if (this.methods.isStandalone())
    listener = this.getStandaloneCookiesListener();
  else
    listener = this.getFirefoxCookiesListener();
  
  CookiesService.init(listener);
  
  if (!this.methods.isStandalone()) {
    CookiesService.FirefoxWindowsWatcher.start();
  }
} catch(e) {
  yoono.log.exception(e);
}
}

YoonoExternalInterfaces.prototype.getStandaloneCookiesListener = function () {
  var _self = this;
  return {
    getBrowserSessionId : function (browser) {
      // No session restore for yoono desktop
      return null;
    },
    onBrowserAttached : function (browser, session_id) {
      
      _self.getTopWindow().ynWebPanel._domWin.gAccountSelector.onTabSelect();
      
    },
    onBrowserDetached : function (browser) {
      
    },
    onBrowserStateChange : function (browser, state) {
    
    }
  };
}

YoonoExternalInterfaces.prototype.getFirefoxCookiesListener = function () {
  var sessionStore = Components.classes["@mozilla.org/browser/sessionstore;1"].getService(Components.interfaces.nsISessionStore);
  var _self = this;
  return {
    getBrowserSessionId : function (browser) {
      try {
        var gBrowser = browser.getTabBrowser();
        var i = gBrowser.getBrowserIndexForDocument(browser.contentDocument);
        var tab = gBrowser.tabContainer.childNodes[i];
        
        return sessionStore.getTabValue(tab, "cookies-db-id");
      } catch(e) {
        Components.utils.reportError(e);
      }
    },
    
    onBrowserAttached : function (browser, session_id) {
      var gBrowser = browser.getTabBrowser();
      
      // On Firefox, save an hint in session store 
      // in order to be able to restore our cookies db
      try {
        var i = gBrowser.getBrowserIndexForDocument(browser.contentDocument);
        var tab = gBrowser.tabContainer.childNodes[i];
        sessionStore.setTabValue(tab, "cookies-db-id", session_id);
      } catch(e) {}
      
      // Then overload some tab methods to intercept location change
      if (!browser._originalLoadURIWithFlags) {
        browser._originalLoadURIWithFlags = browser.loadURIWithFlags;
        browser.loadURIWithFlags = function (aURI, aFlags, aReferrerURI, aCharset, aPostData) {
          //Components.utils.reportError("loadURI : "+browser);
          var original = browser._originalLoadURIWithFlags;
          if (browser.currentURI.spec!=aURI) {
            browser._originalLoadURIWithFlags = null;
            browser.loadURIWithFlags = original;
            // When user go to another website, revert to default cookies database
            //browser.parentNode.removeChild(browser.previousSibling);
            CookiesService.detachSessionFromBrowser(browser);
          }
          return original.call(browser, aURI, aFlags, aReferrerURI, aCharset, aPostData);
        }
      }
      
      //TODO: check this hack
      browser.ownerDocument.defaultView.gAccountSelector.onTabSelect();
      
      this.clearNotifications(gBrowser, browser);
    }, // end of onBrowserAttached
    
    onBrowserDetached : function (browser) {
      var gBrowser = browser.getTabBrowser();
      
      try {
        var i = gBrowser.getBrowserIndexForDocument(browser.contentDocument);
        var tab = gBrowser.tabContainer.childNodes[i];
        sessionStore.deleteTabValue(tab, "cookies-db-id");
      } catch(e) {}
      
      //TODO: check this hack
      browser.ownerDocument.defaultView.gAccountSelector.onTabSelect();
      
      this.clearNotifications(gBrowser, browser);
    },
    
    clearNotifications : function(gBrowser, browser) {
      var notificationBox = gBrowser.getNotificationBox(browser);
      var previousNotif = notificationBox.getNotificationWithValue("accounts-selector-error");
      if (previousNotif)
        notificationBox.removeNotification(previousNotif);
    },
    
    onBrowserStateChange : function (browser, session, state) {
      if (CookiesService.getSessionId(browser)!=session.id) return;
      
      var gBrowser = browser.getTabBrowser();
      
      if (state.value == "ok") {
        this.clearNotifications(gBrowser, browser);
        return;
      }
      
      var notificationBox = gBrowser.getNotificationBox(browser);
      if (notificationBox.getNotificationWithValue("accounts-selector-error")) return;
      
      if (state.value == "another-identity") {
        
        var notif = notificationBox.appendNotification(
          "Account are mixed up, you are signed in as '"+state.identity+"' instead of '"+session.name+"' ...",
          "accounts-selector-error",
          "", // image
          notificationBox.PRIORITY_WARNING_MEDIUM,
          [{ label: "Sign in with the good account",
             accessKey: "S",
             popup:     null,
             callback:  function(){
                var homeURL = CookiesService.getSignoutURL(session);
                if (homeURL) {
                  if (browser._originalLoadURIWithFlags) // bypass loadURI overload that reset selected account
                    browser._originalLoadURIWithFlags(homeURL);
                  else
                    browser.loadURI(homeURL, null, null);
                }
             }
           }]
        );
        notif.persistence = 10;
        
      } else if (state.value == "signed-out" && session.name) {
        
        var notif = notificationBox.appendNotification(
          "Your session has expired, please sign in with your '"+session.name+"' account",
          "accounts-selector-error",
          "", // image
          notificationBox.PRIORITY_WARNING_MEDIUM,
          [{ label: "Sign in",
             accessKey: "S",
             popup:     null,
             callback:  function(){
                var homeURL = CookiesService.getSigninURL(session);
                if (homeURL) {
                  if (browser._originalLoadURIWithFlags) // bypass loadURI overload that reset selected account
                    browser._originalLoadURIWithFlags(homeURL);
                  else
                    browser.loadURI(homeURL, null, null);
                }
             }
           }]
        );
        notif.persistence = 10;
        
      }
    }
  };
}// end of getFirefoxCookiesListener

YoonoExternalInterfaces.prototype.getYoonoGlob = function () {  
  return this.getTopWindow().yoonoGlob;
}
  
YoonoExternalInterfaces.prototype.getSidebar = function () {
  return this.getYoonoGlob().sidebar;
}
 
YoonoExternalInterfaces.prototype.setMethods = function () {
  var _self = this;
  
  this.methods = {};

  // For Chrome compatibility
  this.methods.isFeatureAllowed = function (aFeatureName) {
    var allowed = true;
    // We could set up rules according to FF release, ... or whatever
    return(allowed);
  }

  this.methods.isTosAccepted = function() {
    return(yoono.prefs.get("tos-accepted"));
  }

  this.methods.setTosAccepted = function() {
    var yoonoGlob = _self.getYoonoGlob();
    yoonoGlob.tosAccepted = true;
    yoono.prefs.set("tos-accepted", true);
  }

  // No longer used...(7.4.1)
  this.methods.tosCallBack = function(action) {
    var yoonoGlob = _self.getYoonoGlob();
    yoonoGlob.tosPop = null;
    switch(action) {
      case 'accept':
        yoonoGlob.tosAccepted = true;
        yoono.prefs.set("tos-accepted", true);
        yoonoGlob.sidebar.show();
        yoono.main.getYServices().observer.notifyObservers("app.tos-accepted",null);

      break;
      case 'uninstall':
        yoono.main.addStat(['clic.wizard.uninstall']);
        yoono.main.uninstallFromButton();
      break;                 
    }
  } 
  this.methods.log = function(m,caller) {yoono.log.debug(m,caller?caller:(Components.stack.caller?Components.stack.caller.caller:null))};
  this.methods.logDebug = function(m,caller) {yoono.log.debug(m,caller?caller:(Components.stack.caller?Components.stack.caller.caller:null))};
  this.methods.logWarning = function(m,caller) {yoono.log.warn(m,caller?caller:(Components.stack.caller?Components.stack.caller.caller:null))};
  this.methods.logInfo = function(m,caller) {yoono.log.info(m,caller?caller:(Components.stack.caller?Components.stack.caller.caller:null))};
  this.methods.logError = function(m,caller) {yoono.log.error(m,caller?caller:(Components.stack.caller?Components.stack.caller.caller:null))};
  this.methods.logException = function(m,caller) {yoono.log.exception(m,caller?caller:(Components.stack.caller?Components.stack.caller.caller:null))};
  this.methods.logFatal = function(m,caller) {yoono.log.fatal(m,caller?caller:(Components.stack.caller?Components.stack.caller.caller:null))};
  this.methods.isErrorEnabled = function() {return yoono.log.isErrorEnabled();};
  this.methods.isWarnEnabled = function() {return yoono.log.isWarnEnabled();};
  this.methods.isInfoEnabled = function() {return yoono.log.isInfoEnabled();};
  this.methods.isDebugEnabled = function() {return yoono.log.isDebugEnabled();};
  this.methods.isStandalone = function() {
    if (typeof _self._isStandaloneCache=="undefined") {
      var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULAppInfo);
      _self._isStandaloneCache = appInfo.ID=="xulrunner@yoono.com";
    }
    return _self._isStandaloneCache;
  };
  this.methods.getClientType = function() {return 'xpi';};
  
  // Storage database initialization (creation, upgrading, opening)
  this.methods.openStorage = function() {
    yoono.storage.openStorage();
  }
  this.methods.backupDB = function(aVersion) {
    return(yoono.storage.backupDB(aVersion));
  }
  this.methods.executeAsyncSql = function(aParams) {
    var statement = aParams[0];// one sql query with placeholders
    var values = aParams[1];   // double list with placeholder's values (optional)
    var callback = aParams[2]; // callback function which will receive a double list of string (optional)
    var priority = aParams[3] || false;
    yoono.storage.executeAsyncSql(statement, values, callback, priority);
  }

  this.methods.getUserCredential = yoono.main.getUserCredential;

  this.methods.getVersion = function() {
    return(yoono.main.getExtensionVersion());
  }
  
  this.methods.getPlatformVersion = function() {
    var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULAppInfo);
    return appInfo.version;
  }
  
  this.methods.isFirstRun = function() {
    return _self.getYoonoGlob().firstRun;
  }
  
  this.methods.isFirstReleaseRun = function() {
    return _self.getYoonoGlob().firstReleaseRun;
  }

  this.methods.disableSync = function() {
    YOONO_PREFS.set('nosynchro', true, YNPREFS.PREF_BOOL);
  }

  this.methods.runSync = function(params) {
      var observer = params[0];
      // syncMode should be either first-sync or manual-sync
      var syncMode = params[1];

      var obsSrvc = Components.classes['@mozilla.org/observer-service;1'].getService(Components.interfaces.nsIObserverService);
      _self.syncObserver = {
        // called when sync is over.
        observe : function(subject, topic, data) {
          try {
              yoono.log.debug('sidebarExternal.runSunc.observe : got topic ' + topic);
              switch(topic) {
                case "yoono-synchro-useless":
                  // manual-sync was done, and was useless : must reset the nosynchro flag
                  YNPREFS.setBoolPref("nosynchro" , false); // -> no longer launch a connect !
                  // NO BREAK on purpose
                case "yoono-synchro-end":
                  var obsSrvc = Components.classes['@mozilla.org/observer-service;1'].getService(Components.interfaces.nsIObserverService);
                  obsSrvc.removeObserver(_self.syncObserver,"yoono-synchro-end");
                  obsSrvc.removeObserver(_self.syncObserver,"yoono-synchro-useless");
                  // notify wizard in sidebar
                  observer.notifyEndSync();
                  break;
              }
          } catch(e) { yoono.log.exception(e) }
        }
      }
      obsSrvc.addObserver(_self.syncObserver,"yoono-synchro-end"  , false);
      obsSrvc.addObserver(_self.syncObserver,"yoono-synchro-useless"  , false);
      yoono.server.launch('connect',syncMode);
  };

  this.methods.playSound = function(url) {
    var surl = Components.classes["@mozilla.org/network/standard-url;1"].createInstance(Components.interfaces.nsIURL);
    surl.spec = url;
    YOONO_SOUND.play(surl);
  };

  this.methods.subscribeToAllBrowserEvt = function(observer) {
    _self._browserObservers.push(observer);
    _self.notifyAlreadyOpenTabActivity(observer);
  };
  this.methods.unsubscribeToAllBrowserEvt = function(observer) {
    _self._browserObservers.splice(_self._browserObservers.indexOf(observer),1);
  };

  // Open a page with the user's authentication headers, and optionnal additional headers
  this.methods.openUrlLogin = function(params) {
    var loginPage = "http://api.yoono.com/my-yoono/sign_in.jsp" ;
    params[0] = loginPage + params[0] ;
    var userLoginInfo = _self.methods.getUserCredential();
    if(!userLoginInfo || userLoginInfo.anonymous) return;
    var login = userLoginInfo.login;
    var password = userLoginInfo.password;
    if(!(2 in params)) params[2] = [];
    params[2].push('X-Login: ' + login);
    params[2].push('X-Password: ' + password);
    _self.methods.openUrl(params);
  }

  
  this.methods.renameCookiesKey = function(params) {
    var src = params[0];
    var dst = params[1];
    var name = params[2];
    var avatar = params[3];
    var providerId = params[4];
    
    if (typeof CookiesService!="object") return;
    
    CookiesService.renameSession(src,dst);
    if (name || avatar) {
      var network = yoono.main.getYServices().accountsManager.getProviderDisplayName(providerId);
      CookiesService.setSessionInfo(dst, name, avatar, network, providerId);
    }
  }
  
  this.methods.setCookiesInfo = function(params) {
    var sessionId = params[0];
    var name = params[1];
    var avatar = params[2];
    var providerId = params[3];
    var network = yoono.main.getYServices().accountsManager.getProviderDisplayName(providerId);
    
    if (typeof CookiesService!="object") return;
    
    CookiesService.setSessionInfo(sessionId, name, avatar, network, providerId);
  }
  
  this.methods.deleteCookiesInfo = function (params) {
    var sessionId = params[0];
    
    if (typeof CookiesService!="object") return;
    CookiesService.deleteSessionInfo(sessionId);
  }

  this.methods.findDocForUrl = function(aParams) {
    var aUrl = aParams[0];
    var action = aParams[1];
    var nextUrl = aParams[2];

    // on Desktop : only possible operation is to load a page.
    if (_self.methods.isStandalone()) {
      switch(action) {
        case 'relocate':
          _self.methods.openUrl([nextUrl]);
          break;
        case 'show':
          _self.methods.openUrl([aUrl]);
          break;
        case 'close':
        break;
      }
      return false;
    }

    var topWindow = _self.getTopWindow();
    if(!topWindow) return(null);
    var gBrowser = topWindow.gBrowser;
    var tabPanels = gBrowser.browsers
    for(var idx=0; idx < tabPanels.length; idx++) {
      if(tabPanels[idx].contentWindow.document.URL == aUrl) {
        var doc = tabPanels[idx].contentWindow.document;
        switch(action) {
          // Find a tab and changes its URL
          case 'relocate':
            gBrowser.selectTabAtIndex(idx);
            doc.location = nextUrl;
          break;
          case 'show':
            gBrowser.selectTabAtIndex(idx);
          break;
          case 'close':
            doc = null;
            var tab = gBrowser.mTabs[idx];
            if(1 == gBrowser.mTabs.length) {
              tabPanels[idx].contentWindow.document.location = 'about:blank'
            } else {
              gBrowser.removeTab(tab);
            }
          break;
        }
        return(doc);
      }
    }
    return(null);
  }

  this.methods.openUrl = function(params) {
    var url = params[0];
    var mode = params[1] || 'new-tab';
    var headerArray = params[2] || [];
    var mandatoryInStandalone = params[3] || false; // mandatory open this url in xulrunner (not in system browser!)
    var accountId = params[4] || -1;
    
    //yoono.main.getYServices().log.backtrace("openUrl : "+url+"\n"+accountId+"\n");
    
    // 1) Retrieve a reference to a <browser> object
    var browser;
    if (_self.methods.isStandalone()) {
      if (!mandatoryInStandalone && YOONO_PREFS.get("open-in-system-browser")) {
        _self.getTopWindow().ynWebPanel.openUrlInSystemBrowser(url);
        return;
      }
      browser = _self.getTopWindow().ynWebPanel.openBrowser(mandatoryInStandalone);
    } else {
      var tab = _self.getTopWindow().gBrowser.addTab("about:blank");
      _self.getTopWindow().gBrowser.selectedTab = tab;
    
      browser = tab.linkedBrowser;
    }
    
    // 2) If we receive an account argument,
    //    Create and register a new Cookies database
    if (accountId!=-1 && typeof CookiesService=="object") {
      if (!mandatoryInStandalone) // avoid display of bubble when setting up a network
        _self.getTopWindow().gAccountSelector.displayInfoBubble();
      CookiesService.attachSessionToBrowser(browser, accountId, goLoadURI);
    } else {
      goLoadURI();
    }
    
    function goLoadURI() {
      
      // Build a valid XPCOM headers structure
      var headers = {};
      for(var i=0;i<headerArray.length; i++) {
        var semiC = headerArray[i].indexOf(':');
        if(-1 == semiC) continue;
        var name = headerArray[i].substr(0 , semiC);
        var value = headerArray[i].substr(semiC + 1);
        headers[name] = value;
      }
      if(!('Referer' in headers)){
        headers['Referer'] = 'http://www.yoono.com';
      }
      var aHeaders = Components.classes["@mozilla.org/network/mime-input-stream;1"].createInstance(Components.interfaces.nsIMIMEInputStream);
      for (var i in headers) {
        aHeaders.addHeader(i, headers[i]);
      }

      // Go!!!
      browser.webNavigation.loadURI(url, null, null, null, aHeaders);
      
    }// end of goLoadURI
    
  }

  this.methods.calcUTF8_MD5 = function(aString) {
    
    var converter =
      Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].
        createInstance(Components.interfaces.nsIScriptableUnicodeConverter);

    // we use UTF-8 here, you can choose other encodings.
    converter.charset = "UTF-8";
    // result is an out parameter,
    // result.value will contain the array length
    var result = {};
    // data is an array of bytes
    var data = converter.convertToByteArray(aString, result);
    var ch = Components.classes["@mozilla.org/security/hash;1"]
                       .createInstance(Components.interfaces.nsICryptoHash);
    ch.init(ch.MD5);
    ch.update(data, data.length);
    var hash = ch.finish(false);

    // return the two-digit hexadecimal code for a byte
    function toHexString(charCode)
    {
      return ("0" + charCode.toString(16)).slice(-2);
    }

    // convert the binary hash data to a hex string.
    var s = [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
    
    return s;
  }

  this.methods.setUser = function(aUserData) {
    yoono.main.changeUser(aUserData);
  };

  // Returns a pref from prefs in extensions.yoono. branch
  this.methods.getPref = function(prefName) {
    return YOONO_PREFS.get(prefName);
  };
  this.methods.setPref = function(params) {
    var prefName=params[0], prefValue=params[1];
    return YOONO_PREFS.set(prefName, prefValue);
  };
  this.methods.getLogLevel = function(prefName) {
    return(YOONO_PREFS.get('debug.level'))
  };
  
  this.methods.getKeyValueList = function(keyPattern) {
    return YOONO_KEYVALUEDB.getKeyValueList(keyPattern);
  };
  this.methods.deleteKeyValue = function(key) {
    return YOONO_KEYVALUEDB.deleteKeyValue(key);
  };
  this.methods.getKeyValue = function(key) {
    return YOONO_KEYVALUEDB.getKeyValue(key);
  };
  this.methods.setKeyValue = function(params) {
    var key = params[0];
    var value = params[1];
    var days = 0;
    if (2 in params) days = params[2];
    YOONO_KEYVALUEDB.setKeyValue(key, value, days);
  };

  this.methods.allowCookiesForHost = function(hostname) {
    var ioService = Components.classes["@mozilla.org/network/io-service;1"]
                                .getService(Components.interfaces.nsIIOService);
    var uri = ioService.newURI('http://'  + hostname, null, null);
    COOKIE_PERM.setAccess(uri, COOKIE_PERM.ACCESS_ALLOW);
  }

  this.methods.getCookiesForHost = function(hostname) {
    var result = [];
    var iter = COOKIE_MAN.enumerator;
    while (iter.hasMoreElements()) {
      var cookie = iter.getNext();
      if (cookie instanceof Components.interfaces.nsICookie) {
        if (cookie.host == hostname) {
          result.push({
            host: cookie.host,
            path: cookie.path,
            name: cookie.name,
            value: cookie.value,
            expires: cookie.expires
          });
        }
      }
    }
    return result;
  }

  this.methods.addCookie = function(params) {
    yoono.log.debug("Add cookie: " + params);
    COOKIE_MAN.add(params[0],
                   params[1],
                   params[2],
                   params[3],
                   false,
                   false,
                   false,
                   params[4]);
  }

  this.methods.removeCookiesForHost = function(aHost) {
    var toDelete = [];
    var iter = COOKIE_MAN.enumerator;
    while (iter.hasMoreElements()) {
      var cookie = iter.getNext();
      if (cookie instanceof Components.interfaces.nsICookie) {
        if (cookie.host == aHost) {
          toDelete.push(cookie);
        }
      }
    }
    for (i = 0; i < toDelete.length; i++) {
      var cookie = toDelete[i];
      COOKIE_MAN.remove(cookie.host, cookie.name, cookie.path, false);
    }
  }

  this.methods.getSelectedText = function() {
    return(_self.getYoonoGlob().getSelectedText());
  }

  this.methods.setThin = function(thinWidth) {
    _self.getSidebar().setThin(thinWidth);
  }

  // set a stat counter that will be sent every 24 hours
  this.methods.setStat = function(params) {
    var statName = params[0];
    var value = params[1];
    yoono.main.setStat([statName], value);
  }
  // increments a stat counter by a given amount
  this.methods.incStatBy = function(params) {
    var statName = params[0];
    var value = params[1];
    yoono.main.addStat([statName], value);
  }
  // increments a stat counter that will be sent every 24 hours
  this.methods.incStat = function(statName) {
    yoono.main.addStat([statName], 1);
  }

  this.methods.setLarge = function(largeWidth) {
    _self.getSidebar().setLarge(largeWidth);
  }

  // Open a popup for a Yoodget that is being extracted from the sidebar
  this.methods.popout = function(params) {
    try {
      var owner = params[0];
      var url = params[1];
      var props = params[2];
      var yuid = params[3];
      var instance = params[4];
      var sidebarId = params[5];
      var yoodgetParams = params[6];

      var propsStr = '';
      for (var i in props) {
        propsStr += i + '=' + props[i] + ',';
      }

      // we need a value different for each call
      var openTime = new Date().getTime() + Math.floor((Math.random() * 100000));
      var popup = _self.getTopWindow().open(url, openTime, propsStr);
      popup.ynSidebar = owner;
      popup.addEventListener('DOMContentLoaded',
              function(e) {
                try {
                  if(e.explicitOriginalTarget.defaultView==popup){
                    for (var method in _self.methods) {
                      popup.YEXTIF.methods[method] = _self.methods[method];
                    }
                    yoono.main.getYServices().core.initSubDocument(popup,"yoonoPopoutInit",[yoodgetParams]);
                  }
                } catch(e) {
                  yoono.log.exception(e);
                }
              },
              true);
      if (props.left && props.top)
        popup.moveTo(props.left, props.top);
      // add it to popouted windows  list (in extension)
      var key = yuid + instance + '-' + sidebarId;
      _self.popouted[key] = popup;
    } catch (e) {
      yoono.log.exception(e);
    }
  }
  // Re-insert a Yoodget in the sidebar
  this.methods.popin = function(params) {
    try {
      var url = params[0];
      var instance = params[1];
      _self.getSidebar().win.ynSidebar.addYoodget({'url': url, 'view' : 'block', 'instance': instance, 'pinnedDown':true});
      _self.getSidebar().win.ynSidebar.setSortable();
    } catch (e) {
      yoono.log.exception(e);
    }
  }

  this.methods.checkIfAlreadyPopouted = function(params) {
    var yuid = params[0];
    var instance = params[1];
    var sidebarId = params[2];
    var result = null;
    var key = yuid + instance + '-' + sidebarId;
    if (key in _self.popouted) {
      result = true;
    }
    if (result)
      return(_self.popouted[key]);
    else
      return(null);
  }
  
  
  this.methods.openSidebar = function(windowId) {
    var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                     .getService(Components.interfaces.nsIWindowMediator);
    var enumerator = wm.getEnumerator("navigator:browser");
    while(enumerator.hasMoreElements()) {
      var win = enumerator.getNext();
      var winId = _self.getWindowId(win);
      if (winId==windowId) {
        win.yoonoGlob.sidebar.show();
      }
    }
  }
  
  this.methods.closeSidebar = function() {
    _self.getSidebar().toggle(); // toggle will close it because we can call it only if sidebar is open
  }

  // Open a popup to show properties and actions for an element
  // Only one such popup can be opened at a time. If this method is called while one is already opened, close it first
  this.methods.singletonPopup = function(params) {
    try {
      _self.closeSingletonPopup();
      _self.singletonPopup = _self.methods.popup(params);
    } catch(e) {
      yoono.log.exception(e);
    }
    return(_self.singletonPopup);
  }

  // Open a popup that is not a popouted yoodget
  this.methods.popup = function(params) {
    try {
      var owner = params[0];
      var url = params[1];
      var name = params[2];
      var props = params[3];
      var extraParams = params[4];
      
      // manual centerscreen ... see bottom
      var centerscreen = props.centerscreen && props.centerscreen!="no";
      delete props.centerscreen;
      
      if (!props.left)
        props.left=_self.getTopWindow().screenX;//.availLeft;
      if (!props.top)
        props.top=_self.getTopWindow().screenY;//.availTop;
      
      var propsStr = '';
      for (var i in props) {
        propsStr += i + '=' + props[i] + ',';
      }
      propsStr = propsStr.replace(/,$/, '');
      
      // we need a different URL each time, otherwise an already opened window will be reloaded before 
      // we set the event lister that calls the init procedure...
      if (-1 == url.indexOf('#')) {
        url += '#' + Math.floor(Math.random() * 100000);
      }
      
      var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"].getService(Components.interfaces.nsIWindowWatcher);
      var old=ww.getWindowByName(name,null);
      if (old) {
        old.close();
      }
      
      var popup = _self.getTopWindow().open(url, name, propsStr);
      function onload(e) {
        popup.removeEventListener('DOMContentLoaded',onload,false);
        try {
          if(e.explicitOriginalTarget.defaultView==popup) {
            // for when there is no sidebar
            for (var method in _self.methods) {
              popup.YEXTIF.methods[method] = _self.methods[method];
            }          
            yoono.main.getYServices().core.initSubDocument(popup,"yoonoPopupInit",[owner, extraParams]);
          }
        } catch(e) {
          yoono.log.exception(e);
        }
      }
      popup.addEventListener('DOMContentLoaded', onload, false);
      
      // manual center screen...
      // https://bugzilla.mozilla.org/show_bug.cgi?id=410337
      if (centerscreen) {
        var sm = Components.classes["@mozilla.org/gfx/screenmanager;1"].getService(Components.interfaces.nsIScreenManager);
        var sc = sm.screenForRect(_self.getTopWindow().screenX, _self.getTopWindow().screenY, 10, 10);
        var left={},top={},width={},height={};
        sc.GetAvailRect(left,top,width,height);
        
        popup.moveTo(
          left.value+(width.value-popup.outerWidth)/2,
          top.value+(height.value-popup.outerHeight)/2
          );
        
      }
    } catch(e) {
      yoono.log.exception(e);
    }
    return(popup);
  }

  // This will return a array with the bookmarks
  this.methods.getBookmarks = function(params) {
    var target = yoono.bkm.getCurrentTree();
    params = params[0];
    for (var i = 0; i < params.length; i++) {
      target = target.childsList[params[i]];
    }
    
    var result = new Array();
    var type = "bookmark";
    var path = "";
    for (var j in target.childsList) {
      path = '';
      switch(target.childsList[j].type) {
        case "http://home.netscape.com/NC-rdf#BookmarkSeparator" :
          type = "separator";
          break;

        case 2:   // FF3
        case "http://home.netscape.com/NC-rdf#Folder" :  // FF2
          if(target.childsList[j].url) type = 'bookmark'  // FF3 : rss bkms are folders, with an url...
          else type = "folder";
          path = target.childsList[j].path.toServer;
          break;
        default:
          type = "bookmark";
      }
      var name = target.childsList[j].name;
      if('NOTITLE-rdf' == target.childsList[j].name.substr(0,11)) name = "";
      if ('PERSONAL_BOOKMARK_FOLDER' == name) {
        var tb = _self.getTopWindow().document.getElementById('PersonalToolbar');
        if (tb) {
          name = tb.getAttribute('toolbarname') || 'Personal toolbar';
        }
      }
      result.push({'title': name, 'url': target.childsList[j].url,
                   'type': type, 'path' : path});
    }
    target = null;
    return result;
  }

  // this is called by each yoodget in their init method so that actions can be taken
  // in the FF add-on part
  this.methods.yoodgetInSidebar = function(yuid) {
    switch(yuid) {
      case '{memo@yoono.com}':
        // In each opened window, show the webnote related menu items
        yoono.dialogs.traverseBrowsers(function(aWin) {
          var doc = aWin.document;
          var menuitem = doc.getElementById('yoono-context-menuitem');
          if(menuitem) menuitem.removeAttribute('hidden');
          menuitem = doc.getElementById('yoono-mymemos-menuitem');
          if(menuitem) menuitem.removeAttribute('hidden');
        });
      break;
    }
  }

  // reads the yoodgets manifest file and returns its content
  this.methods.getYoodgets = function(params) {
    try {
      var config = params[0];
      // Request the external yoodgets file
      // NO LONGER
      //yoono.main.sendRequest('http://www.yoono.com/yoodgets.xml', 'get', 'async', '', function(req, res) {
        //_self.handleExternalYoodgets(req, res, config)
      //});
      var manifestList = new Array();
      
      var rootDir = YOONO_DIRSERVICE.get("ProfDS", Components.interfaces.nsIFile);
      rootDir.append('extensions');
      rootDir.append(YOONO_ID);
      
      if (!rootDir.exists()) {
        // Xulrunner 
        // set rootDir to path where extension is located
        rootDir = null;
        var extDirs = YOONO_DIRSERVICE.get("XREExtDL", Components.interfaces.nsISimpleEnumerator);
        while(extDirs.hasMoreElements()) {
          var dir = extDirs.getNext().QueryInterface(Components.interfaces.nsIFile);
          dir.append('chrome');
          dir.append('m');
          dir.append('yoono.com');
          dir.append('sb');
          if (dir.exists()) {
            rootDir = dir;
            break;
          }
        }
        if (!rootDir) {
          var content = _self.methods.readFile("yoodgets.xml");
          if (!content)
            return yoono.log.error("Unable to fetch yoodgets list");
          config.setYoodget(content);
        }
        //rootDir = YOONO_DIRSERVICE.get("resource:app", Components.interfaces.nsIFile);
        //rootDir.append('extensions');
        //rootDir.append(YOONO_ID);
        //rootDir.append('chrome');
        //rootDir.append('m');
        //rootDir.append('yoono.com');
        //rootDir.append('sb');
        var dirEntries = rootDir.directoryEntries;
        while (dirEntries.hasMoreElements()) {
          var file = dirEntries.getNext().QueryInterface(Components.interfaces.nsIFile);
          if (!file || !file.isDirectory()) continue;
          file.append('yoodget.manifest');
          if (file.exists()) {
            manifestList.push(file);
          }
        }
      }
      else if (!rootDir.isDirectory()) {
        // if dev (extension entry is a file, not a directory)
        // read file to get path of dev extension
        var istream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
        istream.init(rootDir, 0x01, 0444, 0);
        istream.QueryInterface(Components.interfaces.nsILineInputStream);
        var line = {};
        istream.readLine(line);
        istream.close();
        rootDir = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
        // set rootDir to path where extension is located
        rootDir.initWithPath(line.value);
        rootDir.append('chrome');
        rootDir.append('m');
        rootDir.append('yoono.com');
        rootDir.append('sb');
        var dirEntries = rootDir.directoryEntries;
        while (dirEntries.hasMoreElements()) {
          var file = dirEntries.getNext().QueryInterface(Components.interfaces.nsIFile);
          if (!file || !file.isDirectory()) continue;
          file.append('yoodget.manifest');
          if (file.exists()) {
            manifestList.push(file);
          }
        }
      } else {
        // when xpi installed, use the local yoodgets.xml file that list all yoodgets
        // this file was created by the build script
        yoono.main.sendRequest('chrome://yoonosb/content/yoodgets.xml', 'get', 'async', '', function(req, res) {
          _self.handleExternalYoodgets(req, res, config)
        });
        return;
      }
      for(var i=0 ; i < manifestList.length; i++) {
        var file = manifestList[i];
        var istream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
        istream.init(file, 0x01, 0444, 0);
        istream.QueryInterface(Components.interfaces.nsILineInputStream);
        var converter = Components.classes["@mozilla.org/intl/converter-input-stream;1"].createInstance(Components.interfaces.nsIConverterInputStream);
        converter.init(istream, 'UTF-8', 1024, '-');
        var line = {};
        var hasmorelines;
        var content = '';
        while (converter.readString(4096, line)) {
          content += line.value;
        }
        istream.close();
        config.setYoodget(content);
      }
    } catch(e) {
      yoono.log.exception(e);
    }
  }

  // Read synchronously a file from its url
  // (Real synchronous ... not a FF3's xmlhttprequest fake!)
  this.methods.readFile = function (param) {
    var str = '';
    try {
      url = "chrome://yoonosb/content/" + param.replace(/\.\./g, '');
      var ioService=Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
      var scriptableStream=Components.classes["@mozilla.org/scriptableinputstream;1"]
        .getService(Components.interfaces.nsIScriptableInputStream);
      var channel=ioService.newChannel(url,'UTF-8',null);
      // TODO : solve UTF8 handling...
      var input=channel.open();
      scriptableStream.init(input);
       str=scriptableStream.read(input.available());
       scriptableStream.close();
      input.close();
    } catch(e) {
      // exception in external interface cannot be catched by application
      // so we return the exception.
      return e;
    }
    return str;
  }

  this.methods.ignorePartner = function(aParams) {
    var name = aParams[0];
    var value = aParams[1]
    _self._ignoredPartners[name] = value;
    var ips = "";
    for(var ip in _self._ignoredPartners) {
      if(_self._ignoredPartners[ip]) {
        if(ips)
          ips += ",";
        ips += ip;
      }
    }
    YOONO_PREFS.set("ignoredPartners", ips);
  };

  this.methods.partnerPresent = function(aName) {
    var isPresent = false;
    if(!_self._ignoredPartners[aName]) {
      // Only one, for now :)
      switch(aName) {
        case 'deviceVM':
          isPresent = yoono.main.isAddonInstalled(yoono.main.DEVICEVM_EXTENSION_ID);
        break;
      }
    }
    return(isPresent);
  }

} // end of setMethods()

YoonoExternalInterfaces.prototype.getTopWindow = function () {
  return WMED.getMostRecentWindow("navigator:browser");
}

YoonoExternalInterfaces.prototype.setSbIframe = function(iframe) {
  this.sbIframe = iframe;
}

YoonoExternalInterfaces.prototype.closeSingletonPopup = function() {
  this.closePopup(this.singletonPopup);
}

YoonoExternalInterfaces.prototype.closePopup = function(popup) {
  try {
    popup.close();
  } catch(e) {
  }
}

// When closing a FF window, close associated popups
YoonoExternalInterfaces.prototype.goodbye = function() {
  this.closeSingletonPopup();
  for (var i in this.popouted) {
    this.closePopup(this.popouted[i]);
  }
}

YoonoExternalInterfaces.prototype.handleExternalYoodgets = function(req, res, config) {
  var text = req.responseText;
  config.setYoodget(text);
}

// TODO : NEEDS TESTING.  and using.
YoonoExternalInterfaces.prototype.findReloadTab = function(url) {
  var result = false;
  var tabbrowser = this.getTopWindow().getBrowser();
  var panels = tabbrowser.mPanelContainer.childNodes;
  var panel = null;
  var panelUrl = null;
  var docContainer = null;
  for (var i = 0; i < panels.length; i++) {
    panel = panels[i];
    docContainer = panel.firstChild;
    while (!docContainer.contentDocument && docContainer.nextSibling) {
      docContainer = docContainer.nextSibling;
    }
    if (docContainer.contentDocument) {
      panelUrl = docContainer.contentDocument.URL;
      if (panelUrl == url) {
        result = true;
      }
    }
  }
  return(result);
}

YoonoExternalInterfaces.prototype._browserObservers = [];

YoonoExternalInterfaces.prototype.isPrivateModeEnabled = function () {
  var pbs = false;
  try {
    pbs = Components.classes["@mozilla.org/privatebrowsing;1"].getService(Components.interfaces.nsIPrivateBrowsingService);
  } catch(e) {}
  return pbs?pbs.privateBrowsingEnabled:false;
}

// Called from yoonoOverlay.js in each firefox window
// Call all observers registered with YEXTIF.subscribeToAllBrowserEvent
YoonoExternalInterfaces.prototype.notifyTabActivity = function (windowId, doc, url, state) {
  try {
    if (state=="loaded")
      doc.loaded=true;
    if ((this.isPrivateModeEnabled() && (url.indexOf('http://api.yoono.com/externals') == -1))) {
      url="about:privatebrowsing";
      //doc={};
    }
  } catch(e) {
    yoono.log.exception(e);
  }
  for(var i=0; i<this._browserObservers.length; i++) {
    try {
      this._browserObservers[i].notify(windowId, doc, url, state);
    } catch(e) {
      yoono.log.exception(e);
    }
  }
}
YoonoExternalInterfaces.prototype.notifyAlreadyOpenTabActivity = function (observer) {
  try {
    if (this.isPrivateModeEnabled()) {
      url="about:privatebrowsing";
      //doc={};
    }
    var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                     .getService(Components.interfaces.nsIWindowMediator);
    var enumerator = wm.getEnumerator("navigator:browser");
    while(enumerator.hasMoreElements()) {
      var win = enumerator.getNext();
      var winId = this.getWindowId(win);
      var doc = win.document.getElementById("content").contentDocument;
      if (doc.loaded || doc.location.href=="about:blank" || (0 == doc.location.href.indexOf('yoono:'))) { // about:blank & yoono: don't generate any notifyTabActivity ... so doc.loaded never become true
        observer.notify(winId, doc, doc.location.href, "loaded");
      }
    }
  } catch(e) {
    yoono.log.exception(e);
  }
}


YoonoExternalInterfaces.prototype.currentWindowId = 1;
YoonoExternalInterfaces.prototype.getWindowId = function (win) {
  if (!win.windowId)
    win.windowId = "window-"+(this.currentWindowId++)+"-"+new Date().getTime();
  return win.windowId;
}



var YOONO_YEXTIF = new YoonoExternalInterfaces();
