// 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 CC = Components.classes;
const CI = Components.interfaces;
const CR = Components.results;
const CU = Components.utils;

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

CU.import("resource:///modules/FlockSvcUtils.jsm");
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 CLASS_NAME = "Wordpress.com";
const CATEGORY_COMPONENT_NAME = "Wordpress JS Component";
const CATEGORY_ENTRY_NAME = "wordpress";
const CLASS_SHORT_NAME = "wordpress";
const MODULE_NAME = "wordpress";

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


function userBlogsListener(aBlogListener){
  this.blogListener = aBlogListener;
}

userBlogsListener.prototype =
{
  // aSimpleEnum is an Array (simpleEnumerator) of nsIPropertyBag
  onResult: function ubl_onResult(aSimpleEnum) {
    var result = new Array();
    for each (var entry in aSimpleEnum){
      var blog = CC["@mozilla.org/hash-property-bag;1"]
                 .createInstance(CI.nsIWritablePropertyBag2);
      blog.setPropertyAsAString("title", entry.blogName);
      blog.setPropertyAsAString("blogID", entry.blogid);
      blog.setPropertyAsAString("APILink", "");
      blog.setPropertyAsAString("URL", entry.url);
      result.push(blog);
    }
    this.blogListener.onResult(new simpleEnumerator(result));
  },
  onError: function ubl_onError(errorcode, errormsg) {
    var error = CC["@flock.com/error;1"].createInstance(CI.flockIError);
    error.serviceErrorCode = errorcode;
    error.serviceErrorString = errormsg;
    this.blogListener.onError(error);
  },
  onFault: function ubl_onFault(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;
        break;
      default: // Unknown error code
        error.errorCode = CI.flockIError.BLOG_UNKNOWN_ERROR;
    }
    this.blogListener.onFault(error);
  }
}



/*************************************************************************
 * Component: flockWPService
 *************************************************************************/
function flockWPService () {
  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");
  this.initialized = false;

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

  this._init();

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

  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "loginURL");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "getAccount");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "getAuthenticatedAccount");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "getAccounts");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "removeAccount");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "docRepresentsSuccessfulLogin");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "getCredentialsFromForm");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "ownsDocument");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "ownsLoginForm");
}

/*************************************************************************
 * flockWPService: XPCOM Component Creation
 *************************************************************************/

flockWPService.prototype = new FlockXPCOMUtils.genericComponent(
  CLASS_NAME,
  WORDPRESS_CID,
  WORDPRESS_CONTRACTID,
  flockWPService,
  CI.nsIClassInfo.SINGLETON,
  [
    CI.flockIWebService,
    CI.flockILoginWebService,
    CI.flockIBlogWebService
  ]
);

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


