// 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("flockWordpressService: "+x+"\n"); }

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

Cu.import("resource:///modules/FlockXMLUtils.jsm");

const WORDPRESS_CID = Components.ID('{458ca863-38fd-4c9b-b76b-44f0453a62cd}');
const WORDPRESS_CONTRACTID = '@flock.com/people/wordpress;1';
const SERVICE_ENABLED_PREF          = "flock.service.wordpress.enabled";
const CATEGORY_COMPONENT_NAME       = "Wordpress JS Component"
const CATEGORY_ENTRY_NAME           = "wordpress"

const WORDPRESS_FAVICON = "chrome://flock/content/services/wordpress/favicon.png";

const RDFS = Cc['@mozilla.org/rdf/rdf-service;1'].getService (Ci.nsIRDFService);

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 loadSubScript(spec) {
  var loader = Cc['@mozilla.org/moz/jssubscript-loader;1'].getService(Ci.mozIJSSubScriptLoader);
  var context = {};
  loader.loadSubScript(spec, context);
  return context;
}

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


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

userBlogsListener.prototype =
{
  // aResult is an Array (simpleEnumerator) of struct objects
  onResult: function(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(errorcode, errormsg) {
    var error = Cc['@flock.com/error;1'].createInstance(Ci.flockIError);
    error.serviceErrorCode = errorcode;
    error.serviceErrorString = errormsg;
    this.listener.onError(error);
  },
  onFault: function(errorcode, errormsg) {
    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";
    }
    this.listener.onFault(error);
  }
}


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

  this._ctk = {
    interfaces: [
      "nsISupports",
      "nsIClassInfo",
      "nsIObserver",
      "nsISupportsCString",
      "flockIWebService",
      "flockIAuthenticateNewAccount",
      "flockIManageableWebService",
      "flockIBlogWebService"
    ],
    shortName: "wordpress",
    fullName: "WordPress.com",
    description: "WordPress.com Web Service",
    favicon: WORDPRESS_FAVICON,
    CID: WORDPRESS_CID,
    contractID: WORDPRESS_CONTRACTID,
    accountClass: flockWPAccount
  };
  this._profiler = Cc["@flock.com/profiler;1"].getService(Ci.flockIProfiler);

  this.init();
}

// nsIObserver
flockWPService.prototype.observe = function flockWPService_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;
  }
}

