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

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

const ONET_BLOG_CID = Components.ID("{4E4D024C-A680-4E2C-BE71-C14F29F41F77}");
const ONET_BLOG_CONTRACTID = "@flock.com/webservice/blog/onet;1";

const ONET_BLOG_CLASS_NAME = "Onet.pl Blog";
const CLASS_SHORT_NAME = "onetblog";
const MODULE_NAME = "onetblog";

const ONET_FAVICON = "chrome://flock/content/services/onet/onetFavicon.png";

// Service Enabled Pref
const SERVICE_ENABLED_PREF = "flock.service.onet.enabled";

// Blog
const ONET_BLOG_PREFIX = "http://";
const ONET_BLOG_SUFFIX = ".blog.onet.pl/xmlrpc.html";
const ONET_BLOG_XMLRPC = "http://blog.onet.pl/xmlrpc.html";

const HASH_PROPERTY_BAG_CONTRACTID = "@mozilla.org/hash-property-bag;1";

function userBlogsListener(aBlogListener) {
  FlockSvcUtils.getLogger(this);
  this._logger.init("userBlogsListener");
  this.blogListener = aBlogListener;
}

userBlogsListener.prototype = {
  // aSimpleEnum is an Array (simpleEnumerator) of nsIPropertyBag
  onResult: function userBlogsListener_onResult(aSimpleEnum) {
    this._logger.debug("userBlogs Result: " + aSimpleEnum);
    var result = new Array();
    for each (var entry in aSimpleEnum) {
      var blog = CC[HASH_PROPERTY_BAG_CONTRACTID]
                 .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 userBlogsListener_onError(aFlockError) {
    this._logger.debug("userBlogs Error: " + aFlockError.serviceErrorString);
    this.blogListener.onError(aFlockError);
  },
  onFault: function userBlogsListener_onFault(aFlockError) {
    this._logger.debug("userBlogs Fault: " + 
           aFlockError.serviceErrorCode + " " + aFlockError.serviceErrorString);
    switch (aFlockError.serviceErrorCode) {
      case 0: // Invalid login/pass
        aFlockError.errorCode = CI.flockIError.BLOG_INVALID_AUTH;
        break;
      default: // Unknown error code
        aFlockError.errorCode = CI.flockIError.BLOG_UNKNOWN;
    }
    this.blogListener.onFault(aFlockError);
  }
};

// Create a prototype as we will need this within both createAccount and
// updateAccountStatusFromDocument

// XXX: ADD EXTRA var for account coo removal / onsuccess 
function handleUserBlogsListener(aAccount, aCoop, aFlockListener) {
  FlockSvcUtils.getLogger(this);
  this._logger.init("handleUserBlogsListener");
  this._account = aAccount;
  this._coop = aCoop;
  this.flockListener = aFlockListener;
}

handleUserBlogsListener.prototype = {
  onResult: function blogListener_onResult(aSimpleEnum) {
    this._logger.debug("handleUserBlogsListener:('" + aSimpleEnum + "')");
    var blog;
    while (aSimpleEnum.hasMoreElements()) {
      blog = aSimpleEnum.getNext();
      blog.QueryInterface(CI.nsIPropertyBag2);
      var blogID = blog.getPropertyAsAString("blogID");
      var blogURN = this._account.urn + ":" + blogID;
      var apiLink = blog.getPropertyAsAString("URL") + "xmlrpc.html";
      theCoopBlog = new this._coop.Blog(blogURN, {
        name: blog.getPropertyAsAString("title"),
        title: blog.getPropertyAsAString("title"),
        blogid: blogID,
        URL: blog.getPropertyAsAString("URL"),
        apiLink: apiLink
      });
      this._logger.debug("handleUserBlogsListener: Found a blog: "
                         + blogID);
      this._account.coopObj.children.addOnce(theCoopBlog);
      if (this.flockListener) {
        this.flockListener.onSuccess(this._account, "addAccount");
      }
    }
  },
  onFault: function blogListener_onFault(aError) {
    this._logger.debug("handleUserBlogsListener:('" + aError + "')");
    aError.errorCode = aError.BLOG_INVALID_AUTH;
    if (this.flockListener) {
      this.flockListener.onError(aError, "fault");
    }
  },
  onError: function blogListener_onError(aError) {
    this._logger.debug("handleUserBlogsListener:('" + aError + "')");
    aError.errorCode = aError.BLOG_INVALID_AUTH;
    if (this.flockListener) {
      this.flockListener.onError(aError, "error");
    }
  }
};

/*************************************************************************
 * Component: OnetService
 *************************************************************************/
function OnetBlogService() {
  var loader = CC["@mozilla.org/moz/jssubscript-loader;1"]
               .getService(CI.mozIJSSubScriptLoader);
  loader.loadSubScript("chrome://flock/content/xmlrpc/xmlrpchelper.js");
  loader.loadSubScript("chrome://flock/content/blog/blogBackendLib.js");

  OnetUtils.initService(this, CLASS_SHORT_NAME, ONET_BLOG_CONTRACTID);

  // Initialize account constructor
  this._accountClassCtor = OnetBlogAccount;

  FlockSvcUtils.flockILoginWebService.addDefaultMethod(this, "logout");
}

/*************************************************************************
 * OnetService: XPCOM Component Creation
 *************************************************************************/

OnetBlogService.prototype = new FlockXPCOMUtils.genericComponent(
  ONET_BLOG_CLASS_NAME,
  ONET_BLOG_CID,
  ONET_BLOG_CONTRACTID,
  OnetBlogService,
  CI.nsIClassInfo.SINGLETON,
  [
    CI.flockIWebService,
    CI.flockIAuthenticateNewAccount,
    CI.flockILoginWebService,
    CI.flockIBlogWebService,
  ]
);

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

/*************************************************************************
 * OnetService: flockIWebService Implementation
 *************************************************************************/

// readonly attribute AString title;
OnetBlogService.prototype.title = ONET_BLOG_CLASS_NAME;

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

// readonly attribute AString icon;
OnetBlogService.prototype.icon = ONET_FAVICON;

// DEFAULT: readonly attribute AString url;

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

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

// DEFAULT: nsIStringBundle getStringBundle();
// DEFAULT: boolean isHidden();

/*************************************************************************
 * OnetService: flockILoginWebService interface
 *************************************************************************/

// readonly attribute AString domains;
OnetBlogService.prototype.__defineGetter__("domains",
function OnetBlogService_getdomains() {
  this._logger.debug("Getting attribute: domains");

  return this._wd.getString(CLASS_SHORT_NAME, "domains", "");
});

 // readonly attribute boolean needPassword;
OnetBlogService.prototype.needPassword = false;

// readonly attribute AString loginURL;
OnetBlogService.prototype.__defineGetter__("loginURL",
function OnetBlogService_getloginURL() {
  this._logger.debug("Getting attribute: loginURL");
  return this._wd.getString(CLASS_SHORT_NAME, "loginURL", "");
});

/**
 * flockIWebServiceAccount addAccount (in AString aAccountId,
 *                                     in boolean aIsTransient,
 *                                     in flockIListener aFlockListener)
 * @see flockILoginWebService#addAccount
 */
OnetBlogService.prototype.addAccount =
function OnetBlogService_addAccount(aAccountId, aIsTransient, aFlockListener) {
  this._logger.debug(".addAccount('" + aAccountId + "', "
                                     + aIsTransient + ", aFlockListener)");

  if (!aAccountId) {
    if (aFlockListener) {
      var error = OnetUtils.api.getError(999, "No Account ID provided");
      aFlockListener.onError(error, arguments.callee.name);
    }
    return null;
  }
  aAccountId = aAccountId.toLowerCase();

  var pw = this._acUtils.getPassword(this.urn + ":" + aAccountId);
  var username = (pw) ? pw.username : aAccountId;

  var accountURN = this._acUtils.createAccount(this,
                                               aAccountId,
                                               username,
                                               null,
                                               aIsTransient);
  var account = this.getAccount(accountURN);
  account.setParam("service", this._coopObj);

  // When creating a blog, the user can specify a subdomain of their choice
  // which doesn't neccessarily need to match the username, let's assign a
  // custom parameter for the blog subdomain
  account.setCustomParam("blogSubdomain", "");

  // Extract the base username in an attempt to find the user's blog
  this.getUsersBlogs(new handleUserBlogsListener(account,
                                                 this._coop,
                                                 aFlockListener),
                     ONET_BLOG_XMLRPC,
                     username,
                     pw.password);
  return account;
};

// DEFAULT: boolean docRepresentsSuccessfulLogin (in nsIDOMHTMLDocument aDocument);
// DEFAULT: flockIWebServiceAccount getAccount(in AString aAccountUrn);
// DEFAULT: AString getAccountIDFromDocument(in nsIDOMHTMLDocument aDocument);
// DEFAULT: nsIStringEnumerator getAccountUrns();
// DEFAULT: flockIWebServiceAccount getAuthenticatedAccount();
// DEFAULT: nsIPassword getCredentialsFromForm(in nsIDOMHTMLFormElement aForm);
// DEFAULT: boolean ownsDocument (in nsIDOMHTMLDocument aDocument);
// DEFAULT: boolean ownsLoginForm (in nsIDOMHTMLFormElement aForm)
// DEFAULT: void logout (in flockIListener aFlockListener)
// DEFAULT: void removeAccount (in AString aUrn);

/**
 * void updateAccountStatusFromDocument (in nsIDOMHTMLDocument aDocument,
 *                                       flockIWebServiceAccount aAccount,
 *                                       in flockIListener aFlockListener)
 * @see flockILoginWebService#updateAccountStatusFromDocument
 */
OnetBlogService.prototype.updateAccountStatusFromDocument =
function OnetBlogService_updateAccountStatusFromDocument(aDocument,
                                                         aAccount,
                                                         aFlockListener)
{
  if (aAccount) {
    // We are currently logged in to aAccount

    // Upon registration, if the user has not already created a blog, or
    // has a blog subdomain which is different that their username the blog
    // isn't registered with coop. If we are on an authorized blog admin page
    // and find a blog subdomain, add the blog for the user.
    if (!aAccount.getCustomParam("blogSubdomain") &&
        this._wd.detect(this.shortName, "blog", aDocument, results))
    {
      var results = FlockSvcUtils.newResults();
      var blogSubdomain = null;
      try {
        blogSubdomain = results.getPropertyAsAString("blogSubdomain");
        this._logger.debug(".updateAccountStatusFromDocument() A new blog "
                           + "subdomain was found: " + blogSubdomain);
      } catch (ex) {
        this._logger.debug("Could not find a blog subdomain on this page.");
      }

      if (blogSubdomain) {
        // Set the custom param for future reference
        aAccount.setCustomParam("blogSubdomain", blogSubdomain);

        // Setup the blog listener to add to the coop.
        var accountId = aAccount.getParam("accountId");

        // Supply a blank username and password as we are authenticated here.
        // Onet's Blog XMLRPC looks at the session cookies for authenticating
        // this call

        // The API URL for a user blog are in the form:
        // http://{blogSubdomain}.blog.onet.pl/xmlrpc.html
        this.getUsersBlogs(new handleUserBlogsListener(aAccount,
                                                       this._coop,
                                                       null),
                           ONET_BLOG_PREFIX + blogSubdomain + ONET_BLOG_SUFFIX,
                           "",
                           "");
      }
    }

    if (!aAccount.isAuthenticated()) {
      // Login the account and authenticate api
      this._authMgr.reportCompleteLoginToNetwork(aAccount);
      aAccount.login(aFlockListener);
    }
  } else if (this._wd.detect(this.shortName, "loggedout", aDocument, null)) {
    // We're logged out (of all accounts), logout and deauthenticate the api
    this._acUtils.markAllAccountsAsLoggedOut(this);
    this._authMgr.reportCompleteLogoutFromNetwork(ONET_BLOG_CONTRACTID);
  }
};

/*************************************************************************
 * OnetBlogService: flockIAuthenticateNewAccount interface
 *************************************************************************/

OnetBlogService.prototype.authenticateNewAccount =
function OnetBlogService_authenticateNewAccount(aFlockListener) {
  this._logger.info("authenticateNewAccount(aFlockListener)");
  var wm = CC["@mozilla.org/appshell/window-mediator;1"]
           .getService(CI.nsIWindowMediator);
  var win = wm.getMostRecentWindow("navigator:browser");
  win.openUILinkIn(this.url, "tab");
};


/*************************************************************************
 * OnetService: flockIBlogWebService interface
 *************************************************************************/

// attribute AString authtoken
OnetBlogService.prototype.authtoken = "";

// attribute AString metadataOverlay
OnetBlogService.prototype.metadataOverlay = "";

// attribute long supportsCategories
OnetBlogService.prototype.supportsCategories = CI.flockIBlogWebService.CATEGORIES_NO;

// attribute boolean supportsPostReplace
OnetBlogService.prototype.supportsPostReplace = true;


/**
 * void editPost (in flockIBlogPublishListener aPublishListener,
 *                in AString aBlogId,
 *                in flockIBlogPost aPost,
 *                in boolean aPublish,
 *                in nsIStringEnumerator aNotifications)
 * @see flockIBlogWebService#editPost
 */
OnetBlogService.prototype.editPost =
function OnetBlogService_editPost(aPublishListener,
                                  aBlogId,
                                  aPost,
                                  aPublish,
                                  aNotifications)
{
  this._logger.info("editPost() for aBlogId: " + aBlogId);
  var mtService = CC["@flock.com/blog/service/movabletype;1"]
                  .getService(CI.flockIBlogWebService);
  mtService.editPost(aPublishListener, aBlogId, aPost,
                     aPublish, aNotifications);
};

OnetBlogService.prototype.deletePost =
function OnetBlogService_deletePost(aPublishListener, aBlogId, aPostid) {
  this._logger.info("deletePost() for aBlogId: " + aBlogId);
  var mtService = CC["@flock.com/blog/service/movabletype;1"]
                  .getService(CI.flockIBlogWebService);
  mtService.deletePost(aPublishListener, aBlogId, aPostid);
};

/**
 * void getCategoryList (in flockIBlogListener aBlogListener,
 *                       in AString aBlogId)
 * @see flockIBlogWebService#getCategoryList
 */
OnetBlogService.prototype.getCategoryList =
function OnetBlogService_getCategoryList(aBlogListener, aBlogId) {
  this._logger.info("getCategoryList() for aBlogId: " + aBlogId);
  var mtService = CC["@flock.com/blog/service/movabletype;1"]
                  .getService(CI.flockIBlogWebService);
  var catBlogListener = {
    onResult: function catBlogListener_onResult(aResult) {
      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);
      }
      aBlogListener.onResult(new simpleEnumerator(result));
    },
    onError: function catBlogListener_onError(aError) {
      aBlogListener.onError(aError);
    },
    onFault: function catBlogListener_onFault(aError) {
      aBlogListener.onFault(aError);
    }
  };
  mtService.getCategoryList(catBlogListener, aBlogId);
};

