// BEGIN FLOCK GPL
// 
// Copyright Flock Inc. 2005-2006
// http://flock.com
// 
// This file may be used under the terms of 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 flockIBlogAPI                 = Components.interfaces.flockIBlogAPI;
const nsISupports                   = Components.interfaces.nsISupports;
const nsIXMLHttpRequest             = Components.interfaces.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';

debug("LOADING flockWebServiceAtom.js...\n");

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

function BlogAccount(aTitle) {
    this.title = aTitle;
    this.uid = FLOCK_BLOG_RDF_SETTINGS + ":" + aTitle;
    this.url = " ";
    this.username = "";
    this.password = "";
    this.pwhost = "";
    this.api = "";
}

BlogAccount.prototype = {
    getInterfaces: function (count) {
        var interfaceList = [Components.interfaces['flockIBlogAccount'], nsIClassInfo];
        count.value = interfaceList.length;
        return interfaceList;
    },
    QueryInterface: function (iid) {
      if (!iid.equals(Components.interfaces.flockIBlogAccount))
          throw Components.results.NS_ERROR_NO_INTERFACE;
      return this;
    }

}



// CONSTRUCTOR 
function atomWebService () {
    debug ('constructed atomWebService\n');
    this.mQueue = null;

    // set attributes
    this.configurationDialog = null;
    this.name = 'ATOM';
    this.shortName = 'atom';
    this.description = "Google's blogging service"
}// END CONSTRUCTOR

// the nsISupports implementation/*{{{*/
atomWebService.prototype.QueryInterface = function (iid) {
    if (iid.equals(flockIBlogAPI) || 
            iid.equals(nsISupports)) {
        return this;
    }

    throw Components.results.NS_ERROR_NO_INTERFACE;
}/*}}}*/

// loadLibraryFromSpec("chrome://browser/content/flock/common/xmlrpc.js");
// loadLibrary("xmlrpc.js.lib");
function loadLibraryFromSpec(aSpec) {
    var loader = Components.classes['@mozilla.org/moz/jssubscript-loader;1']
        .getService(Components.interfaces.mozIJSSubScriptLoader);

    loader.loadSubScript(aSpec);
}

// LOCAL HELPERS /*{{{*/ 

function dateFormat(date) {
    var datetime = date.getUTCFullYear() + "-";
    var month = String(date.getUTCMonth() + 1);
    datetime += (month.length == 1 ?  '0' + month : month) + "-";
    var day = date.getUTCDate();
    datetime += (day < 10 ? '0' + day : day);

    datetime += 'T';

    var hour = date.getUTCHours();
    datetime += (hour < 10 ? '0' + hour : hour) + ':';
    var minutes = date.getUTCMinutes();
    datetime += (minutes < 10 ? '0' + minutes : minutes) + ':';
    var seconds = date.getUTCSeconds();
    datetime += (seconds < 10 ? '0' + seconds : seconds) + "Z";

    return datetime;
}

function parseDate(datestr)
{
    var date = null;

    datestr = datestr.replace(/^\s*|\s*$/g, '');

    var dateval = Date.parse(datestr);

    if (dateval) {
        date = new Date(dateval);
    } else {
        /* this is a parser for the W3C subset of ISO 8601 dates
         * http://www.w3.org/TR/NOTE-datetime
         */
        const re_iso8601 = new RegExp(
            "^(\\d{4})(-(\\d{2,3})(-(\\d{2})" +
            "(T(\\d{2}):(\\d{2})(:(\\d{2})(\\.(\\d+))?)?" +
            "(Z|(([-+])(\\d{2}):(\\d{2})))?)?)?)?$");

        var m = datestr.match(re_iso8601);

        if (m) {
            date = new Date(Date.UTC(m[1]))

            m[3]  && date.setUTCMonth   (m[3] - 1);
            m[5]  && date.setUTCDate    (m[5]);
            m[7]  && date.setUTCHours   (m[7]);
            m[8]  && date.setUTCMinutes (m[8]);
            m[10] && date.setUTCSeconds (m[10]);

            m[12] && date.setUTCMilliseconds(('0.' + m[12]) * 1000);

            if (m[14]) {
                var tz_off = m[16] * 60 + Number(m[17]);
                tz_off *= m[15] == '-' ? -1 : 1;
                tz_off *= 60 * 1000;

                date.setTime(date.getTime() - tz_off);
            }

        }
    }

    return date;
/*
    if (date)
        return date.toString();
    else
        return null;
*/
}/*}}}*/

