// 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 Cu = Components.utils;

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

const FLOCK_MW_CID =  Components.ID('{143c7ec8-6ae9-473a-9289-e19321bfa72e}');
const FLOCK_MW_CONTRACTID = '@flock.com/blog/service/metaweblog;1';
const FLOCK_MW_DESCRIPTION = 'MetaWeblog';

const FLOCK_BLOG_RDF_SETTINGS     = 'urn:flock:blog:settings';

function loadLibraryFromSpec(aSpec) {
  var loader = Components.classes['@mozilla.org/moz/jssubscript-loader;1']
    .getService(Components.interfaces.mozIJSSubScriptLoader);

  loader.loadSubScript(aSpec);
}


// CONSTRUCTOR
function metaweblogWebService () {
  loadLibraryFromSpec("chrome://flock/content/blog/technoratiTags.js");
  loadLibraryFromSpec("chrome://flock/content/blog/blogBackendLib.js");
  loadLibraryFromSpec("chrome://flock/content/xmlrpc/xmlrpchelper.js");
  //this.mQueue = null;

  // set attributes
  this.name = 'MetaWeblog';
  this.shortName = 'metaweblog';
  this.description = "The MetaWeblog API"
  this.supportsCategories = Ci.flockIBlogWebService.CATEGORIES_MULTIPLE;
  this.supportsPostReplace = true;
  this.appKey = "";

  // Logger
  this.logger = Components.classes['@flock.com/logger;1']
    .createInstance(Components.interfaces.flockILogger);
  this.logger.init("blog");

  this._accountClassCtor = metaweblogAccount;
  FlockSvcUtils.flockILoginWebService.addDefaultMethod(this, "getAccount");
}

// the nsISupports implementation
metaweblogWebService.prototype.QueryInterface = function (iid) {
  if (iid.equals(Ci.flockICustomBlogWebService) ||
      iid.equals(Ci.flockIBlogWebService) || 
      iid.equals(Ci.flockIWebService) || 
      iid.equals(Ci.nsIClassInfo) || 
      iid.equals(Ci.nsISupports)) {
    return this;
  }

  throw Components.results.NS_ERROR_NO_INTERFACE;
}


// Listeners
function blogListener(aListener, aBool){
  this.listener = aListener;
  this.container = aBool;
  this.logger = Components.classes['@flock.com/logger;1']
    .createInstance(Components.interfaces.flockILogger);
  this.logger.init("blog");
}

blogListener.prototype = 
{
  // aResult is an Array of struct objects
  onResult: function bl_onResult(aResult) {
    this.logger.info("<<<<<<<<<< MetaWeblog API: SERVER TO FLOCK");
    // Create an in-memory datasource
    var rdfService = Components.classes["@mozilla.org/rdf/rdf-service;1"]
      .getService(Components.interfaces.nsIRDFService);
    var rdf = Components.classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].createInstance(Components.interfaces.nsIRDFDataSource);
    var rdfContainerUtils = Components.classes["@mozilla.org/rdf/container-utils;1"]
      .getService(Components.interfaces.nsIRDFContainerUtils);
    // Fill it with the results
    if (this.container)
      var container = rdfContainerUtils.MakeSeq(rdf, rdfService.GetResource("urn:flock:blog:elements"));
    for each ( var entry in aResult) {
      var id='obj_' + Math.round(10000*Math.random())
      var subject = rdfService.GetResource("http://www.flock.com/rdf/"+id);
      var msg = "";
      for (j in entry){
        var value = entry[j];
        msg += j + "=" + value + " ";
        var predicate = rdfService.GetResource("http://www.flock.com/rdf/"+j);
        var name = rdfService.GetLiteral(value);
        
        rdf.Assert(subject, predicate, name, true);
      }
      this.logger.debug("Found category: " + msg);
      if (this.container)
        container.AppendElement(subject);
    }
 
    // and return the data source through the listener
    this.listener.onResult(rdf);
  },
  onError: function bl_onError(error) {
    this.logger.error("<<<<<<<<<< MetaWeblog API: SERVER TO FLOCK");
    this.logger.error("ERROR " + error.serviceErrorString);
    this.listener.onError(error);
  }, 
  onFault: function bl_onFault(error) {
    this.logger.error("<<<<<<<<<< MetaWeblog API: SERVER TO FLOCK");
    this.logger.error("FAULT " + error.serviceErrorString);
    this.listener.onFault(error);
  } 
}

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

blogCategoryListener.prototype = 
{
  onResult: function bcl_onResult(aResult) {
    this.logger.info("<<<<<<<<<< Metaweblog API: SERVER TO FLOCK");
    var result = new Array();
    var i = 0;
    for each (var entry in aResult) {
      var description = entry.description;
      this.logger.debug("Found category: description=" + description);
      var category = new BlogCategory(i++, description);
      result.push(category);
    }
 
    // and return the data source through the listener
    this.listener.onResult(new simpleEnumerator(result));
  },
  onError: function bcl_onError(aFlockError) {
    this.logger.error("<<<<<<<<<< Metaweblog API: SERVER TO FLOCK");
    this.logger.error("ERROR " + aFlockError.serviceErrorString);
    this.listener.onError(aFlockError);
  }, 
  onFault: function bcl_onFault(error) {
    this.logger.error("<<<<<<<<<< Metaweblog API: SERVER TO FLOCK");
    this.logger.error("FAULT " + aFlockError.serviceErrorCode
                      + " " + aFlockError.serviceErrorString);
    this.listener.onFault(aFlockError);
  } 
}

