// 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 nsISupports           = Ci.nsISupports;
const nsIXMLHttpRequest       = Ci.nsIXMLHttpRequest;

const XMLHTTPREQUEST_CONTRACTID   = '@mozilla.org/xmlextras/xmlhttprequest;1';
const FLOCK_ATOM_CID        = Components.ID('{0c01e44c-a61e-4e44-8dee-151d99c54dcf}');
const FLOCK_ATOM_CONTRACTID     = '@flock.com/blog/service/atom;1';
const FLOCK_ATOM_DESCRIPTION    = 'ATOM Blog API';

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

// nsISimpleEnumerator implementation
function simpleEnumerator (aArray) {
  aArray.hasMoreElements = function () { 
    return this.length != 0; 
  }
  aArray.getNext = function () { 
    return this.shift (); 
  }
  return aArray;
}

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

  // set attributes
  this.configurationDialog = null;
  this.name = 'ATOM';
  this.shortName = 'atom';
  this.description = "The Atom Publishing Protocol"
  this.supportsCategories = 0;
  this.supportsPostReplace = true;

  // Logger
  // this.logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger); 
  // this.logger.init("blog");
  // The logger is buggy?
  this.logger = {};
  this.logger.info = function(txt) { debug("Atom:info:"+txt+"\n"); };
  this.logger.error = function(txt) { debug("Atom:error:"+txt+"\n"); };
}// END CONSTRUCTOR

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

  throw Components.results.NS_ERROR_NO_INTERFACE;
}

// PRIVATE METHODS  

atomWebService.prototype.doRequest =
function(aBlogListener, method, url, body, processor, aUsername, aPassword) {
  var inst = this;
  this._req = Cc[XMLHTTPREQUEST_CONTRACTID].createInstance(nsIXMLHttpRequest);
  this._req.onreadystatechange = function (aEvt) {
    inst.logger.info("<<<<<<<<<< Atom API: SERVER TO FLOCK\n");
    inst.logger.info("Request readyState: "+inst._req.readyState+"\n");
    if (inst._req.readyState == 4) {
      var status = inst._req.status;
      inst.logger.info("Request status: " + status + "\n");    
      inst.logger.info("\nRESPONSE\n" + inst._req.responseText);
      if (status == 200 || status == 201 || status == 205) {
        try {
          processor(aBlogListener, inst);
        } catch(e) {
          // aBlogListener.onError(inst.ERROR_PARSER);
          inst.logger.error(e + " " + e.lineNumber+"\n");
        }
      } else {
        var faultString; //: "Make sure that are not trying to blog really weird html, and note the following kind user:\n\n",
        // };
        faultString = inst._req.responseText;
        //faultString += inst._req;
        inst.logger.error(faultString+"\n");
        aBlogListener.onFault(faultString);
      }
    }
  };
  rval = this._req.open(method, url, true, aUsername, aPassword);
  this.logger.info(">>>>>>>>>> Atom API: FLOCK TO SERVER");
  this.logger.info("\nSENDING\n" + body);
  this.logger.info("\nTO\n" + method + " @ " + url);
  rval = this._req.send(body); 
}