// PRIVATE METHODS /*{{{*/ 

atomWebService.prototype.doRequest = function(listener, method, url, body, processor) {
    var inst = this;
    //debug("doRequest with "+this.username+" "+this.password+"\n");
    //debug(method+"\n----\n"+body+"\n");
    //debug("on "+url+"\n");
    this._req = Components.classes[XMLHTTPREQUEST_CONTRACTID].createInstance(nsIXMLHttpRequest);
    this._req.onreadystatechange = function (aEvt) {
        //debug("Request readyState: "+inst._req.readyState+"\n");
        //debug("Request status: "+inst._req.status+"\n");
        if(inst._req.readyState == 4) {
            //debug("\nRESPONSE\n" + inst._req.responseText);
            try {
			    if(inst._req.status == 200 || inst._req.status == 201 || inst._req.status == 205) {
		        try {
				        processor(listener, inst);
			        }
			        catch(e) {
				        // listener.onError(inst.ERROR_PARSER);
                        debug(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;
                    debug(faultString+"\n");
                    listener.onFault(faultString);
			    }
            } catch(e) {
                debug(e + " " + e.fileName + " " + e.lineNumber + "\n");
                listener.onError(inst.ERROR_PARSER);
            }
        }
    };
    rval = this._req.open(method, url, true);
    //FlockLogger.write("atomapi.txt", "\nSENDING to " + url + "\n");
    //FlockLogger.write("atomapi.txt", body);
    //debug("\nSENDING\n" + body);
    //debug("\nTO\n" + method + " @ " + url + "\n");
    rval = this._req.send(body); 
}


atomWebService.prototype.parseUsersBlogs = function(listener, inst) {
  try {
    var dom = inst._req.responseXML;
    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 newAccount = new BlogAccount(title);
          newAccount.api = this.shortName;
          newAccount.blogid = RegExp.$1;
          newAccount.apiLink = href;
          newAccount.editLink = href;
          result.push(newAccount);
          break;
        case "alternate":
          for (j in result)
            if (result[j].title == title)
              result[j].url = href;
          break;
        default: ;
      }
    }
    dump("Found "+ result.length +" blogs\n");
    listener.onResult(simpleEnumerator(result));
  }
  catch(e) {
    listener.onError(e + " " + e.lineNumber);
    debug(e + " " + e.lineNumber);
  }
}


atomWebService.prototype.parseRecentPosts = function(listener, inst) {
    var dom = inst._req.responseXML;
    var entries = dom.getElementsByTagName("entry");
    // 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"]
       .createInstance(Components.interfaces.nsIRDFContainerUtils);
    // Fill it with the results
    var container = rdfContainerUtils.MakeSeq(rdf, rdfService.GetResource("urn:flock:blog:elements"));
    for(var i=0;i<entries.length;++i) {
        try {
            var entry_n = entries[i];
            var content_n = inst.getNamedChild(entry_n, "content");
            var title_n = inst.getNamedChild(entry_n, "title");
            var created_n = inst.getNamedChild(entry_n, "issued");
            var author_n = inst.getNamedChild(entry_n, "author");
            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 dateval = created_n.firstChild.nodeValue;
            var date = parseDate(dateval);
            var data = "";

            var obj =
            {
                description: data,
                title: title_n.firstChild.nodeValue,
                dateCreated: date,
                postid:  postid,
                editURI:  href,
                permaLink:  permaLink,
                atomid:  atomid,                 
            };
            // rval[rval.length] = obj;
            // Create a RDF subject
            var id='obj_' + Math.round(10000*Math.random())
            var subject = rdfService.GetResource("http://www.flock.com/rdf/"+id);
            for (k in obj){
               var predicate = rdfService.GetResource("http://www.flock.com/rdf/"+k);
               var target = rdfService.GetLiteral(obj[k]);
               rdf.Assert(subject, predicate, target, true);
            }
            container.AppendElement(subject);
        }
        catch(e) {
            debug(e + " " + e.lineNumber + " " + e.fileName);
        }
    }
    listener.onResult(rdf);
}