metaweblogWebService.prototype.detectAccount = function (aUrl, aLinkList){
  return { wrappedJSObject: null };
}


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

  var svc = this;

  var notifsArray = [];
  var catArray = [];
  var html = aPost.description + addTechnoratiTags(aPost.tags);
  while (aPost.categories.hasMoreElements()) {
    var category = aPost.categories.getNext();
    category.QueryInterface(Ci.flockIBlogCategory);
    catArray.push(category.label);
  }

  var listener = {
    onResult: function(aResult) {
      aPublishListener.onResult(aResult);
    },
    onError: function(error) {
      svc.logger.error("<<<<<<<<<< MetaWeblog API: SERVER TO FLOCK");
      svc.logger.error("ERROR " + error.serviceErrorString);
      aPublishListener.onError(error);
    }, 
    onFault: function(error) {
      svc.logger.error("<<<<<<<<<< MetaWeblog API: SERVER TO FLOCK");
      svc.logger.error("FAULT " + error.serviceErrorString);
      aPublishListener.onFault(error);
    } 
  };

  var xmlrpcServer = new flockXmlRpcServer (account.apiLink);
  var args = [account.blogid, account.username, account.password, 
              {title: aPost.title, description: html, categories: catArray,
               mt_convert_breaks: "", mt_tb_ping_urls: notifsArray}, aPublish];
  xmlrpcServer.call("metaWeblog.newPost", args, listener);
}

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

  var svc = this;
  var listener = {
    onResult: function(aResult) {
      aPublishListener.onResult(aResult);
    },
    onError: function(error) {
      svc.logger.error("<<<<<<<<<< MetaWeblog API: SERVER TO FLOCK");
      svc.logger.error("ERROR " + error.serviceErrorString);
      aPublishListener.onError(error);
    }, 
    onFault: function(error) {
      svc.logger.error("<<<<<<<<<< MetaWeblog API: SERVER TO FLOCK");
      svc.logger.error("FAULT " + error.serviceErrorString);
      aPublishListener.onFault(error);
    } 
  };

  var notifsArray = new Array();
  var html = aPost.description + addTechnoratiTags(aPost.tags);
  while (aPost.categories.hasMoreElements()) {
    var category = aPost.categories.getNext();
    category.QueryInterface(Ci.flockIBlogCategory);
    catArray.push(category.label);
  }

  var xmlrpcServer = new flockXmlRpcServer (account.apiLink);
  var args = [aPost.postid, account.username, account.password, 
              {title: aPost.title, description: html, categories: catArray,
               dateCreated: new Date(parseInt(aPost.issued))}, aPublish];
  xmlrpcServer.call("metaWeblog.editPost", args, listener);
}

metaweblogWebService.prototype.deletePost = function(aListener, aAccount, aPostid){
  dump("***** metaweblogWebService.deletePost: not supported!\n");
}

metaweblogWebService.prototype.getUsersBlogs =
function metaweblogWebService_getUsersBlogs(aBlogListener,
                                            aAPILink,
                                            aUsername,
                                            aPassword)
{
  var svc = this;
  var listener = {
    // aResult is an Array of struct objects
    onResult: function ub_onResult(aResult) {
      var result = new Array();
      for (i=0; i<aResult.length; i++){
        var entry = aResult[i];
        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);
      }
      aBlogListener.onResult(new simpleEnumerator(result), null);
    },
    onError: function ub_onError(aFlockError) {
      svc.logger.error("<<<<<<<<<< Metaweblog API: SERVER TO FLOCK");
      svc.logger.error("ERROR " + aFlockError.serviceErrorString);
      aBlogListener.onError(aFlockError);
    }, 
    onFault: function ub_onFault(aFlockError) {
      svc.logger.error("<<<<<<<<<< Metaweblog API: SERVER TO FLOCK");
      svc.logger.error("FAULT " + aFlockError.serviceErrorCode
                       + " " + aFlockError.serviceErrorString);
      switch (aFlockError.serviceErrorCode) {
        case 403: // Invalid login/pass
        case 801: // At least dotclear uses code 801 for bad auth
          aFlockError.errorCode = Ci.flockIError.BLOG_INVALID_AUTH;
          break;
        default: // Unknown error code
          aFlockError.errorCode = Ci.flockIError.BLOG_UNKNOWN_ERROR;
      }
      aBlogListener.onFault(aFlockError);
    } 
  }
  
  var xmlrpcServer = new flockXmlRpcServer (aAPILink);
  var args = [this.appKey, aUsername, aPassword];
  xmlrpcServer.call("blogger.getUsersBlogs", args, listener);
}

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

  var listener = {
    onResult: function(aResult) {
      var result = new Array();
      // svc.logger.info("<<<<<<<<<< MetaWeblog API: SERVER TO FLOCK\n");
      for (i=0; i<aResult.length; i++) {
        var post = new BlogPost();
        post.title = aResult[i].title;
        post.issued = aResult[i].dateCreated.getTime();
        post.postid = aResult[i].postid;
        result.push(post);
      }
 
      // and return the data source through the listener
      aBlogListener.onResult(new simpleEnumerator(result));
    },
    onError: function(error) {
      //svc.logger.error("<<<<<<<<<< MetaWeblog API: SERVER TO FLOCK");
      svc.logger.error("ERROR " + error.serviceErrorString);
      aBlogListener.onError(error);
    }, 
    onFault: function(error) {
      //svc.logger.error("<<<<<<<<<< MetaWeblog API: SERVER TO FLOCK");
      svc.logger.error("FAULT " + error.serviceErrorString);
      aBlogListener.onFault(error);
    } 
  }

  var xmlrpcServer = new flockXmlRpcServer (account.apiLink);
  var args = [account.blogid, account.username, account.password, aNumber];
  xmlrpcServer.call("metaWeblog.getRecentPosts", args, listener);
}

