// vim: ts=2 sw=2 expandtab cindent
//
// BEGIN FLOCK GPL
//
// Copyright Flock Inc. 2005-2008
// http://flock.com
//
// This file may be used under the terms of the
// GNU General Public License Version 2 or later (the "GPL"),
// http://www.gnu.org/licenses/gpl.html
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
// for the specific language governing rights and limitations under the
// License.
//
// END FLOCK GPL

// CONTENTS OF THIS FILE:
// This file contains the implementation of the LiveJournal service. This
// involves the following components:
//   flockLJService - the service class itself
//   flockLJController - the controller class
//   flockLJServiceFactory - factory object for flockLJService
//   flockLJModule - module object for XPCOM registration
//   flockLJAccount - the account class

const ENABLE_DEBUG = true; // switch to turn off slow debug code for production
function DEBUG(x) { if (ENABLE_DEBUG) debug("flockLivejournalService: "+x+"\n"); }

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;

const LIVEJOURNAL_CID        = Components.ID("{e642271b-1b0d-4d5d-ab51-34de23f75fc7}");
const LIVEJOURNAL_CONTRACTID = "@flock.com/people/livejournal;1";
const LIVEJOURNAL_TITLE      = "LiveJournal Web Service";
const LIVEJOURNAL_FAVICON    = "http://www.livejournal.com/favicon.ico";
const SERVICE_ENABLED_PREF          = "flock.service.livejournal.enabled";
const CATEGORY_COMPONENT_NAME       = "LiveJournal JS Component"
const CATEGORY_ENTRY_NAME           = "livejournal"

const RDFS  = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
const IOS   = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
const RDFCU = Cc["@mozilla.org/rdf/container-utils;1"].getService(Ci.nsIRDFContainerUtils);

const OBS_TOPIC_XPCOMSHUTDOWN  = "xpcom-shutdown";

const FLOCK_NS = "http://flock.com/rdf#";
const RDF_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
const RDF_TYPE = RDFS.GetResource(RDF_NS+"type");
const COOP_TYPE = RDFS.GetResource(FLOCK_NS+"CoopType");

const COOP_CHROME_URL = "chrome://flock/content/common/coop.js";

var gCompTK;
function getCompTK() {
  if (!gCompTK) {
    gCompTK = Components.classes["@flock.com/singleton;1"]
                        .getService(Components.interfaces.flockISingleton)
                        .getSingleton("chrome://browser/content/flock/services/common/load-compTK.js")
                        .wrappedJSObject;
  }
  return gCompTK;
}

var gTimers = [];  // For use with the scheduler

function loadSubScript(spec) {
  var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
  var context = {};
  loader.loadSubScript(spec, context);
  return context;
}

var Coop = loadSubScript(COOP_CHROME_URL).Coop;

var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
loader.loadSubScript("chrome://browser/content/utilityOverlay.js");
loader.loadSubScript("chrome://flock/content/xmlrpc/xmlrpchelper.js");
loader.loadSubScript("chrome://flock/content/blog/blogBackendLib.js");


// String defaults... may be updated later through Web Detective
var gStrings = {
  "domains": "livejournal.com",
  "userlogin": "http://www.livejournal.com/",
  "userprofile": "http://%accountid%.livejournal.com/profile",
  "editprofile": "http://www.livejournal.com/manage/profile/",
  "userblog": "http://%accountid%.livejournal.com/",
  "userfriends": "http://%accountid%.livejournal.com/friends",
  "userphotos": "http://pics.livejournal.com/%accountid%",
  "userpics": "http://www.livejournal.com/allpics.bml?user=%accountid%",
  "postcomment": "http://fixme.com",
  "userfoaf": "http://www.livejournal.com/users/%accountid%/data/foaf"
};

// Helper function for LJ URLs - needs to be updated periodically
function makeLiveJournalURL(aType, aFriendID) {
  switch (aType) {
    case "openProfile":
      return gStrings["userprofile"].replace("%accountid%", aFriendID);
    case "openBlog":
      return gStrings["userblog"].replace("%accountid%", aFriendID);
    case "viewFriends":
      return gStrings["userfriends"].replace("%accountid%", aFriendID);
    case "postComment":
      return gStrings["postcomment"].replace("%accountid%", aFriendID);
    case "openPhotos":
      return gStrings["userphotos"].replace("%accountid%", aFriendID);
    case "userpics":
      return gStrings["userpics"].replace("%accountid%", aFriendID);
    default:
      return gStrings[aType].replace("%accountid%", aFriendID);
  }
}

