var ATOM_NS = "http://www.w3.org/2005/Atom";
var ATOM_PUB_INTROSPECT_NS = "http://www.w3.org/2007/app";
var ATOM_PUB_INTROSPECT_MIME_TYPE = "application/atomserv+xml";

function _newXMLDocument() {
   if (document.implementation && document.implementation.createDocument) {
      return document.implementation.createDocument("", "", null);
   } else if (window.ActiveXObject) {
      return new ActiveXObject("Microsoft.XMLDOM");
   } else {
      throw "Cannot create new XML document.";
   }
}

if (typeof DOMParser == "undefined") {
   DOMParser = function () {}

   DOMParser.prototype.parseFromString = function (str, contentType) {
      if (typeof ActiveXObject != "undefined") {
         var d = new ActiveXObject("MSXML.DomDocument");
         d.loadXML(str);
         return d;
      } else if (typeof XMLHttpRequest != "undefined") {
         var req = new XMLHttpRequest;
         req.open("GET", "data:" + (contentType || "application/xml") +
                      ";charset=utf-8," + encodeURIComponent(str), false);
         if (req.overrideMimeType) {
            req.overrideMimeType(contentType);
         }
         req.send(null);
         return req.responseXML;
      }
   }
}
function _newDOMParser() {
   return new DOMParser();
}

if (typeof XMLSerializer == "undefined") {
   XMLSerializer = function() {}
   XMLSerializer.prototype.serializeToString = function(doc) {
      return doc.xml;
   }
}

function _newXMLSerializer() {
   return new XMLSerializer();
}


function _Atom_textOf(parent,name,namespace) {
   var text = "";
   var current = parent.firstChild;
   while (current) {
      if (current.nodeType!=1) {
         current = current.nextSibling;
         continue;
      }
      if (current.localName==name && current.namespaceURI==namespace) {
         text += current.textContent;
      }
      current = current.nextSibling;
   }
   return text;
}

function _Atom_forChild(parent,name,namespace,handler) {
   var current = parent.firstChild;
   while (current) {
      if (current.nodeType!=1) {
         current = current.nextSibling;
         continue;
      }
      if (current.localName==name && current.namespaceURI==namespace) {
         handler(current);
      }
      current = current.nextSibling;
   }
}

function _Atom_text(e) {
   if (e.innerText) {
      return e.innerText;
   } else if (e.textContent) {
      return e.textContent;
   } else {
      var text = "";
      var current = e.firstChild;
      while (current) {
         if (current.nodeType==3) {
            text += current.nodeValue;
         } else if (current.nodeType==1) {
            text += this.text(current);
         }
         current = current.nextSibling;
      }
      return text;
   }
}

function _Atom_removeChildren(parent) {
   while (parent.childNodes.length>0) {
      parent.removeChild(parent.childNodes.item(0));
   }
}

function AtomService(url,source) {
   this.type = 0;
   this.url = url;
   this.authorization = null;
   this.source = source;
   this.username = null;
   this.author = null;
   this.password = null;
   this.xml = null;
   this.loaded = false;
   this.inprocess = null;
   this.attempted = 0;
   this.workspaces = {};
   this.bufferSize = 8192;
}

AtomService.prototype.introspect = function(options) {
   var currentService = this;
   this.attempted += 1;
   this.inprogress = HTTP("GET",this.url,{
      timeout: options.timeout,
      overrideMimeType: "text/xml",
      username: this.username,
      password: this.password,
      onSuccess: function(status,doc,text) {
         currentService.inprogress = null;
         if (doc) {
            doc.documentElement.setAttributeNS("http://www.w3.org/XML/1998/namespace","xml:base",currentService.url);
            currentService.loadIntrospection(doc.documentElement);   
            options.onSuccess(status,text);
         } else {
            options.onFailure(status,doc,text);
         }
      },
      onFailure: function(status,doc,text) {
         currentService.inprogress = null;
         options.onFailure(status,doc,text);
      }
   });
}

AtomService.prototype.loadIntrospection = function(element) {
   this.xml = element;
   this.workspaces = {};
   if (element.namespaceURI!=ATOM_PUB_INTROSPECT_NS) {
      this.loaded = false;
      throw "Introspection document from "+this.url+" is not in the correct namespace: expecting "+ATOM_PUB_INTROSPECT_NS+" and found "+element.namespaceURI;
   }
   if (element.localName!="service") {
      this.loaded = false;
      throw "Introspection document element from "+this.url+" is not {"+ATOM_PUB_INTROSPECT_NS+"}service : {"+element.namespaceURI+"}"+element.localName;
   }
   this.loaded = true;
   var current = element.firstChild;
   var count = 0;
   while (current) {
      if (current.nodeType!=1) {
         current = current.nextSibling;
         continue;
      }
      if (current.localName=="workspace" && current.namespaceURI==ATOM_PUB_INTROSPECT_NS) {
         count++;
         var id = this.url+"/"+count;
         current.setAttribute("client-id",id);
         this.workspaces[id] = new AtomWorkspace(this,id,current);
      }
      current = current.nextSibling;
   }
}