metaweblogWebService.prototype.getCategoryList = function(aBlogListener, aBlogId){
  var gBlogService = Cc['@flock.com/flock-blog;1'].getService(Ci.flockIBlogService);
  var account = gBlogService.getAccount(aBlogId).wrappedJSObject;

  var svc = this;
  var listener = new blogCategoryListener(aBlogListener, false);
  var xmlrpcServer = new flockXmlRpcServer (account.apiLink);
  var args = [account.blogid, account.username, account.password];
  xmlrpcServer.call("metaWeblog.getCategories", args, listener);
}


metaweblogWebService.prototype.removeAccount = function (aId) {
  if (!this.logger)
    this.setup();

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

  var account = faves_coop.get(aId);
  
  this.logger.debug("REMOVE: "+aId+" - "+account.accountId);

  // Remove the password
  acUtils.removeAllPasswordsForHost(aId);

  // Destroy the blogs and the account
  var _enum = account.children.enumerate();
  while (_enum.hasMoreElements()) {
    var blog = _enum.getNext();
    account.children.remove(blog);
    blog.destroy();
  }
  
  account.destroy();
}

metaweblogWebService.prototype.flags = Ci.nsIClassInfo.SINGLETON;
metaweblogWebService.prototype.contractID = FLOCK_MW_CONTRACTID;
metaweblogWebService.prototype.classDescription = "Flock Metaweblog API Service";
metaweblogWebService.prototype.getInterfaces = function (count) {
  var interfaceList = [Ci.flockIWebService,
    Ci.flockICustomBlogWebService,
    Ci.flockIBlogWebService,
  	Ci.nsIClassInfo,
  	Ci.nsISupports];
  count.value = interfaceList.length;
  return interfaceList;
}
metaweblogWebService.prototype.getHelperForLanguage = function (count) {return null;}

/**************************************************************************
 * Component: metaweblog Account
 **************************************************************************/

function metaweblogAccount() {
  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");
}

// readonly attribute AString urn;
metaweblogAccount.prototype.urn = null;
/**************************************************************************
 * metaweblog: XPCOM Component Creation
 **************************************************************************/

metaweblogAccount.prototype = new FlockXPCOMUtils.genericComponent(
  "MetaWeblog Account",
  "",
  "",
  metaweblogAccount,
  0,
  [
    Ci.flockIWebServiceAccount,
    Ci.flockIBlogAccount
  ]
  );

// Module implementation
var MetaweblogModule = new Object();

MetaweblogModule.registerSelf =
function (compMgr, fileSpec, location, type)
{
  compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);

  compMgr.registerFactoryLocation(FLOCK_MW_CID, 
                  "Flock MetaWeblog JS Component",
                  FLOCK_MW_CONTRACTID, 
                  fileSpec, 
                  location,
                  type);
  //necessary category registration
  var catmgr = Components.classes["@mozilla.org/categorymanager;1"]
    .getService (Components.interfaces.nsICategoryManager);
  catmgr.addCategoryEntry('flockICustomBlogWebService', 'metaweblog', FLOCK_MW_CONTRACTID, true, true);
}

MetaweblogModule.getClassObject =
function (compMgr, cid, iid) {
  if (!cid.equals(FLOCK_MW_CID))
    throw Components.results.NS_ERROR_NO_INTERFACE;
  
  if (!iid.equals(Components.interfaces.nsIFactory))
    throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  
  return MetaweblogServiceFactory;
}

MetaweblogModule.canUnload =
function(compMgr)
{
  return true;
}
  
// factory object
var MetaweblogServiceFactory = new Object();

MetaweblogServiceFactory.createInstance =
function (outer, iid) {
  if (outer != null)
    throw Components.results.NS_ERROR_NO_AGGREGATION;

  return (new metaweblogWebService()).QueryInterface(iid);
}

/* entrypoint */
function NSGetModule(compMgr, fileSpec) {
  return MetaweblogModule;
}