function userBlogsListener(aListener) {
  this.listener = aListener;
}

userBlogsListener.prototype = {
  // aResult is an Array (simpleEnumerator) of struct objects
  onResult: function UBL_onResult(aResult) {
    var result = new Array();
    for (i=0; i<aResult.length; i++){
      var entry = aResult[i];
      var newAccount = new BlogAccount(entry.blogName);
      newAccount.blogid = entry.blogid;
      newAccount.apiLink = "";
      newAccount.URL = entry.url;
      result.push(newAccount);
    }
    this.listener.onResult(new simpleEnumerator(result));
  },
  onError: function UBL_onError(aErrorCode, aErrorMsg) {
    var error = Cc['@flock.com/error;1'].createInstance(Ci.flockIError);
    error.serviceErrorCode = aErrorCode;
    error.serviceErrorString = aErrorMsg;
    switch (aErrorCode) {
      case 0:
        // Invalid login/pass
        error.errorCode = Ci.flockIError.BLOG_INVALID_AUTH;
        break;
      default:
        // Unknown error code
        error.errorCode = Ci.flockIError.BLOG_UNKNOWN;
    }
    this.listener.onError(null, "", error);
  },
  onFault: function UBL_onFault(aErrorCode, aErrorMsg) {
    var error = Cc['@flock.com/error;1'].createInstance(Ci.flockIError);
    error.serviceErrorCode = aErrorCode;
    error.serviceErrorString = aErrorMsg;
    switch (aErrorCode) {
      case 0:
        // Invalid login/pass
        error.errorCode = Ci.flockIError.BLOG_INVALID_AUTH;
        break;
      default:
        // Unknown error code
        error.errorCode = Ci.flockIError.BLOG_UNKNOWN;
    }
    this.listener.onError(null, "", error);
  }
}


// ================================================
// ========== BEGIN flockLJService class ==========
// ================================================

function flockLJService()
{
  /* Constructors of Adapter Services should ensure that they're
  properly represented in the RDF.
  Question: Should the Services all be children of a Services Root?

  TODO: Generalize this and make it declarative for every service
  TODO: Add proper refresh intervals to all the object constructors,
  */

  this.acUtils = Components.classes["@flock.com/account-utils;1"]
                           .getService(Components.interfaces.flockIAccountUtils);

  this.obs = Components.classes["@mozilla.org/observer-service;1"]
                       .getService(Components.interfaces.nsIObserverService);
  this.obs.addObserver(this, OBS_TOPIC_XPCOMSHUTDOWN, false);

  this.status = Components.interfaces.flockIWebService.STATUS_UNKNOWN;
  this.url = "http://www.livejournal.com";
  this.initialized = false;

  // flockIBlogWebService
  this.supportsCategories = 0;
  this.supportsPostReplace = true;
  this.metadataOverlay = "chrome://browser/content/flock/services/livejournal/ljBlogOverlay.xul";

  this._ctk = {
    interfaces: [
      "nsISupports",
      "nsISupportsCString",
      "nsIClassInfo",
      "nsIObserver",
      "flockIWebService",
      "flockIBlogWebService",
      "flockIManageableWebService",
      "flockIPollingService"
    ],
    shortName: "livejournal",
    fullName: "LiveJournal",
    description: LIVEJOURNAL_TITLE,
    favicon: LIVEJOURNAL_FAVICON,
    CID: LIVEJOURNAL_CID,
    contractID: LIVEJOURNAL_CONTRACTID,
    accountClass: flockLJAccount
  };
  this._profiler = Cc["@flock.com/profiler;1"].getService(Ci.flockIProfiler);

  this.init();
}


// BEGIN nsIObserver interface
flockLJService.prototype.observe =
function flockLJService_observe(aSubject, aTopic, aState)
{
  switch (aTopic) {
    case OBS_TOPIC_XPCOMSHUTDOWN:
      this.obs.removeObserver(this, OBS_TOPIC_XPCOMSHUTDOWN);
      break;
  } // switch
}
// END nsIObserver interface