function AtomWorkspace(service,id,element) {
   this.type = 1;
   this.service = service;
   this.id = id;
   this.xml = element;
   if (!this.xml) {
      var doc = _newXMLDocument();
      this.xml = doc.createElementNS(ATOM_PUB_INTROSPECT_NS,"workspace");
      var titleE = doc.createElementNS(ATOM_NS,"title");
      this.xml.appendChild(titleE);
      titleE.appendChild(doc.createTextNode("workspace"));
   }
   var title = null;
   _Atom_forChild(
      this.xml,
      "title",
      ATOM_NS,
      function(target) { title = target; } 
   );
   if (title) {
      this.title = title.textContent;
   } else {
      this.title = this.xml.getAttribute("title");
   }
   this.collections = {};
   this.children = [];
   this.baseURI = service.url;
   var current = this.xml.firstChild;
   while (current) {
      if (current.nodeType!=1) {
         current = current.nextSibling;
         continue;
      }
      if (current.localName=="collection" && current.namespaceURI==ATOM_PUB_INTROSPECT_NS) {
         var href = current.getAttribute("href");
         if (href) {
            // Make sure the href is absolute
            var url = null;
            if (this.baseURI.charAt(this.baseURI.length-1)=='/' && href.charAt(0)=='/') {
               url = this.baseURI+href.substring(1);
            } else {
               url = this.baseURI+href;
            }
            current.setAttribute("href",href);
            
            var collection = new AtomCollection(this,href,url,current);
            this.collections[href] = collection;
         }
      }
      current = current.nextSibling;
   }
   this.treeSort();
}

AtomWorkspace.prototype.treeSort = function() {
   var maxLevel = 0;
   for (var href in this.collections) {
      var collection = this.collections[href];
      this._calculateCollectionLevel(collection);
      if (collection.level>maxLevel) {
         maxLevel = collection.level;
      }
   }
   this.parent = null;
   this.children = [];
   for (var href in this.collections) {
      var collection = this.collections[href];
      if (collection.level==0) {
         this.children.push(collection);
         collection.parent = this;
      }
      collection.children = [];
      var next = collection.level + 1;
      for (var otherHref in this.collections) {
         var other = this.collections[otherHref];
         if (other.level==next && otherHref.substring(0,href.length)==href) {
            collection.children.push(other);
            other.parent = collection;
         }
      }
      collection.children.sort(function(x,y) {return x.href < y.href });
   }
   this.children.sort(function(x,y) {return x.href < y.href });
}

AtomWorkspace.prototype._calculateCollectionLevel = function(collection) {
   var count = 0;
   var href = collection.url;
   for (var otherHref in this.collections) {
      if (href!=otherHref && otherHref.length<=href.length && href.substring(0,otherHref.length)==otherHref) {
         count++;
      }
   }
   collection.level = count;
}

AtomWorkspace.prototype.newCollection = function(href,title) {
   var element = this.xml.ownerDocument.createElementNS(ATOM_PUB_INTROSPECT_NS,"collection");
   // Make sure the href is absolute
   var url = null;
   if (this.baseURI.charAt(this.baseURI.length-1)=='/' && href.charAt(0)=='/') {
      url = this.baseURI+href.substring(1);
   } else {
      url = this.baseURI+href;
   }
   element.setAttribute("href",href);
   this.xml.appendChild(element);
   var titleE = this.xml.ownerDocument.createElementNS(ATOM_NS,"title");
   element.appendChild(titleE);
   titleE.appendChild(element.ownerDocument.createTextNode(title));
            
   var collection = new AtomCollection(this,href,url,element);
   this.collections[href] = collection;
   this.treeSort();
   return collection;
}

AtomWorkspace.prototype.createByPost = function(collection,options) {
   var content = _newXMLDocument();
   var feed = content.createElementNS(ATOM_NS,"feed");
   content.appendChild(feed);
   
   var titleE = content.createElementNS(ATOM_NS,"title");
   titleE.appendChild(content.createTextNode(collection.title));
   content.documentElement.appendChild(titleE);
   var authorE = content.createElementNS(ATOM_NS,"author");
   var authorNameE = content.createElementNS(ATOM_NS,"name");
   authorE.appendChild(authorNameE);
   authorNameE.appendChild(content.createTextNode(this.service.author));
   content.documentElement.appendChild(authorE);
   collection.feed.xml = null;
   collection.feed.entries = {};
   var headers = {};
   if (this.service.authorization) {
      headers["authorization"] = this.service.authorization;
   }

   HTTP("POST",collection.url,{
      timeout: options.timeout,
      username: this.service.username,
      password: this.service.password,
      headers: headers,
      overrideMimeType: "text/xml",
      body: content,
      contentType: "application/atom+xml",
      returnHeaders: true,
      onSuccess: function(status,feedDoc,entryText,headers) {
         options.onSuccess();
      },
      onFailure: options.onFailure
   });
}

AtomWorkspace.prototype.removeByDelete = function(collection,options) {
   var theWorkspace = this;
   var headers = {};
   if (this.service.authorization) {
      headers["authorization"] = this.service.authorization;
   }
   HTTP("DELETE",collection.url,{
      timeout: options.timeout,
      username: this.service.username,
      password: this.service.password,
      headers: headers,
      overrideMimeType: "text/xml",
      onSuccess: function(status,feedDoc,entryText,headers) {
         delete theWorkspace.collections[collection.url];
         theWorkspace.xml.removeChild(collection.xml);
         options.onSuccess();
      },
      onFailure: options.onFailure
   });
}