atomWebService.prototype.parsePosts = function(listener, inst) {
    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 dom = inst._req.responseXML;
    debug(inst._req.responseText);
    if(!dom)
    {
        debug("No dom.\n");
        // LiveJournal - everone wants to be different, don't they?
        if(inst._req.responseText.match(/OK/)) {
            listener.onResult(rdf);
            return;
        }
        else {
            var fault =  {
                faultString: "There was an error posting to your blog.  Make sure that are not trying to blog really weird html.",
            };
            listener.onFault(fault);
            return;
        }
    }
    var entries = dom.getElementsByTagName("entry");
     // Create an in-memory datasource
    var rval = new Array();
    rval.permaLink = "";
    rval.editURI = "";
    rval.atomid = "";
    try {
        var entry_n = entries[0];
        var link_n = null;
        var perm_link_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;
                }
                else if(tmp.getAttribute("rel").match(/alternate/)) {
                    perm_link_n = tmp;
                }
            }
        }
        var href = link_n.getAttribute("href");
        rval.editURI = href;
        rval.atomid = atomid;
        href.match(/.+\/(.+)/);
        rval.uid = RegExp.$1;;
        var perm_link = perm_link_n.getAttribute("href");
        //Livejournal hackage
        if(rval.editURI.length>0 && rval.atomid.length==0) {
            if(perm_link.match(/livejournal/)) {
                perm_link.match(/http.\/\/(\w+?)\.livejournal.com.*\/(\w+)\.html/);
                rval.atomid = "urn:lj:livejournal.com:atom1:" + RegExp.$1 + ":" + RegExp.$2;
            }
        }
        rval.permaLink = perm_link;
    }
    catch(e) {
        debug(e + " " + e.lineNumber + " " + e.fileName);
    }
    var subject = rdfService.GetResource("http://www.flock.com/rdf/newpost");

    var predicate = rdfService.GetResource("http://www.flock.com/rdf/permalink");
    var target = rdfService.GetLiteral(rval.permaLink);
    rdf.Assert(subject, predicate, target, true);
 
    var predicate = rdfService.GetResource("http://www.flock.com/rdf/editURI");
    var target = rdfService.GetLiteral(rval.editURI);
    rdf.Assert(subject, predicate, target, true);
    
    var predicate = rdfService.GetResource("http://www.flock.com/rdf/atomid");
    var target = rdfService.GetLiteral(rval.atomid);
    rdf.Assert(subject, predicate, target, true);
    
    listener.onResult(rdf);
}
 

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

atomWebService.prototype.doPost = function (listener, url, method, postid, title, description, date_created, public, aEditURI, aAtomID) {
    description = description.replace(/\r/g,"");
    description = description.replace(/\n/g," ");
    if(postid) url += "/" + postid;

    if(aEditURI) url = aEditURI;

    //var date = dateFormat(date_created);
    var date = dateFormat(new Date());
    var body = "";
    var version = "Flock Alpha";
    body += '<?xml version="1.0" encoding="utf-8"?>';
    body += '<entry xmlns="http://purl.org/atom/ns#">';

    var draft = "false";
    if(!public) draft = "true";

    body += '<draft xmlns="http://purl.org/atom-blog/ns#">' + draft + '</draft> '
    body += '<title mode="escaped" type="text/plain">' + title + '</title>';
    body += '<issued>' + date + '</issued>';
    if(aAtomID && !url.match(/blogger.com/)) body += '<id>' + aAtomID + '</id>';
    body += '<generator url="http://flock.com">' + version + '</generator>';
    body += '<content type="application/xhtml+xml">';
    // description = description.replace(/&\S+?;/g, "");
    description = description.replace(/&nbsp;/g, "");
    var test = description;
    test = test.replace(/\s/g,"");
    if(!test.length) description = "<br/>";
    body += '<div xmlns="http://www.w3.org/1999/xhtml">' + description + '</div>';
//        body += description;
    body += '</content> </entry>';
    debug(body);
    this.doRequest(listener, method, url, body, this.parsePosts);
}
 
// END PRIVATE METHODS /*}}}*/


// the flockIBlogAPI implementation/*{{{*/ 

atomWebService.prototype.detectAccount = function (aUrl, aLinkList){
  while(aLinkList.hasMoreElements()) {
    var link = aLinkList.getNext();
    link.QueryInterface(Components.interfaces['flockIBlogLink']);
    // dump("   "+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\.com/)) {
        dump("Forcing default Livejournal API link.\n");
        account.apiLink.url = "http://www.livejournal.com/interface/atom/post";
      }
      return account; 
    }
  }
  return null;
}


