// 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

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

const Cc = Components.classes;
const Ci = Components.interfaces;

const BLOGGER_CID = Components.ID('{dec6797b-2155-4a79-9872-30f142074f0d}');
const BLOGGER_CONTRACTID = '@flock.com/people/blogger;1';
const BLOGGER_URL = "http://www.blogger.com/";
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 CATEGORY_ENTRY_NAME           = "blogger"

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");

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;
}

function flockBLService () {
  var obs = Cc["@mozilla.org/observer-service;1"]
                      .getService(Ci.nsIObserverService);
  obs.addObserver(this, 'xpcom-shutdown', false);
  this.status = Ci.flockIWebService.STATUS_UNKNOWN;

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

  this.url = BLOGGER_URL;
  this.status = Components.interfaces.flockIWebService.STATUS_UNKNOWN;

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

  this._ctk = {
    interfaces: [
      "nsISupports",
      "nsIClassInfo",
      "nsIObserver",
      "nsISupportsCString",
      "flockIWebService",
      "flockIAuthenticateNewAccount",
      "flockIManageableWebService",
      "flockIBlogWebService"
    ],
    shortName: "blogger",
    fullName: "Blogger",
    description: "Google's Blogger.com Web Service",
    favicon: BLOGGER_FAVICON,
    CID: BLOGGER_CID,
    contractID: BLOGGER_CONTRACTID,
    accountClass: flockBLAccount
  };
  this._profiler = Cc["@flock.com/profiler;1"].getService(Ci.flockIProfiler);

  this.init();
}

// Helpers

// For Google Auth (ClientLogin)
flockBLService.prototype._parseError =
function (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;
}


// nsIObserver
flockBLService.prototype.observe = function flockBLService_observe(subject, topic, state) {
  switch (topic) {
    case 'xpcom-shutdown':
      var obs = Cc["@mozilla.org/observer-service;1"]
        .getService(Ci.nsIObserverService);
      obs.removeObserver(this, 'xpcom-shutdown');
      return;
  }
}

// flockIWebService implementation
flockBLService.prototype.addAccount =
function flockBLService_addAccount(aUsername, aIsTransient, aListener)
{
  var accountURN = this.acUtils.createAccount(this,
                                              aUsername,
                                              aUsername,
                                              null,
                                              aIsTransient);
  var account = this.faves_coop.get(accountURN);
  this.USER = accountURN;

  var acct;

  // Add the blog account
  blsvc = this;
  var listener = {
    onResult: function(aResult) {
      var theBlog;
      while (aResult.hasMoreElements()) {
        theBlog = aResult.getNext();
        theBlog.QueryInterface(Ci.flockIBlogAccount);
        theCoopBlog = new blsvc.faves_coop.Blog(accountURN+":"+theBlog.title, {
          name: theBlog.title,
          title: theBlog.title,
          blogid: theBlog.blogid,
          URL: theBlog.URL,
          apiLink: theBlog.apiLink,
          authtoken: theBlog.authtoken
        });
        account.children.addOnce(theCoopBlog);
      }
      if (aListener) aListener.onSuccess(acct, "addAccount");
    },
    onFault: function(aError) {
      blsvc.faves_coop.accounts_root.children.remove(account);
      account.destroy();
      if (aListener) {
        var error = Components.classes['@flock.com/error;1'].createInstance(Components.interfaces.flockIError);
        error.serviceErrorString = aError;
        aListener.onError(null, 'FAULT', error);
      }
    },
    onError: function(aError) {
      blsvc.faves_coop.accounts_root.children.remove(account);
      account.destroy();
      if(aListener) {
        aListener.onError(null, 'ERROR', aError);
      }
    }
  }

  if (aUsername.match('@'))
    this.getUsersBlogs(listener, 'http://beta.blogger.com/feeds/default/blogs');
  else
    this.getUsersBlogs(listener, 'http://www.blogger.com/feeds/default/blogs');

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


flockBLService.prototype.init =
function ()
{
  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) )
  {
    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;
  }

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

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

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

  this.blService = new this.faves_coop.Service('urn:blogger:service');
  this.blService.name = 'blogger';
  this.blService.desc = 'The Blogger.com Service';
  this.blService.logoutOption = false;
  this.blService.domains = "google.com,blogger.com";
  this.blService.serviceId = BLOGGER_CONTRACTID;

  this.urn = this.blService.id();
  this.webDetective = this.acUtils.useWebDetective("blogger.xml");
  
  // 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, "");
}


flockBLService.prototype.refresh =
function (aURN, aListener) {
  throw Components.results.NS_ERROR_ABORT;
}