function AtomCollection(workspace,href,url,element,titleSpec) {
   this.type = 2;
   this.workspace = workspace;
   this.href = href;
   this.url = url;
   this.xml = element;
   var title = null;
   if (element) {
      _Atom_forChild(
         this.xml,
         "title",
         ATOM_NS,
         function(target) { title = target; } 
      );
      
      if (title) {
         this.title = title.textContent;
      } else {
         this.title = element.getAttribute("title");
      }
   } else {
      this.title = titleSpec;
   }
   this.feed = new AtomFeed(this,url);
   this.parent = null;
   this.children = [];
}

AtomCollection.prototype.newFeed = function() {
   var content = _newXMLDocument();
   var feed = content.createElementNS(ATOM_NS,"feed");
   content.appendChild(feed);
   
   var titleE = content.createElementNS(ATOM_NS,"title");
   titleE.appendChild(content.createTextNode(this.title));
   content.documentElement.appendChild(titleE);
   var authorE = content.createElementNS(ATOM_NS,"author");
   var authorNameE = content.createElementNS(ATOM_NS,"name");
   authorE.appendChild(authorNameE);
   authorNameE.appendChild(content.createTextNode(this.workspace.service.author));
   content.documentElement.appendChild(authorE);
   this.feed.xml = feed;
}

function AtomFeed(collection,url,xml) {
   this.type = 3;
   this.collection = collection;
   this.url = url;
   this.loaded = false;
   this.id = null;
   this.xml = xml;
   this.entries = {};
   if (this.xml) {
      this.loaded = true;
      this._loadEntries();
   }
}

AtomFeed.prototype.load = function(options) {
   this.xml = null;
   this.loaded = false;
   this.entries = {};
   var current = this;
   var headers = {};
   if (this.collection && this.collection.workspace.service.authorization) {
      headers["authorization"] = this.collection.workspace.service.authorization;
   }
   HTTP("GET",this.url,{
      timeout: options.timeout,
      username: this.collection ? this.collection.workspace.service.username : this.username,
      password: this.collection ? this.collection.workspace.service.password : this.password,
      headers: headers,
      overrideMimeType: "text/xml",
      onSuccess: function(status,document) {
         current.xml = document.documentElement;
         current.loaded = true;
         current._loadEntries();
         options.onSuccess();
      },
      onFailure: options.onFailure
   });
}

AtomFeed.prototype._loadEntries = function() {
   this.entries = {};
   if (!this.xml) {
      return;
   }
   this.firstEntry = null;
   var lastEntry = null;
   var current = this.xml.firstChild;
   while (current) {
      if (current.nodeType!=1) {
         current = current.nextSibling;
         continue;
      }
      if (current.localName=="id" && current.namespaceURI==ATOM_NS) {
         this.id = current.textContent;
      } else if (current.localName=="link" && current.namespaceURI==ATOM_NS) {
         var rel = current.getAttribute("rel");
         if (rel=="edit") {
            this.editURL = current.getAttribute("href");
         } else if (rel=="self") {
            this.selfURL = current.getAttribute("href");
         }
      } else if (current.localName=="entry" && current.namespaceURI==ATOM_NS) {
         var entry = new AtomEntry(this,current);
         entry.prev = lastEntry;
         if (lastEntry) {
            lastEntry.next = entry;
         }
         if (!this.firstEntry) {
            this.firstEntry = entry;
         }
         this.entries[entry.id] = entry;
         lastEntry = entry;
      }
      current = current.nextSibling;
   }
}

AtomFeed.prototype.create = function(options) {

   var headers = {};
   if (this.collection.workspace.service.authorization) {
      headers["authorization"] = this.collection.workspace.service.authorization;
   }
   var current = this;
   HTTP("POST",this.url,{
      timeout: options.timeout,
      username: this.collection.workspace.service.username,
      password: this.collection.workspace.service.password,
      headers: headers,
      overrideMimeType: "text/xml",
      body: this.xml.ownerDocument,
      contentType: "application/atom+xml",
      onSuccess: function(status,feedDoc,entryText,headers) {
         current.xml = feedDoc.documentElement;
         options.onSuccess();
      },
      onFailure: options.onFailure
   });
}

AtomFeed.prototype.updateByPut = function(options) {
   var content = _newXMLDocument();
   var feed = content.createElementNS(ATOM_NS,"feed");
   content.appendChild(feed);
   //var content = this.xml.ownerDocument.implementation.createDocument(ATOM_NS,"feed","");
   //var feed = content.documentElement;
   var current = this.xml.firstChild;
   while (current) {
      if (current.localName=="entry" && current.namespaceURI==ATOM_NS) {
         break;
      }
      feed.appendChild(content.importNode(current,true));
      current = current.nextSibling;
   }
   var headers = {};
   if (this.collection.workspace.service.authorization) {
      headers["authorization"] = this.collection.workspace.service.authorization;
   }
   
   HTTP("PUT",this.url,{
      timeout: options.timeout,
      username: this.collection.workspace.service.username,
      password: this.collection.workspace.service.password,
      headers: headers,
      overrideMimeType: "text/xml",
      body: content,
      contentType: "application/atom+xml",
      onSuccess: function(status,feedDoc,entryText,headers) {
         options.onSuccess();
      },
      onFailure: options.onFailure
   });
}

AtomFeed.prototype.getElement = function(name,ns) {
   var e = null;
   _Atom_forChild(this.xml,name,ns ? ns : ATOM_NS,
      function(target) { 
         e = target; 
      }
   );
   return e;
}

AtomFeed.prototype.getElements = function(name,ns) {
   var list = [];
   _Atom_forChild(this.xml,name,ns ? ns : ATOM_NS,
      function(target) { 
         list.push(target); 
      }
   );
   return list;
}

