// BEGIN FLOCK GPL
//
// Copyright Flock Inc. 2005-2009
// 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

const CC = Components.classes;
const CI = Components.interfaces;
const CU = Components.utils;
const CR = Components.results;

CU.import("resource:///modules/FlockXPCOMUtils.jsm");
FlockXPCOMUtils.debug = false;
CU.import("resource:///modules/FlockSvcUtils.jsm");
CU.import("resource:///modules/FlockStringBundleHelpers.jsm");

const BLOGGER_CID = Components.ID('{dec6797b-2155-4a79-9872-30f142074f0d}');
const BLOGGER_CONTRACTID = '@flock.com/people/blogger;1';
const BLOGGER_FAVICON = "chrome://flock/content/services/blogger/favicon.png";
const SERVICE_ENABLED_PREF          = "flock.service.blogger.enabled";
const CATEGORY_COMPONENT_NAME       = "Blogger JS Component";
const CLASS_NAME = "Blogger";
const CLASS_SHORT_NAME = "blogger";
const BLOGGER_TITLE = "Google's Blogger.com Web Service";
const MODULE_NAME = "blogger";

/*************************************************************************
 * Component: Flock Blogger Service
/*************************************************************************/

function flockBLService() {
  var loader = CC["@mozilla.org/moz/jssubscript-loader;1"]
               .getService(CI.mozIJSSubScriptLoader);
  loader.loadSubScript('chrome://browser/content/utilityOverlay.js');
  loader.loadSubScript("chrome://flock/content/blog/atom.js");
  loader.loadSubScript("chrome://flock/content/blog/blogBackendLib.js");

  this.supportsPostReplace = true;
  this.mIsInitialized = false;

  this._profiler = CC["@flock.com/profiler;1"].getService(CI.flockIProfiler);

  this._accountClassCtor = flockBLAccount;

  var ws = FlockSvcUtils.flockIWebService;
  ws.addDefaultMethod(this, "url");
  ws.addDefaultMethod(this, "isHidden");
  ws.addDefaultMethod(this, "getStringBundle");

  var lws = FlockSvcUtils.flockILoginWebService;
  lws.addDefaultMethod(this, "loginURL");
  lws.addDefaultMethod(this, "tooltip");
  lws.addDefaultMethod(this, "getAccount");
  lws.addDefaultMethod(this, "getAuthenticatedAccount");
  lws.addDefaultMethod(this, "getAccountUrns");
  lws.addDefaultMethod(this, "removeAccount");
  lws.addDefaultMethod(this, "ownsLoginForm");
  lws.addDefaultMethod(this, "getSessionValue");
  lws.addDefaultMethod(this, "getCredentialsFromForm");
  lws.addDefaultMethod(this, "getAccountIDFromDocument");

  this.init();
}

/*************************************************************************
 * flockBLService: XPCOM Component Creation
 *************************************************************************/

flockBLService.prototype = new FlockXPCOMUtils.genericComponent(
  BLOGGER_TITLE,
  BLOGGER_CID,
  BLOGGER_CONTRACTID,
  flockBLService,
  CI.nsIClassInfo.SINGLETON,
  [
    CI.flockIWebService,
    CI.flockILoginWebService,
    CI.flockIBlogWebService,
    CI.flockIMigratable
  ]
);

// FlockXPCOMUtils.genericModule() categories
flockBLService.prototype._xpcom_categories = [
  { category: "wsm-startup" },
  { category: "flockWebService", entry: CLASS_SHORT_NAME },
  { category: "flockMigratable", entry: CLASS_SHORT_NAME }
];


/*************************************************************************
 * flockBLService: Private members
 *************************************************************************/

// For Google Auth (ClientLogin)
flockBLService.prototype._parseError =
function flockBLService__parseError(aErrorString) {
  var result = {};
  var lines = aErrorString.split('\n');
  for (i in lines) {
    if (lines[i].length > 0) {
      entry = lines[i].split('=');
      result[entry[0]] = entry[1];
    }
  }

  return result;
};