// BEGIN flockIBlogWebService interface

function atomListener(aListener){
  this.listener = aListener;
  this.logger = Cc['@flock.com/logger;1']
    .createInstance(Ci.flockILogger);
  this.logger.init("blog");
}

atomListener.prototype = {
  onResult: function(aResult) {
    this.listener.onResult(aResult.atomid);
  },
  onError: function(error) {
    this.logger.error("<<<<<<<<<< Google API: SERVER TO FLOCK");
    this.logger.error("ERROR "+error);
    this.listener.onError(error);
  },
  onFault: function(error) {
    this.logger.error("<<<<<<<<<< Google API: SERVER TO FLOCK");
    this.logger.error("FAULTAA "+error);
    this.listener.onFault(error);
  }
}

flockBLService.prototype.doAuthRequest = function(listener, method, url, body, processor) {
  var inst = this;
  this._req = Cc['@mozilla.org/xmlextras/xmlhttprequest;1'].createInstance(Ci.nsIXMLHttpRequest);
  this._req.onreadystatechange = function (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("Request status: "+inst._req.status);
      inst.logger.debug("\nRESPONSE\n" + inst._req.responseText);
      try {
        if(inst._req.status == 200 || inst._req.status == 201 || inst._req.status == 205) {
        try {
            processor(listener, inst);
          }
          catch(e) {
            // listener.onError(inst.ERROR_PARSER);
            inst.logger.error(e + " " + e.lineNumber);
          }
        }
        else {
          var faultString; //: "Make sure that are not trying to blog really weird html, and note the following kind user:\n\n",
          // };
          // faultString = inst._req.responseText;
          inst.logger.error("Error: "+inst._req.responseText);
          // listener.onFault(faultString);
          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;
              error.errorString = "Bad authentication";
              break;
            case "CaptchaRequired":
              error.errorCode = Ci.flockIError.BLOG_CAPTCHA_REQUIRED;
              error.errorString = "Captcha Required";
              break;
             case "NotVerified":
              error.errorCode = Ci.flockIError.BLOG_NOT_VERIFIED;
              error.errorString = "Email Not Verified";
              break;           
            default: // Unknown error code
              error.errorCode = Ci.flockIError.BLOG_UNKNOWN;
              error.errorString = "Unknown Error";
          }
          listener.onError(error);
        }
      } catch(e) {
        inst.logger.error(e + " " + e.fileName + " " + e.lineNumber);
        listener.onError(inst.ERROR_PARSER);
      }
    }
  };
  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(listener, method, url, body, authToken, processor) {
  var inst = this;
  this._req = Cc['@mozilla.org/xmlextras/xmlhttprequest;1'].createInstance(Ci.nsIXMLHttpRequest);
  this._req.onreadystatechange = function (aEvt) {
    inst.logger.info("<<<<<<<<<< Google API: SERVER TO FLOCK");
    debug("<<<<<<<<<< Google API: SERVER TO FLOCK\n");
    inst.logger.info("Request readyState: "+inst._req.readyState);
    debug("Request readyState: "+inst._req.readyState+"\n");
    if (inst._req.readyState == 4) {
//      inst.logger.info("Request status: "+inst._req.status);
//      debug("Request status: "+inst._req.status+"\n");
      inst.logger.info("\nRESPONSE\n" + inst._req.responseText);
      debug("\nRESPONSE\n" + inst._req.responseText+"\n");
      try {
        if (inst._req.status == 200 || inst._req.status == 201 || inst._req.status == 205) {
          try {
            processor(listener, inst, authToken);
          }
          catch(e) {
            // listener.onError(inst.ERROR_PARSER);
            inst.logger.error(e + " " + e.lineNumber);
          }
        }
        else {
          var faultString; //: "Make sure that are not trying to blog really weird html, and note the following kind user:\n\n",
          faultString = inst._req.responseText;
          inst.logger.error(faultString+"\n");
          listener.onFault(faultString);
        }
      } catch(e) {
        inst.logger.error(e + " " + e.fileName + " " + e.lineNumber);
        listener.onError(inst.ERROR_PARSER);
      }
    }
  };
  rval = this._req.open(method, url, true);
  this._req.setRequestHeader("Authorization", "GoogleLogin auth="+authToken);
  this.logger.info(">>>>>>>>>> Google API: FLOCK TO SERVER");
  debug(">>>>>>>>>> Google API: FLOCK TO SERVER\n");
  this.logger.info("\nSENDING\n" + body);
  debug("\nSENDING\n" + body+"\n");
  this.logger.info("\nTO\n" + method + " @ " + url);
  debug("\nTO\n" + method + " @ " + url+"\n");
  rval = this._req.send(body);
}