// BEGIN flockIWebService interface
flockLJService.prototype.addAccountById =
function flockLJService_addAccountById(aAccountID, aIsTransient, aListener)
{
  DEBUG("{flockIWebService}.addAccountById('"+aAccountID+"', "+aIsTransient+", aListener)");
  var ljAccountURN = "urn:flock:lj"+aAccountID;
  var ljAccount = new this.faves_coop.Account(ljAccountURN, {
    name: aAccountID,
    serviceId: LIVEJOURNAL_CONTRACTID,
    service: this.ljService,
    accountId: aAccountID,
    favicon: LIVEJOURNAL_FAVICON,
    URL: 'http://www.livejournal.com/portal',
    isPollable: false,
    isTransient: aIsTransient
  });
  this.account_root.children.addOnce(ljAccount);
  //this.USER = ljAccount.id();
  //var acct = this.getAccount(ljAccount.id());

  this.USER = ljAccountURN;
  // Add the blog(s)
  var ljsvc = this;
  var listener = {
    onResult: function(aResult) {
      ljsvc.logger.info("addAccountById: got the blog list from the server\n");
      var theBlog;
      while (aResult.hasMoreElements()) {
        theBlog = aResult.getNext();
        theBlog.QueryInterface(Components.interfaces.flockIBlogAccount);
        theCoopBlog = new ljsvc.faves_coop.Blog(ljAccountURN+":"+theBlog.title, {
          name: theBlog.title,
          title: theBlog.title,
          blogid: theBlog.blogid,
          URL: theBlog.URL,
          apiLink: 'http://www.livejournal.com/interface/xmlrpc'
        });
        ljAccount.children.addOnce(theCoopBlog);
      }
      if (aListener) aListener.onSuccess(acct, "addAccount");
    },
    onError: function addAcc_listener_onError(aSubject, aStatus, aError) {
      ljsvc.faves_coop.accounts_root.children.remove(ljAccount);
      ljAccount.destroy();
      if (aListener) {
        aListener.onError(aSubject, aStatus, aError);
      }
    }
  }

  this.getUsersBlogs(listener, "");

  acct = this.getAccount(ljAccountURN);
  return acct;
}

flockLJService.prototype.logout =
function flockLJService_logout()
{
  this.acUtils.removeCookies(this.webDetective.getSessionCookies("livejournal"));
  this.USER = null;
}
// END flockIWebService interface


// BEGIN flockIManageableWebService interface
flockLJService.prototype.ownsDocument =
function flockLJService_ownsDocument(aDocument)
{
  DEBUG("{flockIManageableWebService}.ownsDocument(aDocument)");
  aDocument.QueryInterface(Components.interfaces.nsIDOMHTMLDocument);
  var ios = Components.classes["@mozilla.org/network/io-service;1"]
                      .getService(Components.interfaces.nsIIOService);
  var uri = ios.newURI(aDocument.URL, null, null);
  if ((uri.host.indexOf("livejournal.com") == 0) ||
      (uri.host.indexOf(".livejournal.com") > 0))
  {
    if (uri.host.indexOf("adserver.livejournal.com") >= 0) {
      return false;
    } else {
      return true;
    }
  }
  return false;
}

flockLJService.prototype.updateAccountStatusFromDocument =
function flockLJService_updateAccountStatusFromDocument(aDocument)
{
  DEBUG("{flockIManageableWebService}.updateAccountStatusFromDocument()");
  if (this.webDetective.detect("livejournal", "loggedout", aDocument, null)) {
    this.acUtils.markAllAccountsAsLoggedOut(LIVEJOURNAL_CONTRACTID);
  } else if (this.webDetective.detect("livejournal", "loggedin", aDocument, null)) {
    var results = Components.classes["@mozilla.org/hash-property-bag;1"]
                            .createInstance(Components.interfaces.nsIWritablePropertyBag2);
    if (this.webDetective.detect("livejournal", "accountinfo", aDocument, results)) {
      var accountID = results.getPropertyAsAString("accountid");
      if (accountID && accountID.length) {
        var accountURN = this.acUtils.getAccountURNById(this.urn, accountID);
        this.acUtils.ensureOnlyAuthenticatedAccount(accountURN);
        this.USER = accountURN;
      }
    }
  }
}
// END flockIManageableWebService interface


