// 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 FLOCK_MT_CID =  Components.ID('{53254883-dc94-4527-b071-139b93e100f7}');
const FLOCK_MT_CONTRACTID = '@flock.com/blog/service/movabletype;1';
const FLOCK_MT_DESCRIPTION = 'MovableType';

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

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

  loader.loadSubScript(aSpec);
}

loadLibraryFromSpec("chrome://flock/content/blog/technoratiTags.js");
loadLibraryFromSpec("chrome://flock/content/blog/blogBackendLib.js");
loadLibraryFromSpec("chrome://flock/content/xmlrpc/xmlrpchelper.js");


// CONSTRUCTOR
function movabletypeWebService () {
  //this.mQueue = null;

  // set attributes
  this.name = 'MovableType';
  this.shortName = 'movabletype';
  this.description = "The MovableType API"
  this.supportsCategories = 2;
  this.supportsPostReplace = true;
  this.appKey = "";

  var obs = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
  obs.addObserver(this, 'flock-data-ready', false);
  obs.addObserver(this, 'xpcom-shutdown', false);
}

// nsIObserver
movabletypeWebService.prototype.observe = function (subject, topic, state) {
  switch (topic) {
    case 'flock-data-ready':
      var obs = Components.classes["@mozilla.org/observer-service;1"]
        .getService(Components.interfaces.nsIObserverService);
      obs.removeObserver(this, 'flock-data-ready');
      this.setup();
      return;
    case 'xpcom-shutdown':
      var obs = Components.classes["@mozilla.org/observer-service;1"]
        .getService(Components.interfaces.nsIObserverService);
      obs.removeObserver(this, 'xpcom-shutdown');
      return;
  }
}


movabletypeWebService.prototype.setup = function () {
  // Logger
  this.logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
  this.logger.init("blog");
}

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


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

  throw Components.results.NS_ERROR_NO_INTERFACE;
}


// PRIVATE METHODS

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

blogListener.prototype = 
{
  // aResult is an Array of struct objects
  onResult: function blogListener_onResult(aResult) {
    this.logger.info("<<<<<<<<<< MovableType API: SERVER TO FLOCK");
    for (i=0; i<aResult.length; i++){
      this.logger.debug("Entry\n");
      var entry = aResult[i];
      for (j in entry)
        this.logger.debug("  -> "+j+": "+entry[j]+"\n");
    }
    // Create an in-memory datasource
    var rdfService = Cc["@mozilla.org/rdf/rdf-service;1"]
      .getService(Ci.nsIRDFService);
    var rdf = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].createInstance(Ci.nsIRDFDataSource);
    var rdfContainerUtils = Cc["@mozilla.org/rdf/container-utils;1"]
      .getService(Ci.nsIRDFContainerUtils);
    // Fill it with the results
    if (this.container)
      var container = rdfContainerUtils.MakeSeq(rdf, rdfService.GetResource("urn:flock:blog:elements"));
    for (i=0; i<aResult.length; i++){
      var id='obj_' + Math.round(10000*Math.random())
      var subject = rdfService.GetResource("http://www.flock.com/rdf/"+id);
      var entry = aResult[i];
      for (j in entry){
        var predicate = rdfService.GetResource("http://www.flock.com/rdf/"+j);
        var name = rdfService.GetLiteral(entry[j]);

        rdf.Assert(subject, predicate, name, true);
      }
      if (this.container)
        container.AppendElement(subject);
    }

    // and return the data source through the listener
    this.listener.onResult(rdf);
  },

  onError: function blogListener_onError(aMessage) {
    this.logger.error("<<<<<<<<<< MovableType API: SERVER TO FLOCK\n");
    this.logger.error("ERROR " + aMessage + "\n");
    var error = Cc["@flock.com/error;1"].createInstance(Ci.flockIError);
    error.serviceErrorCode = Ci.flockIError.BLOG_UNKNOWN;
    error.serviceErrorString = aMessage;
    this.listener.onError(error);
  },

  onFault: function blogListener_onFault(aFaultCode, aFaultString) {
    this.logger.error("<<<<<<<<<< MovableType API: SERVER TO FLOCK\n");
    this.logger.error("FAULT " + aFaultCode + " " + aFaultString + "\n");
    var error = Cc["@flock.com/error;1"].createInstance(Ci.flockIError);
    error.serviceErrorCode = aFaultCode;
    error.serviceErrorString = aFaultString;
    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 blogCategoryListener_onResult(aResult) {
    this.logger.info("<<<<<<<<<< MovableType API: SERVER TO FLOCK\n");
    var result = new Array();
    for (i=0; i<aResult.length; i++){
      this.logger.debug("Entry\n");
      var entry = aResult[i];
      for (j in entry)
        this.logger.debug("  -> "+j+": "+entry[j]+"\n");
    }
    for (i=0; i<aResult.length; i++){
      this.logger.debug("Found the category "+aResult[i].categoryId+" - "+aResult[i].categoryName+"\n");
      var category = new BlogCategory(aResult[i].categoryId, aResult[i].categoryName);
      result.push(category);
    }

    // and return the data source through the listener
    this.listener.onResult(new simpleEnumerator(result));
  },

  onError: function blogCategoryListener_onError(aErrorCode, aErrorMsg) {
    this.logger.error("<<<<<<<<<< MovableType API: SERVER TO FLOCK\n");
    this.logger.error("ERROR "+aErrorCode+" "+aErrorMsg+"\n");
    var error = Cc['@flock.com/error;1'].createInstance(Ci.flockIError);
    error.serviceErrorCode = aErrorCode;
    error.serviceErrorString = aErrorMsg;
    switch (aErrorCode) {
      case 403: // Invalid login/pass
        error.errorCode = Ci.flockIError.BLOG_INVALID_AUTH;
        break;
      default: // Unknown error code
        error.errorCode = Ci.flockIError.BLOG_UNKNOWN;
    }
    this.listener.onError(error);
  },

  onFault: function blogCategoryListener_onFault(aErrorCode, aErrorMsg) {
    this.logger.error("<<<<<<<<<< MovableType API: SERVER TO FLOCK\n");
    this.logger.error("FAULT "+aErrorCode+" "+aErrorMsg+"\n");
    var error = Cc['@flock.com/error;1'].createInstance(Ci.flockIError);
    error.serviceErrorCode = aErrorCode;
    error.serviceErrorString = aErrorMsg;
    switch (aErrorCode) {
      case 403: // Invalid login/pass
        error.errorCode = Ci.flockIError.BLOG_INVALID_AUTH;
        break;
      default: // Unknown error code
        error.errorCode = Ci.flockIError.BLOG_UNKNOWN;
    }
    this.listener.onError(error);
  }
}

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