/*************************************************************************
 * flockWPService: Private Data and Functions
 *************************************************************************/

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._init =
function flockWPService__init()
{
  FlockSvcUtils.getLogger(this);
  this._logger.debug(".init()");

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

  this.prefService = CC["@mozilla.org/preferences-service;1"]
                     .getService(CI.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.");
    this.deleteCategories();
    return;
  }

  FlockSvcUtils.getCoop(this);
  FlockSvcUtils.getACUtils(this);
  FlockSvcUtils.getWD(this);

  this._accountClassCtor = flockWPAccount;

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

  this.account_root = this._coop.accounts_root;

  this.wpService = new this._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();

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

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

/*************************************************************************
 * flockWPService: flockIWebService Implementation
 *************************************************************************/

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

// readonly attribute AString icon;
flockWPService.prototype.icon = WORDPRESS_FAVICON;

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

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

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

flockWPService.prototype.addAccount =
function flockWPService_addAccount(aUsername, aIsTransient, aFlockListener)
{
  var accountURN = this._acUtils.createAccount(this,
                                               aUsername,
                                               aUsername,
                                               null,
                                               aIsTransient);
  var account = this._coop.get(accountURN);
  this.USER = accountURN;

  // Add the blog account
  var wpsvc = this;
  var blogListener = {
    onResult: function(aSimpleEnum) {
      var theBlog;
      while (aSimpleEnum.hasMoreElements()) {
        theBlog = aSimpleEnum.getNext();
        theBlog.QueryInterface(CI.nsIPropertyBag2);
        var blogID = theBlog.getPropertyAsAString("blogID");
        var blogURN = accountURN + ":" + blogID;
        var apiLink = theBlog.getPropertyAsAString("URL") + "xmlrpc.php";
        apiLink = apiLink.replace(/http:/, "https:");
        theCoopBlog = new wpsvc._coop.Blog(blogURN, {
          name: theBlog.getPropertyAsAString("title"),
          title: theBlog.getPropertyAsAString("title"),
          blogid: blogID,
          URL: theBlog.getPropertyAsAString("URL"),
          apiLink: apiLink
        });
        account.children.addOnce(theCoopBlog);
      }
      if (aFlockListener) {
        aFlockListener.onSuccess(acct, "addAccount");
      }
    },
    onFault: function(aError) {
      aError.errorCode = aError.BLOG_INVALID_AUTH;
      wpsvc._coop.accounts_root.children.remove(account);
      account.destroy();
      if(aFlockListener) {
        aFlockListener.onError(aError, "FAULT");
      }
    },
    onError: function(aError) {
      aError.errorCode = aError.BLOG_INVALID_AUTH;
      wpsvc._coop.accounts_root.children.remove(account);
      account.destroy();
      if(aFlockListener) {
        aFlockListener.onError(aError, "ERROR");
      }
    }
  };

  var username = this._coop.get(this.USER).name;
  var pw = this._acUtils.getPassword(this.urn + ":" + username);
  this.getUsersBlogs(blogListener,
                     "https://wordpress.com/xmlrpc.php",
                     username,
                     pw.password);

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

/*************************************************************************
 * flockWPService: flockIBlogWebService interface
 *************************************************************************/

flockWPService.prototype.newPost =
function flockWPService_newPost(aPublishListener,
                                 aBlogId,
                                 aPost,
                                 aPublish,
                                 aNotifications)
{
  var wpService = CC["@flock.com/blog/service/wordpress;1"]
                  .getService(CI.flockIBlogWebService);
  aPost.description = this._youtubize(aPost.description);
  wpService.newPost(aPublishListener, aBlogId, aPost, aPublish, aNotifications);
}

flockWPService.prototype.editPost =
function flockWPService_editPost(aPublishListener,
                                 aBlogId,
                                 aPost,
                                 aPublish,
                                 aNotifications)
{
  var mtSvc = CC["@flock.com/blog/service/movabletype;1"]
              .getService(CI.flockIBlogWebService);
  mtSvc.editPost(aPublishListener, 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 flockWPService_getUsersBlogs(aBlogListener,
                                      aAPILink,
                                      aUsername,
                                      aPassword)
{
  var rpcListener = new userBlogsListener(aBlogListener, false);
  var xmlrpcServer = new flockXmlRpcServer (aAPILink);
  var args = ["flockbrowser", aUsername, aPassword];
  xmlrpcServer.call("blogger.getUsersBlogs", args, rpcListener);
}

flockWPService.prototype.getRecentPosts =
function flockWPService_getRecentPosts(aBlogListener, aBlogId, aNumber) {
  var mtService = CC["@flock.com/blog/service/movabletype;1"]
                  .getService(CI.flockIBlogWebService);
  mtService.getRecentPosts(aBlogListener, 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);
}

/*************************************************************************
 * flockWPService: flockILoginWebService interface
 *************************************************************************/

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

flockWPService.prototype.getAccountIDFromDocument =
function flockWPService_getAccountIDFromDocument(aDocument)
{
  this._logger.debug(".getAccountIDFromDocument(...)");
  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.username;
  }

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

flockWPService.prototype.updateAccountStatusFromDocument =
function flockWPService_updateAccountStatusFromDocument(aDocument,
                                                        aAcctURN,
                                                        aFlockListener)
{
  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._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 url = "http://" + cookie.host + cookie.path;
    var cookieValue = this._acUtils.getCookie(url, cookie.name);
    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(".logout()");
  this._acUtils.removeCookies(this._WebDetective.getSessionCookies("wordpress"));
}


/*************************************************************************
 * Component: flockWPAccount
 *************************************************************************/

function flockWPAccount() {
  FlockSvcUtils.getLogger(this).info(".init()");
  FlockSvcUtils.getACUtils(this);
  FlockSvcUtils.getCoop(this);

  var wsa = FlockSvcUtils.flockIWebServiceAccount;
  wsa.addDefaultMethod(this, "getService");
  wsa.addDefaultMethod(this, "login");
  wsa.addDefaultMethod(this, "logout");
  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 WP Account: XPCOM Component Creation
 **************************************************************************/

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

/**************************************************************************
 * Flock WP Account: private members
 **************************************************************************/

flockWPAccount.prototype._subscribeCommentFeed =
function flockWPAccount__subscribeCommentFeed(aBlogURL) {
  // Create the "My Blogs" folder if needed
  var feedManager = CC["@flock.com/feed-manager;1"]
                    .getService(CI.flockIFeedManager);
  var blogFolder = null;
  var folderEnum = feedManager.getFeedContext("news").getRoot().getChildren();
  while (folderEnum.hasMoreElements() && !blogFolder) {
    var candidate = folderEnum.getNext();
    if (candidate.getTitle() == "My Blogs") {
      blogFolder = candidate;
    }
  }
  if (!blogFolder) {
    blogFolder = feedManager.getFeedContext("news")
                            .getRoot()
                            .addFolder("My Blogs");
  }

  // Subscribe to feed
  var ios = CC["@mozilla.org/network/io-service;1"]
            .getService(CI.nsIIOService);
  var uri = ios.newURI(aBlogURL+"comments/feed/", null, null);
  var feedManagerListener = {
    onGetFeedComplete: function fML_onGetFeedComplete(feed) {
      blogFolder.subscribeFeed(feed);
      CC["@flock.com/metrics-service;1"]
        .getService(CI.flockIMetricsService)
        .report("FeedsSidebar-AddFeed",  "WordPressNewAccount");
    },
    onError: function fML_onError(aError) {
      this._logger.error("Subscription to the comment feed for blog "
                      + aBlogURL
                      + " failed: "
                      + aError);
    }
  }
  feedManager.getFeed(uri, feedManagerListener);
}

/**************************************************************************
 * Flock WP Account: flockIWebServiceAccount Implementation
 **************************************************************************/

flockWPAccount.prototype.keep = function() {
  var c_acct = this._coop.get(this.urn);
  c_acct.isTransient = false;
  this._acUtils.makeTempPasswordPermanent(this.getService().urn
                                          + ":"
                                          + c_acct.accountId);

  // Subscribe to the comments feed for each blog
  var childrenEnum = c_acct.children.enumerate();
  while (childrenEnum.hasMoreElements()) {
    var child = childrenEnum.getNext();
    if (child && child.isInstanceOf(this._coop.Blog)) {
      this._subscribeCommentFeed(child.URL);
    }
  }
}

/**************************************************************************
 * Flock WP Account: flockIBlogAccount Implementation
 **************************************************************************/

flockWPAccount.prototype.getBlogs = function() {
  this._logger.info("{flockIBlogAccount}.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;
}

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

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

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