AtomFeed.prototype.getTitle = function(create) {
   var title = null;
   _Atom_forChild(this.xml,"title",ATOM_NS,function(target) { title = target; } );
   if (create && !title) {
      title = this.xml.ownerDocument.createElementNS(ATOM_NS,"title");
      title.setAttribute("type","text");
      var firstEntry = null;
      _Atom_forChild(this.xml,"entry",ATOM_NS,function(target) { if (!firstEntry) { firstEntry = target} } );
      if (firstEntry) {
         this.xml.insertBefore(title,firstEntry);
      } else {
         this.xml.appendChild(title);
      }
   }
   return title ? new AtomText(title) : null;
}

AtomFeed.prototype.getSubtitle = function(create) {
   var subtitle = null;
   _Atom_forChild(this.xml,"subtitle",ATOM_NS,function(target) { subtitle = target; } );
   if (create && !subtitle) {
      subtitle = this.xml.ownerDocument.createElementNS(ATOM_NS,"subtitle");
      subtitle.setAttribute("type","text");
      var firstEntry = null;
      _Atom_forChild(this.xml,"entry",ATOM_NS,function(target) { if (!firstEntry) { firstEntry = target} } );
      if (firstEntry) {
         this.xml.insertBefore(subtitle,firstEntry);
      } else {
         this.xml.appendChild(subtitle);
      }
   }
   return subtitle ? new AtomText(subtitle) : null;
}
AtomFeed.prototype.addLink = function(rel,href,type,title) {
   var link = this.xml.ownerDocument.createElementNS(ATOM_NS,"link");
   link.setAttribute("rel",rel);
   link.setAttribute("href",href);
   if (type) {
      link.setAttribute("type",type);
   }
   if (title) {
      link.setAttribute("title",type);
   }
   var firstEntry = null;
   _Atom_forChild(this.xml,"entry",ATOM_NS,function(target) { if (!firstEntry) { firstEntry = target} } );
   if (firstEntry) {
      this.xml.insertBefore(link,firstEntry);
   } else {
      this.xml.appendChild(link);
   }
   return link;
}


AtomFeed.prototype.getCategories = function() {
   var values = [];
   _Atom_forChild(this.xml,"category",ATOM_NS,function(e) { values[values.length] = new AtomCategory(e); } );
   return values;
}

AtomFeed.prototype.getCategory = function(scheme,term) {
   var values = [];
   _Atom_forChild(this.xml,"category",ATOM_NS,function(e) { 
      var cat = new AtomCategory(e);
      if (scheme && cat.scheme!=scheme) {
         return;
      }
      if (term && cat.term!=term) {
         return;
      }
      values.push(cat);
   });
   return values;
}

AtomFeed.prototype.getLinks = function(rel) {
   var values = [];
   _Atom_forChild(this.xml,"link",ATOM_NS,function(e) {
      if (!rel || rel && e.getAttribute("rel")==rel) {
         values[values.length] = new AtomLink(e); 
      }
   });
   return values;
}
AtomFeed.prototype.removeAllLinks = function(matchRel) {
   var toRemove = [];
   _Atom_forChild(this.xml,"link",ATOM_NS,
      function(e) { 
         var rel = e.getAttribute("rel");
         if (matchRel && rel==mathcRel) {
            toRemove[toRemove.length] = e;
         } else if (!matchRel && rel!="edit" && rel!="self") {
            toRemove[toRemove.length] = e;
         }
      }
   );
   for (var i=0; i<toRemove.length; i++) {
      this.xml.removeChild(toRemove[i]);
   }
}
AtomEntry.prototype.removeLink = function(link) {
   this.xml.removeChild(link.xml);
}


AtomFeed.prototype.addCategory = function(scheme,term,value) {
   var category = this.xml.ownerDocument.createElementNS(ATOM_NS,"category");
   if (scheme && scheme.length>0) {
      category.setAttribute("scheme",scheme);
   }
   category.setAttribute("term",term);
   if (value) {
      category.appendChild(this.xml.ownerDocument.createTextNode(value));
   }     
   var firstEntry = null;
   _Atom_forChild(this.xml,"entry",ATOM_NS,function(target) { if (!firstEntry) { firstEntry = target} } );
   if (firstEntry) {
      this.xml.insertBefore(category,firstEntry);
   } else {
      this.xml.appendChild(category);
   }
   return new AtomCategory(category);
}

AtomFeed.prototype.removeCategory = function(category) {
   this.xml.removeChild(category.xml);
}

AtomFeed.prototype.removeAllCategories = function() {
   var toRemove = [];
   _Atom_forChild(this.xml,"category",ATOM_NS,function(e) { toRemove[toRemove.length] = e; });
   for (var i=0; i<toRemove.length; i++) {
      this.xml.removeChild(toRemove[i]);
   }
}

AtomFeed.prototype.getId = function() {
   var id = null;
   _Atom_forChild(this.xml,"id",ATOM_NS,function(target) { id = target.textContent; } );
   return id;
}