atomWebService.prototype.parseUsersBlogs = function(aBlogListener, inst) {
    debug("atomWebService.prototype.parseUsersBlogs\n");
    var result = new Array();
    var dom = inst._req.responseXML;
    var domEntries = dom.getElementsByTagName("entry");

    if (domEntries.length > 0) { // Regular ATOM feed (ATOM)
      for (i=0; i<domEntries.length; i++) {
        debug("One entry...\n");
        domEntry = domEntries[i];
        title = domEntry.getElementsByTagName("title")[0].textContent;
        var blog = Cc["@mozilla.org/hash-property-bag;1"]
                   .createInstance(Ci.nsIWritablePropertyBag2);
        blog.setPropertyAsAString("api", this.shortName);
        var links = domEntry.getElementsByTagName("link");
        for (j=0; j<links.length; j++) {
          var link = links[j]
          switch (link.getAttribute("rel")) {
            case "alternate":
              blog.setPropertyAsAString("URL", link.getAttribute("href"));
              break;
            case "http://schemas.google.com/g/2005#post":
              blog.setPropertyAsAString("APILink", link.getAttribute("href"));
              break;
          }
        }
        result.push(blog);
      }
    }
    else { // Just a list of links (Livejournal)
      var links = dom.getElementsByTagName("link");
      var result = new Array();
      for(var i=0;i<links.length;++i) {
        var link = links[i];
        var title = link.getAttribute("title");
        var rel = link.getAttribute("rel");
        var href = link.getAttribute("href");
        switch (rel) {
        case "service.post":
          href.match(/.+\/(.+)/);
          var blog = Cc["@mozilla.org/hash-property-bag;1"]
                     .createInstance(Ci.nsIWritablePropertyBag2);
          blog.setPropertyAsAString("title", title);
          blog.setPropertyAsAString("blogid", RegExp.$1);
          blog.setPropertyAsAString("APILink", href);
          result.push(blog);
          break;
        case "alternate":
          for (j in result)
          if (result[j].title == title)
            result[j].URL = href;
          break;
        default: ;
        }
      }
    }
    debug("Found "+ result.length +" blogs\n");
    aBlogListener.onResult(simpleEnumerator(result));
}


atomWebService.prototype.parseRecentPosts = function(aBlogListener, inst) {
  var dom = inst._req.responseXML;
  var entries = dom.getElementsByTagName("entry");

  var result = new Array();
  for(var i=0;i<entries.length;++i) {
    try {
      var entry_n = entries[i];
      var title_n = inst.getNamedChild(entry_n, "title");
      var created_n = inst.getNamedChild(entry_n, "published");
      var link_n = null;
      var permalink_n = null;
      var atomid_n = inst.getNamedChild(entry_n, "id");
      var atomid = "";
      if(atomid_n) atomid = atomid_n.firstChild.nodeValue;

      for(var j=0;j<entry_n.childNodes.length;++j) {
        if(entry_n.childNodes[j].nodeName=="link") {
          var tmp = entry_n.childNodes[j];
          if(tmp.getAttribute("rel").match(/edit/)) {
            link_n = tmp;
          }
          if(tmp.getAttribute("rel").match(/alternate/)) {
            permalink_n = tmp;
          }
        }
      }

      var permaLink = permalink_n.getAttribute("href");
      var href = link_n.getAttribute("href");
      href.match(/.+\/(.+)/);
      var postid = RegExp.$1;

      var date = created_n.firstChild.nodeValue;

      var post = new BlogPost();
      post.title = title_n.firstChild.nodeValue;
      post.issued = date;
      post.postid = atomid;
      result.push(post);
    }
    catch(e) {
      var logger = Cc['@flock.com/logger;1']
        .createInstance(Ci.flockILogger);
      logger.error(e + " " + e.lineNumber + " " + e.fileName);
    }
  }
  aBlogListener.onResult(simpleEnumerator(result));
}

atomWebService.prototype.getNamedChild = function(node, name) {
  for(var i=0;i<node.childNodes.length;++i) {
    if(node.childNodes[i].nodeName==name)
      return node.childNodes[i];
  }
  return null;
}

atomWebService.prototype.handleDelete = function(listener, inst) {
  listener.onResult(1);
}

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

atomBlogListener.prototype = {
  onResult: function(aResult) {
    debug("atomBlogListener, onResult\n");
    var link = aResult.link ? aResult.link : "";
    this.publishListener.onResult(link);
  },
  onError: function(error) {
    this.logger.error("<<<<<<<<<< Atom API: SERVER TO FLOCK\n");
    this.logger.error("ERROR "+error+"\n");
    this.publishListener.onError(error);
  }, 
  onFault: function(error) {
    this.logger.error("<<<<<<<<<< Atom API: SERVER TO FLOCK\n");
    this.logger.error("FAULT "+error+"\n");
    this.publishListener.onFault(error);
  } 
}
 