userBlogsListener.prototype = 
{
  // aResult is an Array of struct objects
  onResult: function userBlogListener_onResult(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.onSuccess(new simpleEnumerator(result), null);
  },
  onError: function userBlogListener_onError(aMessage) {
    this.logger.error("ERROR " + aMessage + "\n");
    var error = Cc['@flock.com/error;1'].createInstance(Ci.flockIError);
    error.serviceErrorCode = Ci.flockIError.BLOG_UNKNOWN;
    error.serviceErrorString = aMessage;
    this.listener.onError(null, "", error);
  },
  onFault: function userBlogListener_onFault(aFaultCode, aFaultString) {
    this.logger.error("FAULT " + aFaultCode + " " + aFaultString + "\n");
    var error = Cc['@flock.com/error;1'].createInstance(Ci.flockIError);
    error.serviceErrorCode = aFaultCode;
    error.serviceErrorString = aFaultString;
    switch (aFaultCode) {
      case 403: // Invalid login/pass
        error.errorCode = Ci.flockIError.BLOG_INVALID_AUTH;
        break;
      default: // Unknown error code
        error.errorCode = Ci.flockIError.BLOG_UNKNOWN;
    }
    this.listener.onError(null, "", error);
  }
}


movabletypeWebService.prototype.detectAccount =
function movabletypeWebService_detectAccount (aUrl, aLinkList) {
  while(aLinkList.hasMoreElements()) {
    var link = aLinkList.getNext();
    link.QueryInterface(Ci.flockIBlogLink);
    if (link.rel=="pingback"){
      var account = {
        api : "movabletype",
        apiLink : link.href,
        blogid : "1"
      };
      return account; 
    }
  }
  return null;
}