AtomFeed.prototype.addResource = function(content,options) {

   var currentFeed = this;
   var headers = {};
   if (options.slug) {
      headers["Slug"] = options.slug;
   }
   if (this.collection.workspace.service.authorization) {
      headers["authorization"] = this.collection.workspace.service.authorization;
   }
   //consoleService.logStringMessage("Sending entry to "+this.collection.url+" ...");
   var req = HTTP("POST",this.collection.url,
      {
         username: this.collection.workspace.service.username,
         password: this.collection.workspace.service.password,
         headers: headers,
         contentType: options.contentType,
         body: content,
         returnHeaders: true,
         onSuccess: function(status,entryDoc,entryText,headers) {
            if (entryDoc) {
               var newXML = currentFeed.xml.ownerDocument.importNode(entryDoc.documentElement,true);
               currentFeed.xml.appendChild(newXML);
               var newEntry = new AtomEntry(currentFeed,newXML);
               options.onSuccess(newEntry);
            } else {
               var location = headers["Location"];
               if (!location) {
                  if (consoleService) {
                     consoleService.logStringMessage("There was no entry or location header returned.");
                  }
                  throw "There was no entry or location header returned.";
               }
               HTTP("GET",headers["location"], {
                  timeout: options.timeout,
                  overrideMimeType: "text/xml",
                  onSuccess: function(status,entryDoc) {
                     var newXML = currentFeed.xml.ownerDocument.importNode(entryDoc.documentElement,true);
                     currentFeed.xml.appendChild(newXML);
                     var newEntry = new AtomEntry(currentFeed,newXML);
                     options.onSuccess(newEntry);
                  },
                  onFailure: options.onFailure
               });
            }
         },
         onFailure: function(status,xml,text,headers) {
            if (consoleService) {
               consoleService.logStringMessage("Entry failed: "+status+" "+text);
            }
            options.onFailure(status,xml,text,headers);
         }
      }
   );
}

AtomFeed.prototype._bootstrap = function() {
   var parser = _newDOMParser();
   var doc = parser.parseFromString("<feed xmlns='http://www.w3.org/2005/Atom'/>","text/xml");
   this.xml = doc.documentElement;
}
AtomFeed.prototype.addEntry = function(title) {
   if (!this.xml) {
      this._bootstrap();
   }
   var doc = this.xml.ownerDocument;
   var entry = doc.createElementNS(ATOM_NS,"entry");
   var idE = entry.ownerDocument.createElementNS(ATOM_NS,"id");
   var id = "temp:"+(new Date()).getTime();
   idE.appendChild(doc.createTextNode(id));
   entry.appendChild(idE);
   var titleE = doc.createElementNS(ATOM_NS,"title");
   titleE.appendChild(doc.createTextNode(title));
   entry.appendChild(titleE);
   var authorE = doc.createElementNS(ATOM_NS,"author");
   var authorNameE = doc.createElementNS(ATOM_NS,"name");
   authorNameE.appendChild(doc.createTextNode(this.collection.workspace.service.author));
   authorE.appendChild(authorNameE);
   entry.appendChild(authorE);
   this.xml.appendChild(entry);
   var aentry = new AtomEntry(this,entry);
   aentry.local = true;
   this.entries[id] = aentry;
   return aentry;
}

AtomFeed.prototype.copyEntry = function(otherEntry) {
   return this.addEntryFromXML(otherEntry.xml);
}

AtomFeed.prototype.addEntryFromXML = function(otherEntry,removeEditLink) {
   if (!this.xml) {
      this._bootstrap();
   }
   var entry = this.xml.ownerDocument.importNode(otherEntry,true);
   var id = null;
   _Atom_forChild(entry,"id",ATOM_NS,function(target) { id = target; } );
   if (!id) {
      id = entry.ownerDocument.createElementNS(ATOM_NS,"id");
      entry.appendChild(id);
      var idValue = "temp:"+(new Date()).getTime();
      id.appendChild(this.xml.ownerDocument.createTextNode(idValue));
   }

   if (removeEditLink) {
      var editLink = null;
      _Atom_forChild(entry,"link",ATOM_NS,
          function(target) { 
             if (target.getAttribute("rel")=="edit") { 
                editLink = target; 
             }
          }
      );
      if (editLink) {
         entry.removeChild(editLink);
      }
   }
   this.xml.appendChild(entry);
   var aentry = new AtomEntry(this,entry);
   aentry.local = true;
   this.entries[aentry.id] = aentry;
   return aentry;
}

AtomFeed.prototype.removeEntry = function(entry,options) {
   if (this.entries[entry.id]) {
      if (entry.local) {
         delete this.entries[entry.id];
         this.xml.removeChild(entry.xml);
         if (options) {
            options.onSuccess();
         }
      } else {
         if (!entry.editURL) {
            throw "Entry "+entry.id+" does not have an edit link.";
         }
         var current = this;
         var headers = {};
         if (this.collection.workspace.service.authorization) {
            headers["authorization"] = this.collection.workspace.service.authorization;
         }
         HTTP("DELETE",entry.editURL,{
            username: this.collection.workspace.service.username,
            password: this.collection.workspace.service.password,
            headers: headers,
            timeout: options.timeout,
            overrideMimeType: "text/xml",
            onSuccess: function(status,entryDoc,entryText,headers) {
               delete current.entries[entry.id];
               current.xml.removeChild(entry.xml);
               if (options.onSuccess) {
                  options.onSuccess();
               }
            },
            onFailure: options.onFailure
         });
      }
   } else {
      throw "Entry "+entry.id+" does not belong to this feed.";
   }
}