// BEGIN flockIBlogWebService interface
flockLJService.prototype.parseUsersBlogs =
function flockLJService_parseUsersBlogs(listener, inst)
{
  try {
    debug("flockLJService.prototype.parseUsersBlogs\n");
    var result = [];
    var dom = inst._req.responseXML;
    var domEntries = dom.getElementsByTagName("entry");

    if (domEntries.length > 0) { // Regular ATOM feed (ATOM)
      for (var i = 0; i < domEntries.length; i++) {
        debug("One entry...\n");
        domEntry = domEntries[i];
        title = domEntry.getElementsByTagName("title")[0].textContent;
        var newAccount = new BlogAccount(title);
        newAccount.api = this.shortName;
        var links = domEntry.getElementsByTagName("link");
        for (var j = 0; j < links.length; j++) {
          var link = links[j]
          switch (link.getAttribute("rel")) {
            case "alternate":
              newAccount.URL = link.getAttribute("href");
              break;
            case "http://schemas.google.com/g/2005#post":
              newAccount.apiLink = link.getAttribute("href");
              break;
          }
        }
        result.push(newAccount);
      }
    }
    else { // Just a list of links (Livejournal)
      var links = dom.getElementsByTagName("link");
      var result = [];
      for (var i = 0; i < links.length; ++i) {
        var link = links[i];
        var title = link.getAttribute("title");
        var rel = link.getAttribute("rel");
        var href = link.getAttribute("href");
        switch (rel) {
          case "service.post":
          {
            href.match(/.+\/(.+)/);
            var newAccount = new BlogAccount(title);
            newAccount.api = this.shortName;
            newAccount.blogid = RegExp.$1;
            newAccount.apiLink = href;
            newAccount.editLink = href;
            result.push(newAccount);
          }; break;
          case "alternate":
          {
            for (j in result) {
              if (result[j].title == title) {
                result[j].URL = href;
              }
            }
          }; break;
        }
      }
    }
    debug("Found "+ result.length +" blogs\n");
    listener.onResult(new simpleEnumerator(result));
  }
  catch(e) {
    var logger = Components.classes['@flock.com/logger;1']
                           .createInstance(Components.interfaces.flockILogger);
    logger.init("livejournal");
    logger.error(e + " " + e.lineNumber);
    listener.onError(e + " " + e.lineNumber);
  }
}


flockLJService.prototype.newPost =
function flockLJService_newPost(aListener, aBlogId, aPost, aPublish, aNotifications)
{
  var svc = this;

  var coopBlog = this.faves_coop.get(aBlogId);
  var coopAccount = coopBlog.getParent();

  var pw = this.acUtils.getPassword(this.urn+':'+coopAccount.accountId);

  var extra = [];
  if (aPost.extra)
    while (aPost.extra.hasMore())
      extra.push(aPost.extra.getNext());

  var nocomments = (extra[2] == "0") ? false : true;
  var notifsArray = [];

  var date = new Date();
  var hours = date.getHours();
  var month = date.getMonth() + 1;
  var day = date.getDate();
  var year = date.getFullYear();
  var hour = date.getHours();
  var min = date.getMinutes();

  var labels = [];
  if (aPost.tags) {
    while (aPost.tags.hasMore()) {
      labels.push(aPost.tags.getNext());
    }
  }
  var content = aPost.description.replace("</lj>", "", "gi");

  var listener = {
    onResult: function(aResult) {
      aListener.onResult(aResult);
    },
    onError: function(errorcode, errormsg) {
      svc.logger.error("<<<<<<<<<< Livejournal: SERVER TO FLOCK\n");
      svc.logger.error("FAULT "+errorcode+" "+errormsg+"\n");

      var error = Cc['@flock.com/error;1'].createInstance(Ci.flockIError);
      error.serviceErrorCode = errorcode;
      error.serviceErrorString = errormsg;
      switch (errorcode) {
        case 0: // Invalid login/pass
          error.errorCode = Ci.flockIError.BLOG_INVALID_AUTH;
          error.errorString = "Bad authentication";
          break;
        default: // Unknown error code
          error.errorCode = Ci.flockIError.BLOG_UNKNOWN;
          error.errorString = "Unknown Error";
      }
      aListener.onError(error);
    },
    onFault: function(errorcode, errormsg) {
      svc.logger.error("<<<<<<<<<< Livejournal: SERVER TO FLOCK\n");
      svc.logger.error("FAULT "+errorcode+" "+errormsg+"\n");

      var error = Cc['@flock.com/error;1'].createInstance(Ci.flockIError);
      error.serviceErrorCode = errorcode;
      error.serviceErrorString = errormsg;
      switch (errorcode) {
        case 0: // Invalid login/pass
          error.errorCode = Ci.flockIError.BLOG_INVALID_AUTH;
          error.errorString = "Bad authentication";
          break;
        default: // Unknown error code
          error.errorCode = Ci.flockIError.BLOG_UNKNOWN;
          error.errorString = "Unknown Error";
      }
      aListener.onFault(error);
    }
  };

  var xmlrpcServer = new flockXmlRpcServer(coopBlog.apiLink);
  var args = [{username: coopAccount.accountId, password: pw.password,
               subject: aPost.title, event: content,
               security: extra[0], allowmask: extra[1], ver: 1,
               year: year, mon: month, day: day, hour: hour, min: min,
               // props: [{opts_nocomments: nocomments}],
               props: {taglist: labels.join(','), opt_nocomments: nocomments}
               }];
  xmlrpcServer.call("LJ.XMLRPC.postevent", args, listener);
}