flockBLService.prototype.parseUsersBlogs = function(listener, 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 newAccount = new BlogAccount(title);
      newAccount.api = this.shortName;
      newAccount.authtoken = authToken;
      var links = domEntry.getElementsByTagName("link");
      for (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);
    }
    debug("Found "+ result.length +" blogs\n");
    listener.onResult(new simpleEnumerator(result));
  }
  catch(e) {
    var logger = Cc['@flock.com/logger;1']
       .createInstance(Ci.flockILogger);
    logger.init("blog");
    logger.error(e + " " + e.lineNumber);
    listener.onError(e + " " + e.lineNumber);
  }
}

flockBLService.prototype.parseRecentPosts = function(listener, 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);
    }
  }
  listener.onResult(new simpleEnumerator(result));
}

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

  var listener = {
    onResult: function(aResult) {
      aListener.onResult(aResult.atomid);
    },
    onError: function(error) {
      svc.logger.error("<<<<<<<<<< Google API: SERVER TO FLOCK");
      svc.logger.error("ERROR " + error.serviceErrorString);
      aListener.onError(error);
    },
    onFault: function(error) {
      svc.logger.error("<<<<<<<<<< Google API: SERVER TO FLOCK");
      svc.logger.error("FAULTXX "+error.serviceErrorString);
      if (error.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.faves_coop.get(aBlogId);
          coopBlog.authtoken = token;
          svc.newPost(aListener, aBlogId, aPost, aPublish, aNotifications);
        };
        var body = 'Email='+encodeURIComponent(account.username)+'&Passwd='+encodeURIComponent(account.password)+'&service=blogger&source=FlockInc-Flock-0.8';
        svc.doAuthRequest(listener, "POST", "https://www.google.com/accounts/ClientLogin", body, parseAuthToken);
      }
      else
        aListener.onFault(error);
    }
  }

  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 (listener, url, atomEntry, account.authtoken);
}

flockBLService.prototype.editPost =
function(aListener, aBlogId, aPost, aPublish, aNotifications)
{
  var gBlogService = Cc['@flock.com/flock-blog;1'].getService(Ci['flockIBlogService']);
  var account = gBlogService.getAccount(aBlogId);

  if (account.username.match('@')) { // Blogger beta
    var listener = new atomListener(aListener);
    var account = gBlogService.getAccount(aBlogId);

    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 (listener, url, atomEntry, account.authtoken);
  }
  else {
    var atomAPI = Cc['@flock.com/blog/service/atom;1'].getService (Ci.flockIBlogWebService);
    atomAPI.editPost(aListener, aBlogId, aPost, aPublish, aNotifications);
  }
}

flockBLService.prototype.deletePost =
function(aListener, aBlogId, aPostid)
{
  var gBlogService = Cc['@flock.com/flock-blog;1'].getService(Ci['flockIBlogService']);
  var account = gBlogService.getAccount(aBlogId);

  if (account.username.match('@')) { // 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);
    var url = account.apiLink;
    url += "/" + aPostid;
    // if(aEditURI) url = aEditURI;
    this.doRequest(aListener,"DELETE", url, null, account.authtoken, handleDelete);
  }
  else {
    var atomAPI = Cc['@flock.com/blog/service/atom;1'].getService (Ci.flockIBlogWebService);
    atomAPI.deletePost(aListener, aBlogId, aPostid);
  }
}