AtomFeed.prototype.sortByCategory = function(scheme,term,sortby) {
   // copy the entries;
   var sorted = [];
   for (var id in this.entries) {
      var entry = this.entries[id];
      var match = null;
     _Atom_forChild(entry.xml,"category",ATOM_NS,function(e) { 
        if (scheme && e.getAttribute("scheme")!=scheme) {
           return;
        }
        if (term && e.getAttribute("term")!=term) {
           return; 
        }
        if (!match) {
           match = e;
        }
     });
     sorted.push({
        value: match ? _Atom_text(match) : null,
        entry: entry           
     });
   }
   sorted.sort(sortby ? sortby : function(a,b) {
      if (a.value && b.value) {
         return a.value < b.value ? -1 : (a.value>b.value ? 1 : 0);
      } else if (!a.value) {
         return 1;
      } else {
         return -1;
      }
   });
   return sorted;
}

function AtomNumericSort(a,b) {
   if (a.value && b.value) {
      return parseInt(a.value)-parseInt(b.value);
   } else if (!a.value) {
      return 1;
   } else {
      return -1;
   }
}


AtomFeed.prototype.sortByElement = function(name,ns,sortby) {
   // copy the entries;
   var sorted = [];
   for (var id in this.entries) {
      var entry = this.entries[id];
      sorted.push({
         element: entry.getElement(name,ns),
         entry: entry
      });
   }
   sorted.sort(sortby ? sortby : function(a,b) {
      var avalue = _Atom_text(a.element);
      var bvalue = _Atom_text(b.element);
      return avalue < bvalue ? -1 : (avalue>bvalue ? 1 : 0);
   });
   return sorted;
}
function AtomEntry(feed,entry) {
   this.type = 4;
   this.feed = feed;
   this.xml = entry;
   this.id = null;
   this.local = false;
   this.editURL = null;
   this._idChanged();
   this._editURLChanged();
}

AtomEntry.prototype.addLink = function(rel,href,type,title) {
   var link = this.xml.ownerDocument.createElementNS(ATOM_NS,"link");
   link.setAttribute("rel",rel);
   link.setAttribute("href",href);
   if (type) {
      link.setAttribute("type",type);
   }
   if (title) {
      link.setAttribute("title",type);
   }
   this.xml.appendChild(link);
   return link;
}

AtomEntry.prototype.getAlternateLink = function() {
   var link = null;
   _Atom_forChild(this.xml,"link",ATOM_NS,
   function(target) { 
      if (target.getAttribute("rel")=="alternate") {
         link = target; 
      }
   }
   );
   return link;
}
   
AtomEntry.prototype.getElement = function(name,ns) {
   var e = null;
   _Atom_forChild(this.xml,name,ns ? ns : ATOM_NS,
      function(target) { 
         e = target; 
      }
   );
   return e;
}

AtomEntry.prototype.getElements = function(name,ns) {
   var list = [];
   _Atom_forChild(this.xml,name,ns ? ns : ATOM_NS,
      function(target) { 
         list.push(target); 
      }
   );
   return list;
}

AtomEntry.prototype.getTitle = function() {
   var title = null;
   _Atom_forChild(this.xml,"title",ATOM_NS,function(target) { title = target; } );
   if (!title) {
      throw "Entry "+this.id+" is missing a title.";
   }
   return new AtomText(title);
}
AtomEntry.prototype.getSummary = function(create) {
   var summary = null;
   _Atom_forChild(this.xml,"summary",ATOM_NS,function(target) { summary = target; } );
   if (create && !summary) {
      summary = this.xml.ownerDocument.createElementNS(ATOM_NS,"summary");
      this.xml.appendChild(summary);
      summary.setAttribute("type","text");
   }
   return summary ? new AtomText(summary) : null;
}

AtomEntry.prototype.getPublished = function() {
   var e = null;
   _Atom_forChild(this.xml,"published",ATOM_NS,function(target) { e = target; } );
   return e ? e.textContent : null;
}

AtomEntry.prototype.getUpdated = function() {
   var e = null;
   _Atom_forChild(this.xml,"updated",ATOM_NS,function(target) { e = target; } );
   return e ? e.textContent : null;
}

AtomEntry.prototype.getEdited = function() {
   var e = null;
   _Atom_forChild(this.xml,"edited",ATOM_PUB_INTROSPECT_NS,function(target) { e = target; } );
   return e ? e.textContent : null;
}


AtomEntry.prototype.getContent = function(create) {
   var content = null;
   _Atom_forChild(this.xml,"content",ATOM_NS,function(target) { content = target; } );
   if (create && !content) {
      content = this.xml.ownerDocument.createElementNS(ATOM_NS,"content");
      this.xml.appendChild(content);
      content.setAttribute("type","text");
   }
   return content ? new AtomContent(this,content) : null;
}


AtomEntry.prototype.getLinks = function(rel) {
   var values = [];
   _Atom_forChild(this.xml,"link",ATOM_NS,function(e) {
      if (!rel || rel && e.getAttribute("rel")==rel) {
         values[values.length] = new AtomLink(e); 
      }
   });
   return values;
}

AtomEntry.prototype.removeLink = function(link) {
   this.xml.removeChild(link.xml);
}

AtomEntry.prototype.removeAllLinks = function(matchRel) {
   var toRemove = [];
   _Atom_forChild(this.xml,"link",ATOM_NS,
      function(e) { 
         var rel = e.getAttribute("rel");
         if (matchRel && rel==mathcRel) {
            toRemove[toRemove.length] = e;
         } else if (!matchRel && rel!="edit" && rel!="self") {
            toRemove[toRemove.length] = e;
         }
      }
   );
   for (var i=0; i<toRemove.length; i++) {
      this.xml.removeChild(toRemove[i]);
   }
}