flockLJService.prototype.editPost =
function flockLJService_editPost(aListener, aBlogId, aPost, aPublish, aNotifications)
{
  var svc = this;

  var coopBlog = this.faves_coop.get(aBlogId);
  var coopAccount = coopBlog.getParent();

  var pw = this.acUtils.getPassword(this.urn+':'+coopAccount.accountId);

  var labels = [];
  if (aPost.tags) {
    while (aPost.tags.hasMore()) {
      labels.push(aPost.tags.getNext());
    }
  }
  var content = aPost.description.replace("</lj>", "", "gi");
  var extra = [];
  if (aPost.extra) {
    while (aPost.extra.hasMore()) {
      extra.push(aPost.extra.getNext());
    }
  }
  var nocomments = (extra[2] == "0") ? false : true;

  var notifsArray = [];

  var listener = {
    onResult: function(aResult) {
      aListener.onResult(aResult);
    },
    onError: function(error) {
      svc.logger.error("<<<<<<<<<< LiveJournal API: SERVER TO FLOCK\n");
      svc.logger.error("ERROR "+error+"\n");
      aListener.onError(error);
    },
    onFault: function(error) {
      svc.logger.error("<<<<<<<<<< LiveJournal API: SERVER TO FLOCK\n");
      svc.logger.error("FAULT "+error+"\n");
      aListener.onFault(error);
    }
  };

  var xmlrpcServer = new flockXmlRpcServer(coopBlog.apiLink);
  var args = [{username: coopAccount.accountId,
               password: pw.password,
               itemid: aPost.postid,
               subject: aPost.title,
               event: content,
               security: extra[0],
               allowmask: extra[1],
               ver: 1,
               props:
                 {
                   taglist: labels.join(','),
                   opt_nocomments: nocomments
                 }
              }];
  xmlrpcServer.call("LJ.XMLRPC.editevent", args, listener);
}

flockLJService.prototype.deletePost =
function flockLJService_deletePost(aListener, aBlogId, aPostid)
{
  debug("***** flockLJService.deletePost: not supported!\n");
}

flockLJService.prototype.getPost =
function flockLJService(aListener, aBlogId, aPostid)
{
  debug("***** flockLJService.getPost: not supported!\n");
}

flockLJService.prototype.getUsersBlogs =
function flockLJService_getUserBlogs(aListener, aUrl)
{
  var username = this.faves_coop.get(this.USER).name;
  debug("USER: "+username+"\n");
  var pw = this.acUtils.getPassword(this.urn+':'+username);

  var listener = new userBlogsListener(aListener);
  var xmlrpcServer = new flockXmlRpcServer("http://www.livejournal.com/interface/blogger");
  var args = ["flockbrowser", username, pw.password];
  xmlrpcServer.call("blogger.getUsersBlogs", args, listener);
}