/**
 * void getRecentPosts (in flockIBlogListener aBlogListener,
 *                      in AString aBlogId,
 *                      in PRUint32 aNumber)
 * @see flockIBlogWebService#getRecentPosts
 */
OnetBlogService.prototype.getRecentPosts =
function OnetBlogService_getRecentPosts(aBlogListener, aBlogId, aNumber) {
  this._logger.info("getRecentPosts() for aBlogId: " + aBlogId);
  var mtService = CC["@flock.com/blog/service/movabletype;1"]
                  .getService(CI.flockIBlogWebService);
  mtService.getRecentPosts(aBlogListener, aBlogId, aNumber);
};

/**
 * void getUsersBlogs (in flockIBlogListener aBlogListener,
 *                     in AString aAPILink,
 *                     in AString aUsername,
 *                     in AString aPassword)
 * @see flockIBlogWebService#getUsersBlogs
 */
OnetBlogService.prototype.getUsersBlogs =
function OnetBlogService_getUsersBlogs(aBlogListener,
                                       aApiLink,
                                       aUsername,
                                       aPassword)
{
  this._logger.info("getUsersBlogs() for URL: " + aApiLink);
  var rpcListener = new userBlogsListener(aBlogListener, false);
  var xmlrpcServer = new flockXmlRpcServer(aApiLink);
  var args = ["flockbrowser", aUsername, aPassword];
  xmlrpcServer.call("blogger.getUsersBlogs", args, rpcListener);
};

