// 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/FlockXPCOMUtils.jsm");
FlockXPCOMUtils.debug = false;
Cu.import("resource:///modules/FlockSvcUtils.jsm");

const MODULE_NAME = "typepad";

const TYPEPAD_CID = Components.ID('{b83be4a8-c826-4e24-b8dd-83c2af2d6735}');
const TYPEPAD_NAME = 'Flock Typepad Service';
const TYPEPAD_CONTRACTID = '@flock.com/blog/typepad;1';

const TYPEPAD_FAVICON = "chrome://flock/content/services/typepad/favicon.png";
const SERVICE_ENABLED_PREF = "flock.service.typepad.enabled";
const CATEGORY_COMPONENT_NAME = "Typepad JS Component";
const CATEGORY_ENTRY_NAME = "typepad";

const CLASS_SHORT_NAME = "typepad";
const CLASS_NAME = "Typepad";

function loadSubScript(spec) {
  var loader = Cc['@mozilla.org/moz/jssubscript-loader;1'].getService(Ci.mozIJSSubScriptLoader);
  var context = {};
  loader.loadSubScript(spec, context);
  return context;
}

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

userBlogsListener.prototype =
{
  // aSimpleEnum is an Array of struct objects
  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(aFlockError) {
    this.blogListener.onError(aFlockError);
  },
  onFault: function ubl_onFault(aFlockError) {
    switch (aFlockError.serviceErrorCode) {
      case 403: // Invalid login/pass
        aFlockError.errorCode = Ci.flockIError.BLOG_INVALID_AUTH;
        break;
      default: // Unknown error code
        aFlockError.errorCode = Ci.flockIError.BLOG_UNKNOWN_ERROR;
    }
    this.blogListener.onFault(aFlockError);
  }
}

/*************************************************************************
 * Component: Flock Typepad Service
 *************************************************************************/

function flockTPService () {
  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.lastPassword = null;
  this.initialized = false;

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

  this._init();

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

  FlockSvcUtils.flockILoginWebService.addDefaultMethod(this, "loginURL");
  FlockSvcUtils.flockILoginWebService.addDefaultMethod(this, "tooltip");
  FlockSvcUtils.flockILoginWebService.addDefaultMethod(this, "getAccount");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "getAuthenticatedAccount");
  FlockSvcUtils.flockILoginWebService.addDefaultMethod(this, "getAccountUrns");
  FlockSvcUtils.flockILoginWebService.addDefaultMethod(this, "removeAccount");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "docRepresentsSuccessfulLogin");
  FlockSvcUtils.flockILoginWebService.addDefaultMethod(this, "ownsDocument");
  FlockSvcUtils.flockILoginWebService.addDefaultMethod(this, "ownsLoginForm");
  FlockSvcUtils.flockILoginWebService.addDefaultMethod(this, "getSessionValue");
  FlockSvcUtils.flockILoginWebService.addDefaultMethod(this, "logout");
}

/*************************************************************************
 * flockTPService: XPCOM Component Creation
 *************************************************************************/

flockTPService.prototype = new FlockXPCOMUtils.genericComponent(
  TYPEPAD_NAME,
  TYPEPAD_CID,
  TYPEPAD_CONTRACTID,
  flockTPService,
  Ci.nsIClassInfo.SINGLETON,
  [
    Ci.flockIWebService,
    Ci.flockIAuthenticateNewAccount,
    Ci.flockILoginWebService,
    Ci.flockIBlogWebService
  ]
);

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

/*************************************************************************
 * flockTPService: Private Members
 *************************************************************************/