flockLJService.prototype.getRecentPosts =
function flockLJService_getRecentPosts(aListener, aBlogId, aNumber)
{
  var svc = this;

  var coopBlog = this.faves_coop.get(aBlogId);
  var coopAccount = coopBlog.getParent();

  var pw = this.acUtils.getPassword(this.urn+':'+coopAccount.accountId);

  var listener = {
    onResult: function(aResult) {
      var result = [];
      svc.logger.info("<<<<<<<<<< LiveJournal API: SERVER TO FLOCK\n");
      for (i=0; i<aResult.length; i++) {
        var post = new BlogPost();
        var content = aResult[i].content;
        if (content.match(/<title.*>(.+?)</)) {
          post.title = RegExp.$1;
        }
        post.issued = aResult[i].dateCreated.getTime();
        post.postid = aResult[i].postId.split(":")[1];
        result.push(post);
      }

      // and return the data source through the listener
      aListener.onResult(new simpleEnumerator(result));
    },
    onError: function(error) {
      svc.logger.error("<<<<<<<<<< LiveJournal API: SERVER TO FLOCK\n");
      svc.logger.error("ERROR "+error+"\n");
      aListener.onError(error);
    },
    onFault: function(code, error) {
      svc.logger.error("<<<<<<<<<< LiveJournal API: SERVER TO FLOCK\n");
      svc.logger.error("FAULT #"+code+": "+error+"\n");
      var flerror = Cc['@flock.com/error;1'].createInstance(Ci.flockIError);
      flerror.serviceErrorCode = code;
      flerror.serviceErrorString = error;
      flerror.errorCode = Ci.flockIError.BLOG_UNKNOWN_ERROR;
      flerror.errorString = "Unknown Error";

      aListener.onFault(flerror);// TODO: bubble up the error to the UI
    }
  }

  var xmlrpcServer = new flockXmlRpcServer("http://www.livejournal.com/interface/blogger");
  // The username is used as the blogid for this call
  var args = ['flockblog', coopAccount.accountId, coopAccount.accountId, pw.password, aNumber];
  xmlrpcServer.call("blogger.getRecentPosts", args, listener);
}

flockLJService.prototype.getCategoryList =
function flockLJService_getCategoryList(aListener, aAccount)
{
  aListener.onResult(new simpleEnumerator([]));
}

flockLJService.prototype.init =
function flockLJService_init()
{
  DEBUG(".init()");
  if (this.initialized) {
    return;
  }

  var evtID = this._profiler.profileEventStart("lj-init");

  this.prefService = Components.classes["@mozilla.org/preferences-service;1"]
                               .getService(Components.interfaces.nsIPrefBranch);
  if ( this.prefService.getPrefType(SERVICE_ENABLED_PREF) &&
       !this.prefService.getBoolPref(SERVICE_ENABLED_PREF) )
  {
    DEBUG("Pref "+SERVICE_ENABLED_PREF+" set to FALSE... not initializing.");
    var catMgr = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
    catMgr.deleteCategoryEntry("wsm-startup", CATEGORY_COMPONENT_NAME, true);
    catMgr.deleteCategoryEntry("flockWebService", CATEGORY_ENTRY_NAME, true);
    return;
  }

  // Logger
  this.logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
  this.logger.init("livejournal");

  this.faves = RDFS.GetDataSourceBlocking('rdf:flock-favorites');
  this.faves_coop = Cc["@flock.com/singleton;1"]
                    .getService(Ci.flockISingleton)
                    .getSingleton("chrome://flock/content/common/load-faves-coop.js")
                    .wrappedJSObject;
  this.account_root = this.faves_coop.accounts_root;

  this.ljService = new this.faves_coop.Service(
  'urn:livejournal:service', {
    name: 'livejournal',
    desc: 'The LiveJournal Service'
  });
  this.ljService.serviceId = LIVEJOURNAL_CONTRACTID;

  // Load Web Detective file
  this.webDetective = this.acUtils.useWebDetective("livejournal.xml");
  for (var s in gStrings) {
    gStrings[s] = this.webDetective.getString("livejournal", s, gStrings[s]);
  }
  this.ljService.domains = gStrings["domains"];
  this.ljService.loginURL = gStrings["userlogin"];

  this.urn = this.ljService.id();

  // Update auth states
  if (this.webDetective.detectCookies("livejournal", "loggedout", null)) {
    this.acUtils.markAllAccountsAsLoggedOut(LIVEJOURNAL_CONTRACTID);
  }

  this.initialized = true;

  this._profiler.profileEventEnd(evtID, "");
}

// END flockIBlogWebService interface

flockLJService.prototype.refresh =
function flockLJService_refresh(aURN, aListener)
{
}

// ========== END flockLJService class ==========