movabletypeWebService.prototype.newPost =
function MTWS_newPost (aListener, aBlogId, aPost, aPublish, aNotifications) {
  var svc = this;

  var gBlogService = Cc['@flock.com/flock-blog;1'].getService(Ci.flockIBlogService);
  var account = gBlogService.getAccount(aBlogId);

  var notifsArray = new Array();
  var html = aPost.description;
  if (aPost.tags)
    html +=  addTechnoratiTags(aPost.tags);
  if (aNotifications)
    while (aNotifications.hasMore())
      notifsArray.push(aNotifications.getNext());

  var listener = {
    onResult: function newPost_listener_onResult(aResult) {
      svc.setPostCategories(aListener, account, aResult, aPost.categories);
    },
    onError: function newPost_listener_onError(aMessage) {
      svc.logger.error("<<<<<<<<<< MovableType API: SERVER TO FLOCK\n");
      svc.logger.error("ERROR " + aMessage + "\n");

      var error = Cc['@flock.com/error;1'].createInstance(Ci.flockIError);
      error.serviceErrorCode = Ci.flockIError.BLOG_UNKNOWN;
      error.serviceErrorString = aMessage;

      aListener.onError(error);
    }, 
    onFault: function newPost_listener_onFault(aFaultCode, aFaultString) {
      svc.logger.error("<<<<<<<<<< MovableType API: SERVER TO FLOCK\n");
      svc.logger.error("FAULT " + aFaultCode + " " + aFaultString + "\n");

      var error = Cc['@flock.com/error;1'].createInstance(Ci.flockIError);
      error.serviceErrorCode = aFaultCode;
      error.serviceErrorString = aFaultString;
      switch (aFaultCode) {
        case 0: // Invalid login/pass
          error.errorCode = Ci.flockIError.BLOG_INVALID_AUTH;
          break;
        default: // Unknown error code
          error.errorCode = Ci.flockIError.BLOG_UNKNOWN;
      }
      aListener.onFault(error);
    } 
  };

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

movabletypeWebService.prototype.editPost =
function MTWS_editPost(aListener, aBlogId, aPost, aPublish, aNotifications){
  var svc = this;

  var gBlogService = Cc['@flock.com/flock-blog;1'].getService(Ci.flockIBlogService);
  var account = gBlogService.getAccount(aBlogId);

  var listener = {
    onResult: function editPost_listener_onResult(aResult) {
      aListener.onResult(aResult);
    },
    onError: function editPost_listener_onError(aMessage) {
      svc.logger.error("<<<<<<<<<< MovableType API: SERVER TO FLOCK\n");
      svc.logger.error("ERROR " + aMessage + "\n");
      aListener.onError(aMessage);
    }, 
    onFault: function editPost_listener_onFault(aFaultCode, aFaultString) {
      svc.logger.error("<<<<<<<<<< MovableType API: SERVER TO FLOCK\n");
      svc.logger.error("FAULT " + aFaultCode + " " + aFaultString + "\n");

      var error = Cc['@flock.com/error;1'].createInstance(Ci.flockIError);
      error.serviceErrorCode = aFaultCode;
      error.serviceErrorString = aFaultString;
      switch (aFaultCode) {
        case 0: // Invalid login/pass
          error.errorCode = Ci.flockIError.BLOG_INVALID_AUTH;
          break;
        default: // Unknown error code
          error.errorCode = Ci.flockIError.BLOG_UNKNOWN;
      }
      aListener.onFault(error);
    }
  };

  var notifsArray = new Array();
  var html = aPost.description + addTechnoratiTags(aPost.tags);
  while (aNotifications.hasMore())
    notifsArray.push(aNotifications.getNext());

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

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

movabletypeWebService.prototype.deletePost =
function movabletypeWebService_deletePost(aListener, aAccount, aPostid) {
  throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
}

movabletypeWebService.prototype.setPostCategories =
function MTWS_setPostCategories (aListener, aAccount, aPostid, aCategories) {
  var listener = new blogListener(aListener, false);
  var categories = new Array();
  if (aCategories)
    while (aCategories.hasMoreElements()) {
      var cat = aCategories.getNext();
      cat.QueryInterface(Ci.flockIBlogCategory);
      categories.push({categoryId: cat.id});
    }
  var xmlrpcServer = new flockXmlRpcServer (aAccount.apiLink);
  var args = [aPostid, aAccount.username, aAccount.password, categories];
  xmlrpcServer.call("mt.setPostCategories", args, listener);
}

movabletypeWebService.prototype.getPost =
function movabletypeWebService_getPost(aListener, aAccount, aPostid) {
  var listener = new blogListener(aListener, false);
  var xmlrpcServer = new flockXmlRpcServer (aAccount.apiLink);
  var args = [aPostid, aAccount.username, aAccount.password];
  xmlrpcServer.call("metaWeblog.getPost", args, listener);
}

movabletypeWebService.prototype.getUsersBlogs =
function movabletypeWebService_getUsersBlogs(aListener, aAccount) {
  var listener = new userBlogsListener(aListener);
  var xmlrpcServer = new flockXmlRpcServer (aAccount.apiLink);
  var args = [this.appKey, aAccount.username, aAccount.password];
  xmlrpcServer.call("blogger.getUsersBlogs", args, listener);
}

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

  this.logger = Cc["@flock.com/logger;1"].createInstance(Ci.flockILogger);
  this.logger.init("blog");
  var svc = this;

  var listener = {
    onResult: function getRecentPosts_listener_onResult(aResult) {
      var result = new Array();
      svc.logger.info("<<<<<<<<<< MovableType API: SERVER TO FLOCK");
      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
      aListener.onResult(new simpleEnumerator(result));
    },
    onError: function getRecentPosts_listener_onError(aMessage) {
      svc.logger.error("<<<<<<<<<< MovableType API: SERVER TO FLOCK");
      svc.logger.error("ERROR " + aMessage);
      var error = Cc["@flock.com/error;1"].createInstance(Ci.flockIError);
      error.serviceErrorCode = Ci.flockIError.BLOG_UNKNOWN;
      error.serviceErrorString = aMessage;
      aListener.onError(error);
    }, 
    onFault: function getRecentPosts_listener_onFault(aFaultCode, aFaultString) {
      svc.logger.error("<<<<<<<<<< MovableType API: SERVER TO FLOCK");
      svc.logger.error("FAULT " + aFaultCode + " " + aFaultString);
      var error = Cc["@flock.com/error;1"].createInstance(Ci.flockIError);
      error.serviceErrorCode = aFaultCode;
      error.serviceErrorString = aFaultString;
      switch (aFaultCode) {
        case 403:
          // Invalid login/pass
          error.errorCode = Ci.flockIError.BLOG_INVALID_AUTH;
          break;
        default:
          // Unknown error code
          error.errorCode = Ci.flockIError.BLOG_UNKNOWN;
      }
      aListener.onFault(error);
    } 
  }

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

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

  var listener = new blogCategoryListener(aListener, false);
  var xmlrpcServer = new flockXmlRpcServer (account.apiLink);
  var args = [account.blogid, account.username, account.password];
  xmlrpcServer.call("mt.getCategoryList", args, listener);
}

movabletypeWebService.prototype.removeAccount =
function movabletypeWebService_removeAccount(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 pm = Cc["@mozilla.org/passwordmanager;1"].getService(Ci.nsIPasswordManager);

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

  // Remove the password
  try {
    pm.removeUser(aId, account.accountId);
  }
  catch(e) {
    this.logger.warn("Exception trying to remove the password. It may have been already deleted.");
  }

  // 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();
}

// That a bit hacky, a way to not break flockAccountUtils.removeAllAccounts
movabletypeWebService.prototype.getAccount =
function movabletypeWebService_getAccount(aId) {
  return {
    logout: function(aArg) { return; },
    getInterfaces: function (count) {
      var interfaceList = [Ci.nsISupports, Ci.flockIWebServiceAccount, Ci.flockIBlogWebServiceAccount];
      count.value = interfaceList.length;
      return interfaceList;
    },
    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;
    }
  }
}

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

MovabletypeModule.registerSelf =
function (compMgr, fileSpec, location, type)
{
  compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);

  compMgr.registerFactoryLocation(FLOCK_MT_CID, 
                  "Flock MovableType JS Component",
                  FLOCK_MT_CONTRACTID, 
                  fileSpec, 
                  location,
                  type);
  //necessary category registration
  var catmgr = Cc["@mozilla.org/categorymanager;1"]
    .getService (Ci.nsICategoryManager);
  catmgr.addCategoryEntry('flockICustomBlogWebService', 'movabletype', FLOCK_MT_CONTRACTID, true, true);
}

MovabletypeModule.getClassObject =
function (compMgr, cid, iid) {
  if (!cid.equals(FLOCK_MT_CID))
    throw Components.results.NS_ERROR_NO_INTERFACE;
  
  if (!iid.equals(Ci.nsIFactory))
    throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  
  return MovabletypeServiceFactory;
}

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

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

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

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