atomWebService.prototype.newPost = function (aListener, aBlogAccount, aTitle, aDescription, aDateCreated, aPublish, aNotifications){
    this.doPost(aListener, aBlogAccount.apiLink, "POST", null, aTitle, aDescription, aDateCreated, aPublish);
}

atomWebService.prototype.editPost = function (aListener, aBlogAccount, aPostid, aTitle, aDescription, aDateCreated, aPublish, aEditURI, aAtomID, aNotifications){
    debug("Edit with editURI="+aEditURI+"\n");
    this.doPost(aListener, aBlogAccount.apiLink, "PUT", aPostid, aTitle, aDescription, aDateCreated, aPublish, aEditURI, aAtomID);
}

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

atomWebService.prototype.setPostCategories = function (aListener, aAccount, aPostid, aCategories){
    aListener.onResult(null);
}

atomWebService.prototype.getPost = function (aListener, aAccount, aPostid){
    var url = aAccount.apiLink;
    if(aPostid) url += "/" + aPostid;
    var myListener = {
        onResult: function(aResult) {
            var rval = aResult[0];
            aListener.onResult(rval);
        },
        onError: function(aError) {
            aListener.onError(aError);
        },
        onFault: function(aFault) {
            aListener.onFault(aFault);
        },
    };
    this.doRequest(myListener,"GET", url, null, this.parseRecentPosts);
    // this.xmlrpc(myListener, url, "GET", null, this.parseRecentPosts);
}

atomWebService.prototype.getUsersBlogs = function (aListener, aAccount){
    var url = aAccount.apiLink.match(/(.+)\//);
    url = RegExp.$1;
    debug("getUsersBlog... "+this.apiLink+"\n");
    debug("getUsersBlog... "+url+"\n");
    this.doRequest(aListener,"GET", url, null, this.parseUsersBlogs);
}

atomWebService.prototype.getRecentPosts = function (aListener, aAccount, aNumber){
    var url = aAccount.apiLink; 
    url.match(/(.+\/)(.+)/);
    if(RegExp.$2=="post") {
        url = RegExp.$1 + "feed";
    }
    debug("getRecentPosts with url = "+url+"\n");
    this.doRequest(aListener, "GET", url, null, this.parseRecentPosts);
}

atomWebService.prototype.getCategoryList = function (aListener, aAccount){
        aListener.onResult(null);
}/*}}}*/

function loadLibrary(aFilename) { /*{{{*/
    var file = Components.classes["@mozilla.org/file/directory_service;1"]
        .getService(Components.interfaces.nsIProperties)
        .get("ComsD", Components.interfaces.nsIFile);
    file.append(aFilename);

    var ios = Components.classes["@mozilla.org/network/io-service;1"]
        .getService(Components.interfaces.nsIIOService);
    var fileHandler = ios.getProtocolHandler("file")
        .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
    var spec = fileHandler.getURLSpecFromFile(file);

    var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
        .getService(Components.interfaces.mozIJSSubScriptLoader);

    scriptLoader.loadSubScript(spec);
}
/*}}}*/


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

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

    compMgr.registerFactoryLocation(FLOCK_ATOM_CID, 
                                    "Flock Blogger JS Component",
                                    FLOCK_ATOM_CONTRACTID, 
                                    fileSpec, 
                                    location,
                                    type);
    //necessary category registration
    var catmgr = Components.classes["@mozilla.org/categorymanager;1"]
        .getService (Components.interfaces.nsICategoryManager);
    catmgr.addCategoryEntry('flockIBlogAPI', '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(Components.interfaces.nsIFactory))
        throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
    
    return AtomServiceFactory;
}

AtomModule.canUnload =
function(compMgr)
{
    debug("Unloading Atom component.\n");
    return true;
}/*}}}*/
    
/* factory object *//*{{{*/
var AtomServiceFactory = new Object();

AtomServiceFactory.createInstance =
function (outer, iid) {
    debug("CI: " + iid);
    if (outer != null)
        throw Components.results.NS_ERROR_NO_AGGREGATION;

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

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

debug ('LOADED: flockBlogAPI.js\n');