// ========== BEGIN XPCOM Module support ==========

function createModule(aParams) {
  return {
    registerSelf: function (aCompMgr, aFileSpec, aLocation, aType) {
      aCompMgr = aCompMgr.QueryInterface(Ci.nsIComponentRegistrar);
      aCompMgr.registerFactoryLocation( aParams.CID, aParams.componentName,
                                        aParams.contractID, aFileSpec,
                                        aLocation, aType );
      var catMgr = Cc["@mozilla.org/categorymanager;1"]
        .getService(Ci.nsICategoryManager);
      if (!aParams.categories) { aParams.categories = []; }
      for (var i = 0; i < aParams.categories.length; i++) {
        var cat = aParams.categories[i];
        catMgr.addCategoryEntry( cat.category, cat.entry,
                                 cat.value, true, true );
      }
    },
    getClassObject: function (aCompMgr, aCID, aIID) {
      if (!aCID.equals(aParams.CID)) { throw Cr.NS_ERROR_NO_INTERFACE; }
      if (!aIID.equals(Ci.nsIFactory)) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; }
      return { // Factory
        createInstance: function (aOuter, aIID) {
          if (aOuter != null) { throw Cr.NS_ERROR_NO_AGGREGATION; }
          var comp = new aParams.componentClass();
          if (aParams.implementationFunc) { aParams.implementationFunc(comp); }
          return comp.QueryInterface(aIID);
        }
      };
    },
    canUnload: function (aCompMgr) { return true; }
  };
}

// NS Module entrypoint
function NSGetModule(aCompMgr, aFileSpec) {
  return createModule({
    componentClass: flockLJService,
    CID: LIVEJOURNAL_CID,
    contractID: LIVEJOURNAL_CONTRACTID,
    componentName: CATEGORY_COMPONENT_NAME,
    implementationFunc: function (aComp) { getCompTK().addAllInterfaces(aComp); },
    categories: [
      { category: "wsm-startup", entry: CATEGORY_COMPONENT_NAME, value: LIVEJOURNAL_CONTRACTID },
      { category: "flockWebService", entry: CATEGORY_ENTRY_NAME, value: LIVEJOURNAL_CONTRACTID }
    ]
  });
}

// ========== END XPCOM module support ==========



// ================================================
// ========== BEGIN flockLJAccount class ==========
// ================================================

function flockLJAccount()
{
  this.acUtils = Components.classes["@flock.com/account-utils;1"]
                           .getService(Components.interfaces.flockIAccountUtils);
  this.service = Components.classes[LIVEJOURNAL_CONTRACTID]
                           .getService(Components.interfaces.flockIWebService);
  this.faves = RDFS.GetDataSourceBlocking("rdf:flock-favorites");
  this._coop = Components.classes['@flock.com/singleton;1']
                         .getService(Components.interfaces.flockISingleton)
                         .getSingleton("chrome://flock/content/common/load-faves-coop.js")
                         .wrappedJSObject;
  this._ctk = {
    interfaces: [
      "nsISupports",
      "flockIWebServiceAccount",
      "flockIBlogWebServiceAccount"
    ]
  };
  getCompTK().addAllInterfaces(this);
}

// BEGIN flockIWebServiceAccount interface
flockLJAccount.prototype.activate =
function flockLJAccount_activate(aListener)
{
  DEBUG("{flockIWebServiceAccount}.activate()");
  var acctCoopObj = this._coop.get(this.urn);
  acctCoopObj.isPollable = true;
  this.acUtils.ensureOnlyAuthenticatedAccount(this.urn);
}
// END flockIWebServiceAccount interface


// BEGIN flockIBlogWebServiceAccount interface
flockLJAccount.prototype.getBlogs =
function flockLJAccount_getBlogs()
{
  DEBUG("{flockIBlogWebServiceAccount}.getBlogs()");
  var blogsEnum = {
    QueryInterface: function(iid) {
      if (!iid.equals(Components.interfaces.nsISupports) &&
          !iid.equals(Components.interfaces.nsISimpleEnumerator))
      {
        throw Components.results.NS_ERROR_NO_INTERFACE;
      }
      return this;
    },
    hasMoreElements: function() {
      return false;
    },
    getNext: function() {
    }
  };
  return blogsEnum;
}
// END flockIBlogWebServiceAccount interface

// ========== END flockLJAccount class ==========