AtomEntry.prototype.getCategories = function() {
   var values = [];
   _Atom_forChild(this.xml,"category",ATOM_NS,function(e) { values[values.length] = new AtomCategory(e); } );
   return values;
}

AtomEntry.prototype.getCategory = function(scheme,term) {
   var values = [];
   _Atom_forChild(this.xml,"category",ATOM_NS,function(e) { 
      var cat = new AtomCategory(e);
      if (scheme && cat.scheme!=scheme) {
         return;
      }
      if (term && cat.term!=term) {
         return;
      }
      values.push(cat);
   });
   return values;
}



AtomEntry.prototype.addCategory = function(scheme,term,value) {
   var category = this.xml.ownerDocument.createElementNS(ATOM_NS,"category");
   if (scheme && scheme.length>0) {
      category.setAttribute("scheme",scheme);
   }
   category.setAttribute("term",term);
   if (value) {
      category.appendChild(this.xml.ownerDocument.createTextNode(value));
   }     
   this.xml.appendChild(category);
   return new AtomCategory(category);
}

AtomEntry.prototype.removeCategory = function(category) {
   this.xml.removeChild(category.xml);
}

AtomEntry.prototype.removeAllCategories = function() {
   var toRemove = [];
   _Atom_forChild(this.xml,"category",ATOM_NS,function(e) { toRemove[toRemove.length] = e; });
   for (var i=0; i<toRemove.length; i++) {
      this.xml.removeChild(toRemove[i]);
   }
}

AtomEntry.prototype._xmlChanged = function(xml) {
   if ((xml.localName == "parsererror") &&
       (xml.namespaceURI == "http://www.mozilla.org/newlayout/xml/parsererror.xml")) {  
      throw "There was a parse error on the returned XML: "+xml.firstChild.textContent;
   }
   if (xml.namespaceURI!=ATOM_NS || xml.localName!="entry") {
      throw "The entry element is {"+xml.namespaceURI+"}"+xml.localName+" which is not {"+ATOM_NS+"}entry as expected.";
   }
   var newXML = this.feed.xml.ownerDocument.importNode(xml,true);
   this.feed.xml.replaceChild(newXML,this.xml);
   this.xml = newXML;
   this._idChanged();
   this._editURLChanged();
}

AtomEntry.prototype._idChanged = function() {
   var id = null;
   _Atom_forChild(this.xml,"id",ATOM_NS,function(target) { id = target.textContent; } );
   if (!id) {
      throw "No id was found in the entry element.";
   }
   if (this.feed.entries[this.id] && id!=this.id) {
      var oldId = this.id;
      delete this.feed.entries[oldId];
   }
   this.id = id;
   this.feed.entries[id] = this;
}

AtomEntry.prototype._editURLChanged = function() {
   this.editURL = null;
   var currentEntry = this;
   _Atom_forChild(this.xml,"link",ATOM_NS,
       function(target) { 
          if (target.getAttribute("rel")=="edit") { 
             currentEntry.editURL = target.getAttribute("href"); 
          }
       }
   );
   if (this.editURL) {
      this.editURL = this.feed.url+this.editURL;
      if (this.editURL==this.feed.url) {
         this.editURL = null;
      }
   }
}

AtomEntry.prototype.save = function(options) {
   var serializer = _newXMLSerializer();
   var content = serializer.serializeToString(this.xml);
   if (this.local) {
      // new entry
      var currentEntry = this;
      var headers = {};
      if (this.feed.collection.workspace.service.authorization) {
         headers["authorization"] = this.feed.collection.workspace.service.authorization;
      }
      HTTP("POST",this.feed.collection.url,{
         username: this.feed.collection.workspace.service.username,
         password: this.feed.collection.workspace.service.password,
         headers: headers,
         timeout: options.timeout,
         overrideMimeType: "text/xml",
         body: content,
         contentType: "application/atom+xml",
      returnHeaders: true,
         onSuccess: function(status,entryDoc,entryText,headers) {
            if (entryDoc) {
               currentEntry.local = false;
               try {
                  currentEntry._xmlChanged(entryDoc.documentElement);
                  options.onSuccess();
               } catch (ex) {
                  options.onFailure(500,null,ex);
               }
            } else {
               var location = headers["Location"];
               if (!location) {
                  throw "There was no entry or location header returned.";
               }
               HTTP("GET",headers["location"], {
                  username: this.feed.collection.workspace.service.username,
                  password: this.feed.collection.workspace.service.password,
                  timeout: options.timeout,
                  overrideMimeType: "text/xml",
                  onSuccess: function(status,entryDoc) {
                     currentEntry._xmlChanged(entryDoc.documentElement);
                     options.onSuccess();
                  },
                  onFailure: options.onFailure
               });
            }
         },
         onFailure: options.onFailure
      });
   } else {
      // existing entry
      if (!this.editURL) {
         throw new "Cannot save changed to entry "+this.id+" as the entry does not have an edit relation link.";
      }
      var currentEntry = this;
      var headers = {};
      if (this.feed.collection.workspace.service.authorization) {
         headers["authorization"] = this.feed.collection.workspace.service.authorization;
      }
      HTTP("PUT",this.editURL,{
         username: this.feed.collection.workspace.service.username,
         password: this.feed.collection.workspace.service.password,
         headers: headers,
         timeout: options.timeout,
         overrideMimeType: "text/xml",
         body: content,
         contentType: "application/atom+xml",
         returnHeaders: true,
         onSuccess: function(status,entryDoc,entryText,headers) {
            if (entryDoc) {
               try {
                  currentEntry._xmlChanged(entryDoc.documentElement);
                  options.onSuccess();
               } catch (ex) {
                  options.onFailure(0,null,ex);
               }
            } else {
               var location = headers["Location"];
               if (!location) {
                  throw "There was no entry or location header returned.";
               }
               HTTP("GET",headers["location"], {
                  timeout: options.timeout,
                  overrideMimeType: "text/xml",
                  onSuccess: function(status,entryDoc) {
                     currentEntry._xmlChanged(entryDoc.documentElement);
                     options.onSuccess();
                  },
                  onFailure: options.onFailure
               });
            }
         },
         onFailure: options.onFailure
      });
   }
}