// Migrate coop objects
flockBLService.prototype._migrateFromCoop =
function flockBLService__migrateFromCoop(aContext) {
  this._logger.debug("_migrateFromCoop(...)");
  if (aContext.majorVersion < 2) {
    // 2.0 changes
    var accountUrns = this.getAccountUrns();
    while (accountUrns.hasMore()) {
      var account = this.getAccount(accountUrns.getNext());
      if (account instanceof CI.flockIWebServiceAccount) {
        // Migrate old coop urn (USER@gmail.com or user@googlemail.com) to
        // new urn (USER)
        var urn = account.urn.match(/(.+:(.+))@g(?:|oogle)mail\.com/);
        if (urn) {
          // Elements 0, 1 and 2 are valid if urn is non-null
          // Format:
          //   urn[0]: urn:flock:<service>:account:<accountId>@gmail.com
          //   urn[1]: urn:flock:<service>:account:<accountId>
          //   urn[2]: <accountId>
          this._logger.debug("  urn[0]: " + urn[0] + "\n  urn[1]: " + urn[1]
                             + "\n  urn[2]: " + urn[2]);
          var coopAccount = this._coop.get(urn[0]);
          coopAccount.accountId = urn[2];
          coopAccount.changeId(urn[1]);

          // Update the Blog child nodes so we don't create RDF orphans
          var childrenEnum = coopAccount.children.enumerate();
          while (childrenEnum.hasMoreElements()) {
            var child = childrenEnum.getNext();
            if (child.isInstanceOf(this._coop.Blog)) {
              var oldId = child.id();
              var newId = oldId.replace(/@g(?:|oogle)mail\.com/, "");
              if (oldId != newId) {
                this._logger.debug("Migrating '" + oldId
                                   + "' to '" + newId + "'\n");
                this._coop.get(oldId).changeId(newId);
              }
            }
          }

          // Update the urn entry in the Password Manager
          var oldAcctId = urn[0].replace(/^urn:flock:blogger:account:/, "");
          var pw = this._acUtils.removePassword(this.urn + ":" + oldAcctId);
          if (pw) {
            this._acUtils.setPassword(this.urn + ":" + urn[2],
                                     pw.username,
                                     pw.password);
          }
        }
      }
    }
  }
  // Makes this a generator-iterator
  yield true;
};

/*************************************************************************
 * flockBLService: flockIWebService implementation
 *************************************************************************/

// readonly attribute AString contractId;
// ALMOST duplicated by nsIClassInfo::contractID
// with different case: contractId != contractID
flockBLService.prototype.contractId = BLOGGER_CONTRACTID;

// readonly attribute AString icon;
flockBLService.prototype.icon = BLOGGER_FAVICON;

// readonly attribute boolean needPassword;
flockBLService.prototype.needPassword = true;

// readonly attribute AString shortName;
flockBLService.prototype.shortName = CLASS_SHORT_NAME;

// readonly attribute AString title;
flockBLService.prototype.title = CLASS_NAME;

// readonly attribute AString urn;
flockBLService.prototype.urn = "urn:" + CLASS_SHORT_NAME + ":service";

flockBLService.prototype.addAccount =
function flockBLService_addAccount(aAccountId, aIsTransient, aFlockListener) {
  this._logger.debug("addAccount('" + aAccountId + "', "
                     + aIsTransient + ", aFlockListener)");

  var pw = this._acUtils.getPassword(this.urn + ":" + aAccountId);
  var user = pw ? pw.username : aAccountId;
  var acctId = aAccountId.replace(/@g(?:|oogle)mail\.com$/, "");
  var accountURN =
    this._acUtils.createAccount(this, acctId, user, null, aIsTransient);
  this.USER = accountURN;

  var account = this.getAccount(accountURN);
  account.setParam("service", this._coopObj);

  // Add the blog account
  blsvc = this;
  var blogListener = {
    onResult: function bl_onResult(aSimpleEnum) {
      var theBlog;
      while (aSimpleEnum.hasMoreElements()) {
        theBlog = aSimpleEnum.getNext();
        theBlog.QueryInterface(CI.nsIPropertyBag2);
        var title = theBlog.getPropertyAsAString("title");
        var URL = theBlog.getPropertyAsAString("URL");
        var APILink = theBlog.getPropertyAsAString("APILink");
        var authToken = theBlog.getPropertyAsAString("authToken");
        var blogURN = accountURN + ":" + title;
        theCoopBlog = new blsvc._coop.Blog(blogURN, {
          name: title,
          title: title,
          blogid: "",
          URL: URL,
          apiLink: APILink,
          authtoken: authToken
        });
        account.coopObj.children.addOnce(theCoopBlog);
      }
      if (aFlockListener) {
        aFlockListener.onSuccess(account, "addAccount");
      }
    },
    onFault: function bl_onFault(aFlockError) {
      account.coopObj.destroy();
      if (aFlockListener) {
        aFlockError.errorCode = CI.flockIError.BLOG_UNKNOWN_ERROR;
        aFlockListener.onError(aFlockError, "FAULT");
      }
    },
    onError: function bl_onError(aFlockError) {
      account.coopObj.destroy();
      if(aFlockListener) {
        aFlockListener.onError(aFlockError, "ERROR");
      }
    }
  }

  var apiLink = "http://www.blogger.com/feeds/default/blogs";
  this.getUsersBlogs(blogListener, apiLink, acctId, pw.password);

  return account;
};