flockBLService.prototype._getUsersBlogs =
function(aListener, aAPILink)
{
  var inst = this;
  this._req = Cc['@mozilla.org/xmlextras/xmlhttprequest;1'].createInstance(Ci.nsIXMLHttpRequest);
  this._req.onreadystatechange = function (aEvt) {
    inst.logger.info("<<<<<<<<<< Blogger: SERVER TO FLOCK");
    inst.logger.info("Request readyState: "+inst._req.readyState);
    if(inst._req.readyState == 4) {
      inst.logger.info("Request status: "+inst._req.status);
      inst.logger.info("\nRESPONSE\n" + inst._req.responseText);
      //try {
        if(inst._req.status == 200 || inst._req.status == 201 || inst._req.status == 205) {
        //try {
          var result = new Array();
          var dom = inst._req.responseXML;

          if (!dom) {
            debug("Problem: "+inst._req.responseText+"\n");
            listener.onError(null);
          }

          var domEntries = dom.getElementsByTagName("entry");
          for (i=0; i<domEntries.length; i++) {
            domEntry = domEntries[i];
            title = domEntry.getElementsByTagName("title")[0].textContent;
            var newAccount = new BlogAccount(title);
            newAccount.api = this.shortName;
            var links = domEntry.getElementsByTagName("link");
            for (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);
          }
          debug("Found "+ result.length +" blogs\n");
          aListener.onResult(new simpleEnumerator(result));
        //}
        //catch(e) {
          // listener.onError(inst.ERROR_PARSER);
          //inst.logger.error(e + " " + e.lineNumber+"\n");
        //}
      }
      else {
        var faultString; //: "Make sure that are not trying to blog really weird html, and note the following kind user:\n\n",
        faultString = inst._req.responseText;
        //faultString += inst._req;
        inst.logger.error(faultString);
        aListener.onFault(faultString);
      }
    //} catch(e) {
    //  inst.logger.error(e + " " + e.fileName + " " + e.lineNumber + "\n");
    //  listener.onError(inst.ERROR_PARSER);
    //  }
    }
  };

  var username = this.faves_coop.get(this.USER).name;
  debug("USER: "+username+"\n");
  var pw = this.acUtils.getPassword(this.urn+':'+username);

  rval = this._req.open("GET", aAPILink, true, username, pw.password);
  rval = this._req.send(null);
}

flockBLService.prototype._getUsersBlogsBeta = function (aListener, aUrl){
  debug("getUsersBlog... "+aUrl+"\n");
  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");
    listener.onResult(token);
  };

  var listener = {
    onResult: function(aToken) {
      inst.doRequest(aListener, "GET", aUrl, null, aToken, inst.parseUsersBlogs);
    },
    onError: function(aError) {
      aListener.onError(aError);
    },
    onFault: function(aError) {
      aListener.onError(aError);
    }
  }

  var username = this.faves_coop.get(this.USER).name;
  debug("USER: "+username+"\n");
  var pw = this.acUtils.getPassword(this.urn+':'+username);

  var body = 'Email='+encodeURIComponent(username)+'&Passwd='+encodeURIComponent(pw.password)+'&service=blogger&source=FlockInc-Flock-0.8';
  this.doAuthRequest(listener, "POST", "https://www.google.com/accounts/ClientLogin", body, parseAuthToken);
}

flockBLService.prototype.getUsersBlogs =
function(aListener, aAPILink)
{
  var username = this.faves_coop.get(this.USER).name;
  if (username.match('@'))
    this._getUsersBlogsBeta(aListener, aAPILink);
  else
    this._getUsersBlogs(aListener, aAPILink);
}

flockBLService.prototype.getRecentPosts = function(aListener, aBlogId, aNumber){
  var gBlogService = Cc['@flock.com/flock-blog;1'].getService(Ci['flockIBlogService']);
  var account = gBlogService.getAccount(aBlogId);

  var url = account.apiLink;
  url.match(/(.+\/)(.+)/);
  if (RegExp.$2 == "post") {
    url = RegExp.$1 + "feed";
  }
  
  var listener = {
    onResult: function(aResult) {
      aListener.onResult(aResult);
    },
    onError: function(error) {
      svc.logger.error("<<<<<<<<<< Google API: SERVER TO FLOCK");
      svc.logger.error("ERROR " + error.serviceErrorString);
      aListener.onError(error);
    },
    onFault: function(error) {
      svc.logger.error("<<<<<<<<<< Google API: SERVER TO FLOCK");
      svc.logger.error("FAULTXX "+error.serviceErrorString);
      if (error.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.faves_coop.get(aBlogId);
          coopBlog.authtoken = token;
          inst.doRequest(listener, "GET", url, null, token, inst.parseRecentPosts);
        };
        var body = 'Email='+encodeURIComponent(account.username)+'&Passwd='+encodeURIComponent(account.password)+'&service=blogger&source=FlockInc-Flock-0.8';
        inst.doAuthRequest(listener, "POST", "https://www.google.com/accounts/ClientLogin", body, parseAuthToken);
      }
      else
        aListener.onFault(error);
    }
  }
  
  this.doRequest(listener, "GET", url, null, account.authtoken, this.parseRecentPosts);
}

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

// END flockIBlogWebService interface


// BEGIN flockIManageableWebService interface
flockBLService.prototype.docRepresentsSuccessfulLogin =
function flockBLService_docRepresentsSuccessfulLogin(aDocument)
{
  this.logger.debug("{flockIManageableWebService}.docRepresentsSuccessfulLogin(aDocument)");
  aDocument.QueryInterface(Components.interfaces.nsIDOMHTMLDocument);
  return this.webDetective.detect("blogger", "loggedin", aDocument, null);
}

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