function AtomText(element) {
   this.xml = element;
}


AtomText.prototype.getType = function() { 
   return this.xml.getAttribute("type"); 
}

AtomText.prototype.setType = function(v) {
   this.xml.setAttribute("type",v); 
   this.xml.removeAttribute("src");
}

AtomText.prototype.getXHTMLContent = function() {
   var div = null;
   _Atom_forChild(this.xml,"div","http://www.w3.org/1999/xhtml",function(target) { div = target; } );
   return div;
}

AtomText.prototype.getNodeContent = function() {
   var current = this.xml.firstChild;
   while (current) {
      if (current.nodeType==1) {
         return current;
      }
      current = current.nextSibling;
   }
   return null;
}
AtomText.prototype.setTextContent = function(text) {
   _Atom_removeChildren(this.xml);
   this.xml.appendChild(this.xml.ownerDocument.createTextNode(text));
}

AtomText.prototype.setNodeContent = function(node) {
   if (this.xml.ownerDocument!=node.ownerDocument) {
      node = this.xml.ownerDocument.importNode(node,true);
   }
   _Atom_removeChildren(this.xml);
   this.xml.appendChild(node);
}

function AtomContent(entry,element) {
   this.entry = entry;
   this.xml = element;
}
AtomContent.prototype = new AtomText();
AtomContent.prototype.getMediaReference = function() {
   return this.xml.getAttribute("src");
}
AtomContent.prototype.setMediaReference = function(src,mediaType) {
   this.xml.setAttribute("src",src); 
   this.xml.removeAttribute("type");
   if (mediaType) {
      this.xml.setAttribute("type",mediaType); 
   }
   _Atom_removeChildren(this.xml);
}

AtomContent.prototype.getMediaType = function() {
   return this.xml.getAttribute("type");
}
AtomContent.prototype.setMediaType = function(mediaType) {
   this.xml.setAttribute("type",mediaType); 
}

AtomContent.prototype.getLocation = function() {
   return this.entry.feed.url+this.xml.getAttribute("src");
}

AtomContent.prototype.update = function(content,options) 
{

   var currentFeed = this;
   var headers = {};
   if (this.entry.feed.collection.workspace.service.authorization) {
      headers["authorization"] = this.entry.feed.collection.workspace.service.authorization;
   }
   var contentType = options.contentType ? options.contentType : this._type;
   var req = HTTP("PUT",this.getLocation(),
      {
         username: this.entry.feed.collection.workspace.service.username,
         password: this.entry.feed.collection.workspace.service.password,
         headers: headers,
         contentType: contentType,
         body: content,
         returnHeaders: true,
         onSuccess: function(status,entryDoc,entryText,headers) {
            options.onSuccess();
         },
         onFailure: function(status,xml,text,headers) {
            options.onFailure(status,xml,text,headers);
         }
      }
   );
}

AtomContent.prototype.getMedia = function(options) 
{
   var req = HTTP("GET",this.getLocation(),
      {
         username: this.entry.feed.collection.workspace.service.username,
         password: this.entry.feed.collection.workspace.service.password,
         onSuccess: options.onSuccess,
         onFailure: options.onFailure
      }
   );
}

function AtomCategory(element) {
   this.xml = element;
   this.scheme = element ? element.getAttribute("scheme") : null;
   this.term = element ? element.getAttribute("term") : null;
   this.value = element ? _Atom_text(element) : null;
}

AtomCategory.prototype.set = function(scheme,term,value) {
   if (scheme) {
      this.xml.setAttribute("scheme",scheme);
   } else {
      this.xml.removeAttribute("scheme");
   }
   this.xml.setAttribute("term",term);
   _Atom_removeChildren(this.xml);
   if (value) {
      this.xml.appendChild(this.xml.ownerDocument.createTextNode(value)); 
   }
}


function AtomLink(element) {
   this.xml = element;
   this.rel = element ? element.getAttribute("rel") : null;
   this.href = element ? element.getAttribute("href") : null;
   this.type = element ? element.getAttribute("type") : null;
   this.title = element ? element.getAttribute("title") : null;
}

AtomLink.prototype.set = function(rel,href,type,title)
{
   this.xml.setAttribute("rel",rel);
   this.xml.setAttribute("href",href);
   if (type) {
      this.xml.setAttribute("type",type);
   } else {
      this.xml.removeAttribute("type")
   }
   if (title) {
      this.xml.setAttribute("type",type);
   } else {
      this.xml.removeAttribute("title")
   }
}