flockBLService.prototype.init =
function flockBLService_init() {
  FlockSvcUtils.getLogger(this);
  this._logger.init("blogger");
  this._logger.debug(".init()");

  // Prevent re-entry
  if (this.mIsInitialized) return;
  this.mIsInitialized = true;

  var evtID = this._profiler.profileEventStart("blogger-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) )
  {
    this._logger.info("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("flockMigratable", CATEGORY_ENTRY_NAME, true);
    catMgr.deleteCategoryEntry("flockWebService", CATEGORY_ENTRY_NAME, true);
    return;
  }

  // getWD() also adds the "_acUtils" property.
  FlockSvcUtils.getWD(this);
  this.domains = "google.com,blogger.com";
  FlockSvcUtils.initService(this);
  this._coopObj.logoutOption = false;

  // Initialize member that specifies path for localized string bundle
  this._stringBundlePath = "";

  // Load auth manager
  this._authMgr = CC["@flock.com/authmgr;1"]
                  .getService(CI.flockIAuthenticationManager);
  this._authMgr.joinNetwork(BLOGGER_CONTRACTID, "google");

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


/*************************************************************************
 * flockLJService: flockIBlogService implementation
 *************************************************************************/

flockBLService.prototype.supportsCategories = CI.flockIBlogWebService.CATEGORIES_NO;

flockBLService.prototype.metadataOverlay = null;

flockBLService.prototype.authtoken = null;

flockBLService.prototype.setPostCategories =
function flockBLService_setPostCategories(aPublishListener,
                                          aBlogId,
                                          aPostId,
                                          aCategories) {
  inst._logger.error("Blogger doesn't support categories!");
  throw(CR.NS_ERROR_NOT_IMPLEMENTED);
};


function atomBlogListener(aPublishListener) {
  FlockSvcUtils.getLogger(this).init("atomBlogListener");
  this.publishListener = aPublishListener;
}

atomBlogListener.prototype = {
  onResult: function atomBlogListener_onResult(aResult) {
    this.publishListener.onResult(aResult.atomid);
  },
  onError: function atomBlogListener_onError(aError) {
    this._logger.error("<<<<<<<<<< Google API: SERVER TO FLOCK");
    this._logger.error("ERROR " + aError);
    this.publishListener.onError(aError);
  },
  onFault: function atomBlogListener_onFault(aError) {
    this._logger.error("<<<<<<<<<< Google API: SERVER TO FLOCK");
    this._logger.error("FAULT " + aError);
    this.publishListener.onFault(aError);
  }
};

flockBLService.prototype.doAuthRequest =
function flockBLService_doAuthRequest(aBlogListener,
                                      method,
                                      url,
                                      body,
                                      processor)
{
  var inst = this;
  this._req = CC['@mozilla.org/xmlextras/xmlhttprequest;1']
              .createInstance(CI.nsIXMLHttpRequest);
  this._req.onreadystatechange = function doAuthRequest_ORSC(aEvt) {
    inst._logger.info("<<<<<<<<<< Google API: SERVER TO FLOCK");
    inst._logger.info("Request readyState: " + inst._req.readyState);
    if (inst._req.readyState == 4) {
      var status = inst._req.status;
      inst._logger.info("Request status: " + inst._req.status);
      inst._logger.debug("\nRESPONSE\n" + inst._req.responseText);
      if (status == 200 || status == 201 || status == 205) {
        try {
          processor(aBlogListener, inst);
        } catch (e) {
          inst._logger.error(e + " " + e.lineNumber);
        }
      } else {
        inst._logger.error("Error: " + inst._req.responseText);
        var answer = inst._parseError(inst._req.responseText);
        var error = CC['@flock.com/error;1'].createInstance(CI.flockIError);
        error.serviceErrorString = answer["Error"];
        inst._logger.error("***" + error.serviceErrorString + "***");
        switch (error.serviceErrorString) {
          case "BadAuthentication": // Invalid login/pass
            error.errorCode = CI.flockIError.BLOG_INVALID_AUTH;
            break;
          case "CaptchaRequired":
            error.errorCode = CI.flockIError.BLOG_CAPTCHA_REQUIRED;
            break;
           case "NotVerified":
            error.errorCode = CI.flockIError.BLOG_NOT_VERIFIED;
            break;           
          default: // Unknown error code
            error.errorCode = CI.flockIError.BLOG_UNKNOWN_ERROR;
        }
        aBlogListener.onError(error);
      }
    }
  };
  rval = this._req.open(method, url, true);
  this._req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  this._logger.info(">>>>>>>>>> Google API: FLOCK TO SERVER");
  this._logger.info("\nSENDING\n" + body);
  this._logger.info("\nTO\n" + method + " @ " + url);
  rval = this._req.send(body);
};

flockBLService.prototype.doRequest =
function flockBLService_doRequest(aBlogListener,
                                  method,
                                  url,
                                  body,
                                  authToken,
                                  processor) 
{
  var inst = this;
  this._req = CC['@mozilla.org/xmlextras/xmlhttprequest;1']
              .createInstance(CI.nsIXMLHttpRequest);
  this._req.onreadystatechange = function doRequest_ORSC(aEvt) {
    inst._logger.info("<<<<<<<<<< Google API: SERVER TO FLOCK");
    inst._logger.info("Request readyState: " + inst._req.readyState);
    if (inst._req.readyState == 4) {
      inst._logger.info("\nRESPONSE\n" + inst._req.responseText);
      var status = inst._req.status;
      if (status == 200 || status == 201 || status == 205) {
        try {
          processor(aBlogListener, inst, authToken);
        } catch(e) {
          inst._logger.error(e + " " + e.lineNumber);
        }
      } else {
        var faultString = inst._req.responseText;
        inst._logger.error(faultString);
        aBlogListener.onFault(faultString);
      }
    }
  };
  rval = this._req.open(method, url, true);
  this._req.setRequestHeader("Authorization", "GoogleLogin auth="+authToken);
  this._logger.info(">>>>>>>>>> Google API: FLOCK TO SERVER");
  this._logger.info("\nSENDING\n" + body);
  this._logger.info("\nTO\n" + method + " @ " + url);
  rval = this._req.send(body);
};

flockBLService.prototype.parseUsersBlogs =
function flockBLService_parseUsersBlogs(aBlogListener, inst, authToken) {
  try {
    var result = new Array();
    var dom = inst._req.responseXML;

    var domEntries = dom.getElementsByTagName("entry");
    for (i=0; i<domEntries.length; i++) {
      domEntry = domEntries[i];
      title = domEntry.getElementsByTagName("title")[0].textContent;
      var blog = CC["@mozilla.org/hash-property-bag;1"]
                 .createInstance(CI.nsIWritablePropertyBag2);
      blog.setPropertyAsAString("title", title);
      blog.setPropertyAsAString("api", this.shortName);
      blog.setPropertyAsAString("authToken", authToken);
      var links = domEntry.getElementsByTagName("link");
      for (j=0; j<links.length; j++) {
        var link = links[j]
        switch (link.getAttribute("rel")) {
          case "alternate":
            blog.setPropertyAsAString("URL", link.getAttribute("href"));
            break;
          case "http://schemas.google.com/g/2005#post":
            blog.setPropertyAsAString("APILink", link.getAttribute("href"));
            break;
        }
      }
      result.push(blog);
    }
    debug("Found "+ result.length +" blogs\n");
    aBlogListener.onResult(new simpleEnumerator(result));
  }
  catch(e) {
    var logger = CC['@flock.com/logger;1']
       .createInstance(CI.flockILogger);
    logger.init("blog");
    logger.error(e + " " + e.lineNumber);
    aBlogListener.onError(e + " " + e.lineNumber);
  }
};

flockBLService.prototype.parseRecentPosts =
function flockBLService_parseRecentPosts(aBlogListener, inst) {
  var getNamedChild = function(node, name) {
    for(var i=0;i<node.childNodes.length;++i) {
      if(node.childNodes[i].nodeName==name)
        return node.childNodes[i];
    }
    return null;
  };

  var result = new Array();

  debug(inst._req.responseText);

  var dom = inst._req.responseXML;
  var entries = dom.getElementsByTagName("entry");
  for(var i=0;i<entries.length;++i) {
    try {
      var entry_n = entries[i];
      var post = new BlogPost();
      post.title = getNamedChild(entry_n, "title").firstChild.nodeValue;
      post.issued = getNamedChild(entry_n, "published").firstChild.nodeValue;

      var atomid_n = entry_n.getElementsByTagName("id")[0];
      post.postid = "";
      if(atomid_n) post.postid = atomid_n.firstChild.nodeValue; //.split('/').pop();

      var link_n = null;
      for(var j=0;j<entry_n.childNodes.length;++j) {
        if(entry_n.childNodes[j].nodeName=="link") {
          var tmp = entry_n.childNodes[j];
          if(tmp.getAttribute("rel").match(/edit/)) {
            link_n = tmp;
          }
          if(tmp.getAttribute("rel").match(/alternate/)) {
            permalink_n = tmp;
          }
        }
      }

      var permaLink = permalink_n.getAttribute("href");
      var href = link_n.getAttribute("href");
      // href.match(/.+\/(.+)/);
      post.editURI = href; // RegExp.$1;
      debug("post.editURI: "+post.editURI+"\n");

      result.push(post);
    }
    catch(e) {
      var logger = CC['@flock.com/logger;1']
        .createInstance(CI.flockILogger);
      logger.error(e + " " + e.lineNumber + " " + e.fileName);
    }
  }
  aBlogListener.onResult(new simpleEnumerator(result));
};

flockBLService.prototype.newPost =
function flockBLService_newPost(aPublishListener,
                                aBlogId,
                                aPost,
                                aPublish,
                                aNotifications)
{
  var svc = this;
  var gBlogService = CC['@flock.com/flock-blog;1']
                     .getService(CI['flockIBlogService']);
  var account = gBlogService.getAccount(aBlogId).wrappedJSObject;

  var blogListener = {
    onResult: function newPost_onResult(aResult) {
      aPublishListener.onResult(aResult.link);
    },
    onError: function newPost_onError(aError) {
      svc._logger.error("<<<<<<<<<< Google API: SERVER TO FLOCK");
      svc._logger.error("ERROR " + aError.serviceErrorString);
      aPublishListener.onError(aError);
    },
    onFault: function newPost_onFault(aError) {
      svc._logger.error("<<<<<<<<<< Google API: SERVER TO FLOCK");
      svc._logger.error("FAULT " + aError.serviceErrorString);
      if (aError.serviceErrorCode == 401) {
        // The token is bad or missing, let's get a brand new one
        var parseAuthToken = function (aBlogListener, inst) {
          var response = inst._req.responseText;
          response.match(/Auth=(.+)/);
          var token = RegExp.$1;
          var coopBlog = svc._coop.get(aBlogId);
          coopBlog.authtoken = token;
          svc.newPost(aPublishListener, aBlogId, aPost, aPublish, aNotifications);
        };
        var body = "Email="
                 + encodeURIComponent(account.username)
                 + "&Passwd="
                 + encodeURIComponent(account.password)
                 + "&service=blogger&source=FlockInc-Flock-0.8";
        var url = "https://www.google.com/accounts/ClientLogin";
        svc.doAuthRequest(blogListener, "POST", url, body, parseAuthToken);
      }
      else
        aListener.onFault(aError);
    }
  }

  var atomEntry = {
    title: aPost.title,
    content: aPost.description
  };
  var labels = new Array();
  if (aPost.tags)
    while (aPost.tags.hasMore()) {
      var label = aPost.tags.getNext();
      if (label.length > 0)
        labels.push(label);
    }
  if (labels.length > 0)
    atomEntry.categories = labels;
  // Hack because Blogger announce www but takes beta
  var url = account.apiLink.replace('www', 'beta');
  flockAtomPost(blogListener, url, atomEntry, account.authtoken);
};

flockBLService.prototype.editPost =
function flockBLService_editPost(aPublishListener,
                                 aBlogId,
                                 aPost,
                                 aPublish,
                                 aNotifications)
{
  var gBlogService = CC['@flock.com/flock-blog;1']
                     .getService(CI['flockIBlogService']);
  var account = gBlogService.getAccount(aBlogId).wrappedJSObject;

  if (account.username) { // Blogger beta
    var blogListener = new atomBlogListener(aPublishListener);
    var account = gBlogService.getAccount(aBlogId).wrappedJSObject;

    var atomEntry = {
      id: aPost.postid,
      title: aPost.title,
      content: aPost.description,
      issued: aPost.issued
    };
    var labels = new Array();
    while (aPost.tags.hasMore())
      labels.push(aPost.tags.getNext());
    if (labels.length > 0)
      atomEntry.categories = labels;
    // Hack because Blogger announce www but takes beta
    var url = aPost.editURI.replace('www', 'beta');
    flockAtomEdit(blogListener, url, atomEntry, account.authtoken);
  }
  else {
    var atomAPI = CC['@flock.com/blog/service/atom;1']
                  .getService(CI.flockIBlogWebService);
    atomAPI.editPost(aPublishListener, aBlogId, aPost, aPublish, aNotifications);
  }
};

flockBLService.prototype.deletePost =
function flockBLService_deletePost(aBlogListener, aBlogId, aPostid) {
  var gBlogService = CC['@flock.com/flock-blog;1']
                     .getService(CI['flockIBlogService']);
  var account = gBlogService.getAccount(aBlogId).wrappedJSObject;

  if (account.username) { // Blogger beta
    var handleDelete = function(listener, inst) {
      listener.onResult(1);
    }

    var gBlogService = CC['@flock.com/flock-blog;1']
                       .getService(CI['flockIBlogService']);
    var account = gBlogService.getAccount(aBlogId).wrappedJSObject;
    var url = account.apiLink;
    url += "/" + aPostid;
    // if(aEditURI) url = aEditURI;
    this.doRequest(aBlogListener, "DELETE", url, null,
                   account.authtoken, handleDelete);
  }
  else {
    var atomAPI = CC['@flock.com/blog/service/atom;1']
                  .getService(CI.flockIBlogWebService);
    atomAPI.deletePost(aBlogListener, aBlogId, aPostid);
  }
};

flockBLService.prototype.getUsersBlogs =
function flockBLService_getUsersBlogs(aBlogListener,
                                      aAPILink,
                                      aUsername,
                                      aPassword)
{
  var inst = this;

  var parseAuthToken = function (listener, inst){
    var response = inst._req.responseText;
    response.match(/Auth=(.+)/);
    var token = RegExp.$1;
    debug("Found the token: "+token+"\n");
    inst.doRequest(aBlogListener, "GET", aAPILink, null,
                   token, inst.parseUsersBlogs);
  };

  var body = "Email="
           + encodeURIComponent(aUsername)
           + "&Passwd="
           + encodeURIComponent(aPassword)
           + "&service=blogger&source=FlockInc-Flock-1.2";
  var url = "https://www.google.com/accounts/ClientLogin";
  this.doAuthRequest(aBlogListener, "POST", url, body, parseAuthToken);
};

flockBLService.prototype.getRecentPosts =
function flockBLService_getRecentPosts(aBlogListener, aBlogId, aNumber) {
  var gBlogService = CC['@flock.com/flock-blog;1']
                     .getService(CI['flockIBlogService']);
  var account = gBlogService.getAccount(aBlogId).wrappedJSObject;

  var url = account.apiLink;
  url.match(/(.+\/)(.+)/);
  if (RegExp.$2 == "post") {
    url = RegExp.$1 + "feed";
  }
  
  var blogListener = {
    onResult: function getRecentPosts_onResult(aResult) {
      aBlogListener.onResult(aResult);
    },
    onError: function getRecentPosts_onError(aError) {
      svc._logger.error("<<<<<<<<<< Google API: SERVER TO FLOCK");
      svc._logger.error("ERROR " + aError.serviceErrorString);
      aBlogListener.onError(aError);
    },
    onFault: function getRecentPosts_onFault(aError) {
      svc._logger.error("<<<<<<<<<< Google API: SERVER TO FLOCK");
      svc._logger.error("FAULT " + aError.serviceErrorString);
      if (aError.serviceErrorCode == 401) {
        // The token is bad or missing, let's get a brand new one
        var parseAuthToken = function (listener, inst){
          var response = inst._req.responseText;
          response.match(/Auth=(.+)/);
          var token = RegExp.$1;
          var coopBlog = svc._coop.get(aBlogId);
          coopBlog.authtoken = token;
          inst.doRequest(blogListener, "GET", url, null,
                         token, inst.parseRecentPosts);
        };
        var body = "Email="
                 + encodeURIComponent(account.username)
                 + "&Passwd="
                 + encodeURIComponent(account.password)
                 + "&service=blogger&source=FlockInc-Flock-0.8";
        var url = "https://www.google.com/accounts/ClientLogin";
        inst.doAuthRequest(blogListener, "POST", url, body, parseAuthToken);
      } else {
        aBlogListener.onFault(aError);
      }
    }
  }
  
  this.doRequest(blogListener, "GET", url, null,
                 account.authtoken, this.parseRecentPosts);
};

flockBLService.prototype.getCategoryList =
function flockBLService_getCategoryList(aBlogListener, aBlogId) {
  aBlogListener.onResult(null);
};


/*************************************************************************
 * flockBLService: flockILoginWebService implementation
 *************************************************************************/

flockBLService.prototype.docRepresentsSuccessfulLogin =
function flockBLService_docRepresentsSuccessfulLogin(aDocument) {
  this._logger.debug(".docRepresentsSuccessfulLogin(...)");
  aDocument.QueryInterface(Components.interfaces.nsIDOMHTMLDocument);
  return this._WebDetective.detect("blogger", "loggedin", aDocument, null);
};

flockBLService.prototype.ownsDocument =
function flockBLService_ownsDocument(aDocument) {
  this._logger.debug(".ownsDocument(...)");
  aDocument.QueryInterface(Components.interfaces.nsIDOMHTMLDocument);
  return this._WebDetective.detect("blogger", "ownsDocument", aDocument, null);
};

flockBLService.prototype.updateAccountStatusFromDocument =
function flockBLService_updateAccountStatusFromDocument(aDocument,
                                                        aAccount,
                                                        aFlockListener)
{
  if (aAccount) {
    // We're logged in to this account, but we still need to grab the user's
    // avatar
    this._authMgr.reportCompleteLoginToNetwork(aAccount);
    this._acUtils.ensureOnlyAuthenticatedAccount(aAccount);
    var results = CC["@mozilla.org/hash-property-bag;1"]
                  .createInstance(CI.nsIWritablePropertyBag2);
    if (this._WebDetective
            .detect("blogger", "accountinfo", aDocument, results))
    {
      var avatarURL = null;
      try {
        avatarURL = results.getPropertyAsAString("avatarURL");
      } catch(e) {
        this._logger.debug("No avatar found");
      }
      if (avatarURL) {
        aAccount.setParam("avatar", avatarURL);
      }
    }
  } else if (this._WebDetective
                 .detect("blogger", "loggedout", aDocument, null))
  {
    // We logged out (of all accounts)
    this._acUtils.markAllAccountsAsLoggedOut(this);
    this._authMgr.reportCompleteLogoutFromNetwork(this.contractId);
  }
};

flockBLService.prototype.logout =
function flockBLService_logout() {
  this._logger.debug(".logout()");
  this._acUtils.markAllAccountsAsLoggedOut(this);
  var cookieManager = Components.classes["@mozilla.org/cookiemanager;1"]
                                .getService(Components.interfaces.nsICookieManager);
  cookieManager.remove(".blogger.com", "blogger_SID", "/", false);
  cookieManager.remove(".blogger.com", "B2I", "/", false);
  cookieManager.remove(".blogger.com", "ServerID", "/", false);
  cookieManager.remove(".blogger.com", "S", "/", false);
  cookieManager.remove(".blogger.com", "__utma", "/", false);
  cookieManager.remove(".blogger.com", "__utmb", "/", false);
  cookieManager.remove(".blogger.com", "__utmc", "/", false);
  cookieManager.remove(".blogger.com", "__utmz", "/", false);
  cookieManager.remove(".blogger.com", "NSC_cmphhfs-fyu", "/", false);
  cookieManager.remove("blogger.com", "NSC_cmphhfs-fyu", "/", false);
  cookieManager.remove("www.blogger.com", "NSC_cmphhfs-fyu", "/", false);
  cookieManager.remove("www.blogger.com", "JSESSIONID", "/", false);
  cookieManager.remove(".www2.blogger.com", "__utma", "/", false);
  cookieManager.remove(".www2.blogger.com", "__utmb", "/", false);
  cookieManager.remove(".www2.blogger.com", "__utmc", "/", false);
  cookieManager.remove(".www2.blogger.com", "__utmz", "/", false);
  cookieManager.remove("www2.blogger.com", "S", "/", false);
  cookieManager.remove("www.google.com", "LSID", "/accounts", false);
};

/*************************************************************************
 * flockBLService: flockIMigratable implementation
 *************************************************************************/

flockBLService.prototype.__defineGetter__("migrationName",
function flockBLService_getter_migrationName() {
  this._logger.debug("_getter_migrationName()");
  return flockGetString("common/migrationNames", "migration.name.blog");
});

// boolean needsMigration(in string aOldVersion);
flockBLService.prototype.needsMigration =
function flockBLService_needsMigration(aOldVersion) {
  this._logger.debug("needsMigration(" + aOldVersion + ")");
  // Version: major.minor.revision
  var major = parseInt(aOldVersion[0], 10);
  var minor = parseInt(aOldVersion[2], 10);
  if (major == 1 && (minor == 1 || minor == 2)) {
    // Grackle and Grouse: This service was added for Grackle and changed for
    // Ibis.
    return true;
  }
  return false;
};

// nsISupports startMigration(in string aOldVersion,
//                            in flockIMigrationProgressListener aListener);
flockBLService.prototype.startMigration =
function flockBLService_startMigration(aOldVersion,
                                       aFlockMigrationProgressListener)
{
  this._logger.debug("startMigration(" + aOldVersion
                     + ", aFlockMigrationProgressListener)");
  var ctxt = {
    majorVersion: parseInt(aOldVersion[0], 10),
    minorVersion: parseInt(aOldVersion[2], 10),
    listener: aFlockMigrationProgressListener
  };
  return { wrappedJSObject: ctxt };
};

// boolean doMigrationWork(in nsISupports aMigrationContext);
flockBLService.prototype.doMigrationWork =
function flockBLService_doMigrationWork(aMigrationContext) {
  this._logger.debug("doMigrationWork(aMigrationContext)");
  var context = aMigrationContext.wrappedJSObject;
  if (!context.bloggerMigrator) {
    context.bloggerMigrator = this._migrateFromCoop(context);
  }
  if (context.bloggerMigrator.next()) {
    context.bloggerMigrator = null;
  }
  return (context.bloggerMigrator != null);
};

// void finishMigration(in nsISupports aMigrationContext);
flockBLService.prototype.finishMigration =
function flockBLService_finishMigration(aMigrationContext) {
  this._logger.debug("finishMigration(aMigrationContext)");
};

/*************************************************************************
 * Component: flockBLAccount
 *************************************************************************/

function flockBLAccount() {
  FlockSvcUtils.getLogger(this).init(CLASS_SHORT_NAME + ":Account");
  FlockSvcUtils.getACUtils(this);
  FlockSvcUtils.getCoop(this);

  // BC: Do not use FlockSvcUtils.getWD() here, because it would load
  //     the Web detective file a second time
  this._WebDetective = CC["@flock.com/web-detective;1"]
                       .getService(CI.flockIWebDetective);

  this.service = CC[BLOGGER_CONTRACTID].getService(CI.flockIBlogWebService)

  var wsa = FlockSvcUtils.flockIWebServiceAccount;
  wsa.addDefaultMethod(this, "getService");
  wsa.addDefaultMethod(this, "keep");
  wsa.addDefaultMethod(this, "getParam");
  wsa.addDefaultMethod(this, "setParam");
  wsa.addDefaultMethod(this, "getCustomParam");
  wsa.addDefaultMethod(this, "getAllCustomParams");
  wsa.addDefaultMethod(this, "setCustomParam");
  wsa.addDefaultMethod(this, "isAuthenticated");
}

/**************************************************************************
 * Flock BL Account: XPCOM Component Creation
 **************************************************************************/

flockBLAccount.prototype = new FlockXPCOMUtils.genericComponent(
  CLASS_NAME + " Account",
  "",
  "",
  flockBLAccount,
  0,
  [
    CI.flockIWebServiceAccount,
    CI.flockIBlogAccount
  ]
);

/**************************************************************************
 * Flock BL Account: flockIWebServiceAccount Implementation
 **************************************************************************/

// flockIWebServiceAccount implementation
flockBLAccount.prototype.login =
function flockBLAccount_login(listener) {
  this._logger.info("login(listener)");
  if (!this.coopObj.isAuthenticated) {
    this.coopObj.isAuthenticated = true;
  }
  if (listener) {
    listener.onSuccess(this, "login");
  }
};

flockBLAccount.prototype.logout =
function flockBLAccount_logout(listener) {
  this._logger.info("logout(listener)");
  if (this.coopObj.isAuthenticated) {
    var cookieManager = Components.classes["@mozilla.org/cookiemanager;1"]
                                  .getService(Components.interfaces.nsICookieManager);
    cookieManager.remove(".blogger.com", "B2I", "/", false);
    cookieManager.remove(".blogger.com", "blogger_SID", "/", false);
    cookieManager.remove(".blogger.com", "ServerID", "/", false);
    cookieManager.remove(".blogger.com", "__utma", "/", false);
    cookieManager.remove(".blogger.com", "__utmb", "/", false);
    cookieManager.remove(".blogger.com", "__utmc", "/", false);
    cookieManager.remove(".blogger.com", "__utmz", "/", false);
    cookieManager.remove(".blogger.com", "NSC_cmphhfs-fyu", "/", false);
    cookieManager.remove("blogger.com", "NSC_cmphhfs-fyu", "/", false);
    cookieManager.remove("www.blogger.com", "NSC_cmphhfs-fyu", "/", false);
    cookieManager.remove("www.blogger.com", "JSESSIONID", "/", false);
    cookieManager.remove(".www2.blogger.com", "__utma", "/", false);
    cookieManager.remove(".www2.blogger.com", "__utmb", "/", false);
    cookieManager.remove(".www2.blogger.com", "__utmc", "/", false);
    cookieManager.remove(".www2.blogger.com", "__utmz", "/", false);
    cookieManager.remove("www2.blogger.com", "S", "/", false);
    cookieManager.remove("www.google.com", "LSID", "/accounts", false);
  }
  if (listener) {
    listener.onSuccess(this, "logout");
  }
};

/*************************************************************************
 * XPCOM Support - Module Construction
 *************************************************************************/

// Create array of components.
var componentsArray = [flockBLService];

// Generate a module for XPCOM to find.
var NSGetModule = FlockXPCOMUtils.generateNSGetModule(MODULE_NAME,
                                                      componentsArray);