flockBLService.prototype.updateAccountStatusFromDocument =
function flockBLService_updateAccountStatusFromDocument(aDocument, aAcctURN)
{
  this.logger.debug("updateAccountStatusFromDocument('" + aAcctURN + "')");
  if (aAcctURN) {
    // We're logged in to this account, but we still need to grab the user's
    // avatar
    this._authMgr.reportCompleteLoginToNetwork(aAcctURN);
    this.acUtils.ensureOnlyAuthenticatedAccount(aAcctURN);
    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) {
        var c_acct = this.faves_coop.get(aAcctURN);
        c_acct.avatar = avatarURL;
      }
    }
  } else if (this.webDetective
                 .detect("blogger", "loggedout", aDocument, null))
  {
    // We logged out (of all accounts)
    this.acUtils.markAllAccountsAsLoggedOut(BLOGGER_CONTRACTID);
    this._authMgr.reportCompleteLogoutFromNetwork(this.contractId);
  }
}

flockBLService.prototype.logout = function() {
  this.logger.info("{flockIWebServiceService}.logout()");
  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);
}


// END flockIManageableWebService interface



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

function createModule(aParams) {
  var Cc = Components.classes;
  var Ci = Components.interfaces;
  var Cr = Components.results;
  return {
    registerSelf: function (aCompMgr, aFileSpec, aLocation, aType) {
      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: flockBLService,
    CID: BLOGGER_CID,
    contractID: BLOGGER_CONTRACTID,
    componentName: CATEGORY_COMPONENT_NAME,
    implementationFunc: function (aComp) { getCompTK().addAllInterfaces(aComp); },
    categories: [
      { category: "wsm-startup", entry: CATEGORY_COMPONENT_NAME, value: BLOGGER_CONTRACTID },
      { category: "flockWebService", entry: CATEGORY_ENTRY_NAME, value: BLOGGER_CONTRACTID }
    ]
  });
}

// ========== END XPCOM Module support ==========


/* ********** Account Class ********** */

function flockBLAccount() {
  this.logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
  this.logger.init("bloggerAccount");

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

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

  this.service = Cc[BLOGGER_CONTRACTID].getService(Ci.flockIBlogWebService)
}

// nsISupports implementation
flockBLAccount.prototype.QueryInterface = function(iid) {
  if (!iid.equals(Ci.nsISupports) &&
    !iid.equals(Ci.flockIWebServiceAccount) &&
    !iid.equals(Ci.flockIBlogWebServiceAccount))
  {
    throw Components.results.NS_ERROR_NO_INTERFACE;
  }
  return this;
}

// flockIWebServiceAccount implementation
flockBLAccount.prototype.login = function(listener) {
  this.logger.info("{flockIWebServiceAccount}.login()");
  var c_acct = this.faves_coop.get(this.urn);
  if (!c_acct.isAuthenticated) {
    c_acct.isAuthenticated = true;
  }
  if (listener) {
    listener.onSuccess(this, "login");
  }
}
flockBLAccount.prototype.logout = function(listener) {
  this.logger.info("{flockIWebServiceAccount}.logout()");
  var c_acct = this.faves_coop.get(this.urn);
  if (c_acct.isAuthenticated) {
    c_acct.isAuthenticated = false;
    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");
  }
}
flockBLAccount.prototype.activate = function(aListener) {
  this.logger.info("{flockIWebServiceAccount}.activate()");
}
flockBLAccount.prototype.deactivate = function() {
  this.logger.info("{flockIWebServiceAccount}.deactivate()");
}
flockBLAccount.prototype.keep = function() {
  var c_acct = this.faves_coop.get(this.urn);
  c_acct.isTransient = false;
  this.acUtils.makeTempPasswordPermanent(this.service.urn+':'+c_acct.accountId);
}

// flockIBlogWebServiceAccount implementation
flockBLAccount.prototype.getBlogs = function() {
  this.logger.info("{flockIBlogWebServiceAccount}.getBlogs()");
  var blogsEnum = {
    QueryInterface : function(iid) {
      if (!iid.equals(Ci.nsISupports) &&
          !iid.equals(Ci.nsISimpleEnumerator))
      {
        throw Components.results.NS_ERROR_NO_INTERFACE;
      }
      return this;
    },
    hasMoreElements : function() {
      return false;
    },
    getNext : function() {
    }
  };
  return blogsEnum;
}