flockWPService.prototype.addAccount =
function flockWPService_addAccount(aUsername, aIsTransient, aListener)
{
  var accountURN = this.acUtils.createAccount(this,
                                              aUsername,
                                              aUsername,
                                              null,
                                              aIsTransient);
  var account = this.faves_coop.get(accountURN);
  this.USER = accountURN;

  // Add the blog account
  var wpsvc = this;
  var listener = {
    onResult: function(aResult) {
      var theBlog;
      while (aResult.hasMoreElements()) {
        theBlog = aResult.getNext();
        theBlog.QueryInterface(Ci.flockIBlogAccount);
        theCoopBlog = new wpsvc.faves_coop.Blog(accountURN+":"+theBlog.blogid, {
          name: theBlog.title,
          title: theBlog.title,
          blogid: theBlog.blogid,
          URL: theBlog.URL,
          apiLink: theBlog.URL+'xmlrpc.php'
        });
        account.children.addOnce(theCoopBlog);
        dump("Added the wordpress blog "+theBlog.title+"\n");

        // Comments: create the "My Blogs" folder if needed
        var feedManager = Components.classes["@flock.com/feed-manager;1"].getService(Components.interfaces.flockIFeedManager);
        var blogFolder = null;
        var _enum = feedManager.getFeedContext("news").getRoot().getChildren();
        while (_enum.hasMoreElements() && !blogFolder) {
          var candidate = _enum.getNext();
          if (candidate.getTitle() == "My Blogs")
            blogFolder = candidate;
        }
        if (!blogFolder)
          blogFolder = feedManager.getFeedContext("news").getRoot().addFolder("My Blogs");
        // Comments: add the stream
        var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
        var uri = ios.newURI(theBlog.URL+"comments/feed/", null, null);
        var feedManagerListener = {
          onGetFeedComplete: function(feed) {
            blogFolder.subscribeFeed(feed);
            Cc["@flock.com/metrics-service;1"]
              .getService(Ci.flockIMetricsService)
              .report("FeedsSidebar-AddFeed",  "WordPressNewAccount");
          },
          onError: function(error) {
          }
        }
        feedManager.getFeed(uri, feedManagerListener);
      }
      if (aListener) aListener.onSuccess(acct, "addAccount");
    },
    onFault: function(aError) {
      aError.errorCode = aError.BLOG_INVALID_AUTH;
      wpsvc.faves_coop.accounts_root.children.remove(account);
      account.destroy();
      if(aListener) {
        aListener.onError(null, 'FAULT', aError);
      }
    },
    onError: function(aError) {
      aError.errorCode = aError.BLOG_INVALID_AUTH;
      wpsvc.faves_coop.accounts_root.children.remove(account);
      account.destroy();
      if(aListener) {
        aListener.onError(null, 'ERROR', aError);
      }
    }
  };

  this.getUsersBlogs(listener, null);

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


flockWPService.prototype.init =
function ()
{
  DEBUG(".init()");

  if (this.initialized) return;
  this.initialized = true;

  var evtID = this._profiler.profileEventStart("wp-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("wordpress");

  // Attributes of flockIBlogWebService
  this.supportsCategories = 2;
  this.supportsPostReplace = true;
  this.metadataOverlay = "";

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

  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.wpService = new this.faves_coop.Service('urn:wordpress:service');
  this.wpService.name = 'wordpress';
  this.wpService.desc = 'The Wordpress.com Service';
  this.wpService.logoutOption = false;
  this.wpService.domains = "wordpress.com";
  this.wpService.serviceId = WORDPRESS_CONTRACTID;

  this.urn = this.wpService.id();
  this.url = "http://www.wordpress.com";

  this.webDetective = this.acUtils.useWebDetective("wordpress.xml");

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

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


flockWPService.prototype.refresh =
function flockWPService_refresh(aURN, aListener)
{
	debug ("flockWPService refresh with aURN of " + aURN + "\n");

	// Introspection against what we're syncing - Identity or Account or Item?
	var refreshItem = this.faves_coop.get(aURN);

  if (refreshItem instanceof this.faves_coop.Account) {
	}
  else if (refreshItem instanceof this.faves_coop.Favorite) {
	}
  else {
		throw Components.results.NS_ERROR_ABORT;
	}
}

flockWPService.prototype.refreshAccount =
function flockWPService_refreshAccount (aURN, aListener) {
  debug("WPService - refreshAccount with aURN of " + aURN);
}


// BEGIN flockIBlogWebService interface

flockWPService.prototype._youtubize = function WPS_youtubize (aContent) {
  var result = aContent;

  var re = /<object.*><param.*value="(.+?)".*>.*<\/object>/;
  var arr = re.exec(result);
  while (arr) {
    result = result.replace(arr[0], "[youtube="+arr[1].replace('/v/', '/w/?v=')+"]");
    arr = re.exec(result);
  }
  return result;
}

flockWPService.prototype.newPost =
function(aListener, aBlogId, aPost, aPublish, aNotifications)
{
  var mtService = Cc['@flock.com/blog/service/movabletype;1'].getService(Ci.flockIBlogWebService);
  aPost.description = this._youtubize(aPost.description);
  mtService.newPost(aListener, aBlogId, aPost, aPublish, aNotifications);
}

flockWPService.prototype.editPost =
function(aListener, aBlogId, aPost, aPublish, aNotifications)
{
  var mtService = Cc['@flock.com/blog/service/movabletype;1'].getService(Ci.flockIBlogWebService);
  // Bug 10368: Wordpress.com's date parsing is buggy, so we clear the date
  // so the date of the original post is used
  aPost.issued = null;
  mtService.editPost(aListener, aBlogId, aPost, aPublish, aNotifications);
}

flockWPService.prototype.deletePost =
function(aListener, aBlogId, aPostid)
{
  var mtService = Cc['@flock.com/blog/service/movabletype;1'].getService(Ci.flockIBlogWebService);
  mtService.deletePost(aListener, aBlogId, aPostid);
}

flockWPService.prototype.getUsersBlogs =
function(aListener, aUrl)
{
  var username = this.faves_coop.get(this.USER).name;
  var pw = this.acUtils.getPassword(this.urn+':'+username);

  var listener = new userBlogsListener(aListener, false);
  var xmlrpcServer = new flockXmlRpcServer ('http://wordpress.com/xmlrpc.php');
  var args = ["flockbrowser", username, pw.password];
  xmlrpcServer.call("blogger.getUsersBlogs", args, listener);
}

flockWPService.prototype.getRecentPosts =
function(aListener, aBlogId, aNumber)
{
  var mtService = Cc['@flock.com/blog/service/movabletype;1'].getService(Ci.flockIBlogWebService);
  mtService.getRecentPosts(aListener, aBlogId, aNumber);
}

flockWPService.prototype.getCategoryList =
function(aListener, aBlogId)
{
  var mtService = Cc["@flock.com/blog/service/movabletype;1"]
                  .getService(Ci.flockIBlogWebService);
  var catListener = {
    onResult: function catListener_onResult(aResult) {
      // [BUG10932]
      // Wordpress.com is sending us categories name escaped twice!
      // Our XMLRPC lib will unescape it once, but we need to unescape it
      // here again
      var result = [];
      while (aResult.hasMoreElements()) {
        var entry = aResult.getNext();
        if (!entry) {
          continue;
        }
        entry.QueryInterface(Ci.flockIBlogCategory);
        var category = new BlogCategory(entry.id, flockXMLDecode(entry.label));
        result.push(category);
      }
      aListener.onResult(new simpleEnumerator(result));
    },
    onError: function catListener_onError(aError) {
      aListener.onError(aError);
    },
    onFault: function catListener_onFault(aError) {
      aListener.onFault(aError);
    }
  }
  mtService.getCategoryList(catListener, aBlogId);
}
// END flockIBlogWebService interface


// BEGIN flockIManageableWebService interface

flockWPService.prototype.getCredentialsFromForm =
function flockWPService_getCredentialsFromForm(aForm)
{
  this.logger.debug("{flockIManageableWebService}.getCredentialsFromForm(aForm)");
  aForm.QueryInterface(Ci.nsIDOMHTMLFormElement);
  var pw = this.acUtils.extractPasswordFromHTMLForm(aForm);

  return {
    QueryInterface: function(aIID) {
      if (!aIID.equals(Ci.nsISupports) &&
          !aIID.equals(Ci.nsIPassword) &&
          !aIID.equals(Ci.flockIPassword)) { 
        throw Ci.NS_ERROR_NO_INTERFACE;
      }
      return this;
    },
    host: "",
    user: pw.user,
    password: pw.password,
    formType: "login"
  };
}


flockWPService.prototype.getAccountIDFromDocument =
function flockWPService_getAccountIDFromDocument(aDocument)
{
  this.logger.debug("{flockIManageableWebService}.getAccountIDFromDocument(aDocument)");
  aDocument.QueryInterface(Components.interfaces.nsIDOMHTMLDocument);

  var results = Cc["@mozilla.org/hash-property-bag;1"].createInstance(Ci.nsIWritablePropertyBag2);
  var results2 = Cc["@mozilla.org/hash-property-bag;1"].createInstance(Ci.nsIWritablePropertyBag2);
  if (this.webDetective.detect("wordpress", "blogURL", aDocument, results)) {
    this.blogURL = results.getPropertyAsAString("blogURL").replace("/wp-admin","");
  }
  else // The landing page IS the blog URL
    this.blogURL = aDocument.URL;

  if (this.webDetective
          .detect("wordpress", "accountinfo", aDocument, results2))
  {
    // We found the account ID on the document, yay!
    return results2.getPropertyAsAString("accountid");
  }

  // We DID NOT find the account ID on the document.  Bummer.  See if we can
  // snag it from the most recent login, instead.  Note that this is a bit of a
  // hack, but a necessary one since Wordpress is not guaranteed to give us the
  // account ID as part of the document.
  var pw = this.acUtils.getTempPassword(this.urn);
  if (pw) {
    return pw.user;
  }

  // Sorry, no account ID for you.
  return null;
}

flockWPService.prototype.updateAccountStatusFromDocument =
function flockWPService_updateAccountStatusFromDocument(aDocument, aAcctURN)
{
  this.logger.debug("updateAccountStatusFromDocument('" + aAcctURN + "')");
  if (aAcctURN) {
    // We're logged in to this account, but still need to grab the avatar URL
    this.acUtils.ensureOnlyAuthenticatedAccount(aAcctURN);
    var results = Cc["@mozilla.org/hash-property-bag;1"]
                  .createInstance(Ci.nsIWritablePropertyBag2);
    if (this.webDetective
            .detect("wordpress", "accountinfo", aDocument, results))
    {
      var avatarURL = null;
      try {
        avatarURL = results.getPropertyAsAString("avatarURL");
      } catch (ex) {
        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("wordpress", "loggedout", aDocument, null))
  {
    // We're logged out (of all accounts)
    this.acUtils.markAllAccountsAsLoggedOut(WORDPRESS_CONTRACTID);
  }
}

// We need a custom implementation of this function for WordPress, rather than
// using the default implementation, because we need to actually dissect the WP
// session cookie to find the session token.  Other information in the cookie
// may change during the course of a session, and we want to omit that from the
// "session value".
flockWPService.prototype.getSessionValue =
function flockWPService_getSessionValue()
{
  var sessionValue = "";
  var dissectCookie = this.webDetective.getString("wordpress", "dissectCookie",
                                                  "wordpressloggedin");
  var cookies = this.webDetective.getSessionCookies("wordpress");
  while (cookies && cookies.hasMoreElements()) {
    var cookie = cookies.getNext()
                        .QueryInterface(Ci.nsICookie);
    var cookieValue = this.acUtils.getCookieValue(cookie.host,
                                                  cookie.name,
                                                  cookie.path);
    if (cookieValue) {
      if (cookie.name == dissectCookie) {
        // We only want the first line of this cookie, not the whole thing.
        sessionValue += cookieValue.split("\n")[0];
      } else {
        sessionValue += cookieValue;
      }
    }
  }
  return sessionValue;
}

flockWPService.prototype.logout =
function flockWPService_logout(aDocument)
{
  this.logger.debug("{flockIManageableWebService}.logout()");
  this.acUtils.removeCookies(this.webDetective.getSessionCookies("wordpress"));
}

// 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: flockWPService,
    CID: WORDPRESS_CID,
    contractID: WORDPRESS_CONTRACTID,
    componentName: CATEGORY_COMPONENT_NAME,
    implementationFunc: function (aComp) { getCompTK().addAllInterfaces(aComp); },
    categories: [
      { category: "wsm-startup", entry: CATEGORY_COMPONENT_NAME, value: WORDPRESS_CONTRACTID },
      { category: "flockWebService", entry: CATEGORY_ENTRY_NAME, value: WORDPRESS_CONTRACTID }
    ]
  });
}

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


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

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

  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.webDetective = this.acUtils.useWebDetective("wordpress.xml");
  this.service = Cc[WORDPRESS_CONTRACTID].getService(Ci.flockIBlogWebService)
}

// nsISupports implementation
flockWPAccount.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
flockWPAccount.prototype.login = function(listener) {
  this.logger.info("{flockIWebServiceAccount}.login()");
  if (listener) {
    listener.onSuccess(this, "login");
  }
}
flockWPAccount.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;
    this.acUtils.removeCookies(this.webDetective.getSessionCookies("wordpress"));
  }
  if (listener) {
    listener.onSuccess(this, "logout");
  }
}
flockWPAccount.prototype.activate = function(aListener) {
  this.logger.info("{flockIWebServiceAccount}.activate()");
}
flockWPAccount.prototype.deactivate = function(aListener) {
  this.logger.info("{flockIWebServiceAccount}.deactivate()");
}
flockWPAccount.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
flockWPAccount.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;
}