/**
 * void newPost (in flockIBlogPublishListener aPublishListener,
 *               in AString aBlogId,
 *               in flockIBlogPost aPost,
 *               in boolean aPublish,
 *               in nsIStringEnumerator aNotifications)
 * @see flockIBlogWebService#newPost
 */
OnetBlogService.prototype.newPost =
function OnetBlogService_newPost(aPublishListener,
                                 aBlogId,
                                 aPost,
                                 aPublish,
                                 aNotifications)
{
  this._logger.info("newPost() for aBlogId: " + aBlogId);
  var mtService = CC["@flock.com/blog/service/movabletype;1"]
                  .getService(CI.flockIBlogWebService);
  mtService.newPost(aPublishListener, aBlogId, aPost, aPublish, aNotifications);
};

OnetBlogService.prototype.setPostCategories =
function OnetBlogService_setPostCategories(aPublishListener,
                                           aBlogId,
                                           aPostId,
                                           aCategories)
{
  // Not yet implemented
  this._logger.info("setPostCategories() for aBlogId: " + aBlogId);
  aPublishListener.onError(null, arguments.callee.name);
};

/*************************************************************************
 * Component: OnetBlogAccount
 *************************************************************************/
function OnetBlogAccount() {
  FlockSvcUtils.getLogger(this);
  this._logger.init("OnetBlogAccount");

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

  var wsa = FlockSvcUtils.flockIWebServiceAccount;
  wsa.addDefaultMethod(this, "getService");
  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");
}