// END PRIVATE METHODS 


// the flockIBlogWebService implementation 

atomWebService.prototype.detectAccount = function (aUrl, aLinkList){
  while(aLinkList.hasMoreElements()) {
    var link = aLinkList.getNext();
    link.QueryInterface(Ci['flockIBlogLink']);
    debug("   ATOM link.href : "+link.href+"\n");
    if(link.type=="application/atom+xml" && link.rel=="service.post") {
      var account = {
        api : "atom",
        apiLink : link.href
      };
      if (account.apiLink.match(/livejournal/)) {
        debug("Forcing default Livejournal API link.\n");
        account.apiLink = "http://www.livejournal.com/interface/atom";
      }
      return { wrappedJSObject: account };
    }
  }
  return { wrappedJSObject: null };
}


atomWebService.prototype.newPost =
function (aPublishListener, aBlogId, aPost, aPublish, aNotifications){
  var gBlogService = Cc['@flock.com/flock-blog;1'].getService(Ci['flockIBlogService']);
  var blogListener = new atomBlogListener(aPublishListener);
  var atomEntry = {title: aPost.title, content: aPost.description};
  var account = gBlogService.getAccount(aBlogId).wrappedJSObject;

  var labels = new Array();
  if (aPost.tags)
    while (aPost.tags.hasMore())
      labels.push(aPost.tags.getNext());
  if (labels.length > 0)
    atomEntry.categories = labels;
  flockAtomPost(blogListener, account.apiLink, atomEntry,
                 new Array(account.username, account.password));
}

atomWebService.prototype.editPost =
function (aPublishListener, aBlogAccount, aPostid, aTitle, aDescription, 
          aTags, aDateCreated, aPublish, aEditURI, aAtomID, aNotifications){
  var blogListener = new atomBlogListener(aPublishListener);
  var atomEntry = { id: aAtomID, title: aTitle, content: aDescription, issued: aDateCreated};
  var labels = new Array();
  while (aTags.hasMore())
    labels.push(aTags.getNext());
  if (labels.length > 0)
    atomEntry.categories = labels;
  flockAtomEdit(blogListener, aEditURI, atomEntry,
                [aBlogAccount.username, aBlogAccount.password]);
}

atomWebService.prototype.deletePost =
function (aBlogListener, aBlogAccount, aPostid) {
  var url = aBlogAccount.apiLink;
  url += "/" + aPostid;
  // if(aEditURI) url = aEditURI;
  this.doRequest(aBlogListener, "DELETE", url, null, this.handleDelete,
                 aBlogAccount.username, aBlogAccount.password);
}

atomWebService.prototype.setPostCategories =
function atomWebService_setPostCategories(aPublishListener,
                                          aBlogId,
                                          aPostid,
                                          aCategories)
{
  aPublishListener.onResult(null);
}

atomWebService.prototype.getUsersBlogs =
function atomWebService_getUsersBlogs(aBlogListener, aAPILink, aUsername, aPassword) {
  this.doRequest(aBlogListener, "GET",
                 aAPILink,
                 null,
                 this.parseUsersBlogs,
                 aUsername,
                 aPassword);
}

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

  var url = account.apiLink; 
  //url.match(/(.+\/)(.+)/);
  //if(RegExp.$2=="post") {
  //  url = RegExp.$1 + "feed";
  //}
  debug("getRecentPosts with url = "+url+"\n");
  this.doRequest(aBlogListener, "GET", url, null,
                 this.parseRecentPosts, account.username, account.password);
}

atomWebService.prototype.getCategoryList = function (aBlogListener, aAccount){
  aBlogListener.onResult(null);
}

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

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

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

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

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

AtomModule.canUnload =
function(compMgr) {
  return true;
}
  
/* factory object */
var AtomServiceFactory = new Object();

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

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

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