flockTPService.prototype._init =
function flockTPService__init() {
  FlockSvcUtils.getLogger(this).info(".init()");

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

  this._accountClassCtor = flockTPAccount;

  var evtID = this._profiler.profileEventStart("typepad-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.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;
  }

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

  // getWD() also adds the "_acUtils" property.
  FlockSvcUtils.getWD(this);
  for (var s in gStrings) {
    gStrings[s] = this._WebDetective.getString("typepad", s, gStrings[s]);
  }
  this.domains = gStrings["domains"];
  FlockSvcUtils.initService(this);

  this.account_root = this._coop.accounts_root;

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

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

/*************************************************************************
 * flockTPService: flockIWebService implementation
 *************************************************************************/

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

// readonly attribute AString icon;
flockTPService.prototype.icon = TYPEPAD_FAVICON;

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

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

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

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

flockTPService.prototype.addAccount =
function flockTPService_addAccount(aUsername, aIsTransient, aFlockListener)
{
  var accountURN = this._acUtils.createAccount(this,
                                              aUsername,
                                              aUsername,
                                              null,
                                              aIsTransient);
  var account = this.getAccount(accountURN);
  account.setParam("service", this._coopObj);
  this.USER = accountURN;

  // Add the blog account
  var wpsvc = this;
  var blogListener = {
    onResult: function bl_onResult(aSimpleEnum) {
      var theBlog;
      while (aSimpleEnum.hasMoreElements()) {
        theBlog = aSimpleEnum.getNext();
        theBlog.QueryInterface(Ci.nsIPropertyBag2);
        var blogID = theBlog.getPropertyAsAString("blogID");
        var title = theBlog.getPropertyAsAString("title");
        var URL = theBlog.getPropertyAsAString("URL");
        var blogURN = accountURN + ":" + title;
        theCoopBlog = new wpsvc._coop.Blog(blogURN, {
          name: title,
          title: title,
          blogid: blogID,
          URL: URL,
          apiLink: "https://www.typepad.com/t/api"
        });
        account.coopObj.children.addOnce(theCoopBlog);
      }
      if (aFlockListener) {
        aFlockListener.onSuccess(account, "addAccount");
      }
    },
    onFault: function bl_onFault(aFlockError) {
      account.coopObj.destroy();
      if(aFlockListener) {
        aFlockListener.onError(aFlockError, "FAULT");
      }
    },
    onError: function bl_onError(aFlockError) {
      account.coopObj.destroy();
      if(aFlockListener) {
        aFlockListener.onError(aFlockError, "ERROR");
      }
    }
  };

  var apiLink = "https://www.typepad.com/t/api";
  var pw = this._acUtils.getPassword(this.urn + ":" + aUsername);
  this.getUsersBlogs(blogListener, apiLink, aUsername, pw.password);

  return account;
}

var gStrings = {
  "domains": "typepad.com",
}

/*************************************************************************
 * flockTPService: flockIBlogWebService implementation
 *************************************************************************/

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

flockTPService.prototype.editPost =
function(aPublishListener, aBlogId, aPost, aPublish, aNotifications){
  // [bug 8596] Setting categories in editPost doesn't work well on Typepad,
  // so we need to do it manually
  var mtService = Cc['@flock.com/blog/service/movabletype;1'].getService(Ci.flockIBlogWebService);

  var categories = [];
  while (aPost.categories.hasMoreElements()) {
    var category = aPost.categories.getNext();
    category.QueryInterface(Ci.flockIBlogCategory);
    categories.push(category);
  }

  var listener = {
    onResult: function(aResult) {
      var gBlogService = Cc['@flock.com/flock-blog;1'].getService(Ci.flockIBlogService);
      mtService.setPostCategories(aPublishListener,
                                  aBlogId,
                                  aPost.postid,
                                  new simpleEnumerator(categories));
    },
    onError: aPublishListener.onError,
    onFault: aPublishListener.onFault
  };

  mtService.editPost(listener, aBlogId, aPost, aPublish, aNotifications);
}

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

flockTPService.prototype.getUsersBlogs =
function flockTPService_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);
}

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

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

/*************************************************************************
 * flockTPService: flockILoginWebService implementation
 *************************************************************************/

flockTPService.prototype.getAccountIDFromDocument =
function flockTPService_getAccountIDFromDocument(aDocument)
{
  this._logger.debug(".getAccountIDFromDocument(...)");

  var results = Cc["@mozilla.org/hash-property-bag;1"]
                .createInstance(Ci.nsIWritablePropertyBag2);
  
  if (this._WebDetective.detect("typepad", "accountinfo", aDocument, results)) {
    return results.getPropertyAsAString("accountid");
  } else if (this.lastPassword) {
    return this.lastPassword.user;
  } else {
    return null;
  }
}

flockTPService.prototype.getCredentialsFromForm =
function flockTPService_getCredentialsFromForm(aForm)
{
  this._logger.debug(".getCredentialsFromForm(...)");
  aForm.QueryInterface(Components.interfaces.nsIDOMHTMLFormElement);
  // Keep it for getAccountIDFromDocument
  this.lastPassword = this._acUtils.extractPasswordFromHTMLForm(aForm);
  return {
    QueryInterface: function(aIID) {
      if (!aIID.equals(Components.interfaces.nsISupports) &&
          !aIID.equals(Components.interfaces.nsILoginInfo) &&
          !aIID.equals(Components.interfaces.flockILoginInfo)) { 
        throw Components.interfaces.NS_ERROR_NO_INTERFACE;
      }
      return this;
    },
    hostname: "",
    username: this.lastPassword.username,
    password: this.lastPassword.password,
    formType: "login"
  };
//  return this.lastPassword;
}


flockTPService.prototype.updateAccountStatusFromDocument =
function flockTPService_updateAccountStatusFromDocument(aDocument,
                                                        aAccount,
                                                        aAuthListener)
{
  if (aAccount) {
    this._acUtils.ensureOnlyAuthenticatedAccount(aAccount);
  } else if (this._WebDetective
                 .detect("typepad", "loggedout", aDocument, null))
  {
    this._acUtils.markAllAccountsAsLoggedOut(this);
  }
}

/*************************************************************************
 * Component: flockTPAccount
 *************************************************************************/

function flockTPAccount() {
  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, "keep");
  wsa.addDefaultMethod(this, "getParam");
  wsa.addDefaultMethod(this, "setParam");
  wsa.addDefaultMethod(this, "getCustomParam");
  wsa.addDefaultMethod(this, "getAllCustomParams");
  wsa.addDefaultMethod(this, "setCustomParam");
  wsa.addDefaultMethod(this, "isAuthenticated");
}

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

flockTPAccount.prototype = new FlockXPCOMUtils.genericComponent(
  TYPEPAD_NAME + " Account",
  "",
  "",
  flockTPAccount,
  0,
  [
    Ci.flockIWebServiceAccount,
    Ci.flockIBlogAccount
  ]
);

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

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

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