/**************************************************************************
 * OnetBlogAccount: XPCOM Component Creation
 **************************************************************************/

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

/**************************************************************************
 * OnetBlogAccount: flockIWebServiceAccount Implementation
 **************************************************************************/

// DEFAULT: nsIPropertyBag getAllCustomParams ()
// DEFAULT: nsIVariant getCustomParam (in AString aParamName)
// DEFAULT: nsIVariant getParam (in AString aParamName)
// DEFAULT: flockILoginWebService getService()
// DEFAULT: boolean isAuthenticated ()

/**
 * void keep()
 * @see flockIWebServiceAccount#keep
 */
OnetBlogAccount.prototype.keep =
function OnetAccount_keep() {
  this._logger.debug("OnetBlogAccount_keep()");
  this.coopObj.isTransient = false;
  this._acUtils.makeTempPasswordPermanent(this.getService().urn
                                          + ":"
                                          + this.coopObj.accountId);
};

/**
 * void login(in flockIListener aFlockListener)
 * @see flockIWebServiceAccount#login
 */
OnetBlogAccount.prototype.login =
function OnetBlogAccount_login(aFlockListener) {
  OnetUtils.loginAccount(this, CLASS_SHORT_NAME, aFlockListener);
};

// DEFAULT: void logout (in flockIListener aFlockListener)
// DEFAULT: void setCustomParam (in AString aParamName, in nsIVariant aValue)
// DEFAULT: void setParam (in AString aParamName, in nsIVariant aValue)

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

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

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