/**
 * Copyright 2009 Lime Labs LLC
 *
 * This file is part of AXIS.
 *
 * AXIS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License
 * version 3 as published by the Free Software Foundation.
 *
 * AXIS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with AXIS.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @fileoverview
 *
 * CustomEvents
 *
 * Allows the creation of custom events which can be subscribed to and fired.
 *
 *  @requires AXIS
 */

function CustomEvents()
  {
    /**
     * @constructor
     */
    this.__construct = function()
      {
      };
    
    /**
     * Creates a subscribable event.
     *
     *  @param      {Object}      ob    Object of form:
     *                {
     *                  {String}  name      Name of event.
     *                  {Object}  [scope]   Scope to fire subscrber handler in. Default window.
     *                }
     */
    this.create = function(ob)
      {
        var d       = ob || {};
        d.name      = d.name || '';
        d.scope     = d.scope || window;

        /**
         * Note that there is a closure on d.name throughout the method definitions
         * for the custom object defined below.
         */
        
        var evOb        = function()
          {
            this._subscribers   = [];
            this._hasFired      = false;
            this._scope         = d.scope;
            this._name          = d.name;
          };
          
        evOb.prototype  =
          {
            /**
             * Subscribes to an event.
             *
             *  @param      {Object}      ob    Object of form:
             *                {
             *                  {Function}  callback    Function to call when event fires.
             *                  {Object}    [object]    Scope to fire subscrber handler in. Default window.
             *                }
             */
            subscribe:      function(ob)
              {
                var b         = ob || {};
                b.callback    = b.callback  || AXIS.F;
                b.object      = b.object    || {};
                b.wait        = b.wait || false;
                
                this._subscribers.push(b);  
                
                if(this._hasFired && (b.wait === false))
                  {
                    b.callback.apply(this._scope,this._hasFired); 
                  }
              },
              
            fire:           function(a)
              {
                var fR;
                var args = a || [];

                /**
                 *  @see  #subscribe
                 *  @see  #hasFired
                 */
                this._hasFired = args;
                
                for(var f=0; f < this._subscribers.length; f++)
                  {
                    ss = this._subscribers[f];
                    ss.callback.apply(this._scope,args.concat([this._name, ss.object]));
                  }
              },
            
            hasFired:       function()
              {
                return this._hasFired;  
              }
          };
        
        return new evOb;
      };
  };
  
/**
 * Copyright 2008, 2009 Lime Labs LLC
 *
 * This file is part of AXIS.
 *
 * AXIS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License
 * version 3 as published by the Free Software Foundation.
 *
 * AXIS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with AXIS.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @fileoverview
 */
function Util()
  {
    /**
     * @constructor
     */
    this.__construct = function()
      {
        AXIS.onContentReady(function(){
          var limefooter = document.getElementById('limefooter');
          if (limefooter == null)
            return;

          var get_bit_link = limefooter.getElementsByTagName('span')[0];
          if (get_bit_link) {
            get_bit_link = get_bit_link.getElementsByTagName('a')[0];
            AXIS.attachEvent('click', function(e) {
                if (!e.preventDefault) {
                    e.returnValue = false;
                }
                else {
                    e.preventDefault();
                }

                var bitPath;
                if (this.getAttribute) {
                    bitPath = this.getAttribute('bitPath') || location.pathname;
                }
                else {
                    bitPath = location.pathname;
                }

              AXIS.Util.bit.getBit(bitPath);
            }, get_bit_link);
          }
          
          if (AXIS._siteData.hosts.limebits == 'http://www.limebits.com/')
            return;
          
          var footer_links = limefooter.getElementsByTagName('a');
          for (var i = 0; i < footer_links.length; i++)
            {
              var href = footer_links[i].href;
              if (href.match(/http:\/\/[^\.]*.limebits.com\//))
                footer_links[i].href = href.replace('limebits.com', AXIS._siteData.hosts.limebits.match(/http:\/\/www\.([^\/]*)\//)[1]);
            }
        });
      };

    this.bit =
      {
        getBit: function(bitPath)
          {
            var bit_folder = bitPath.match(/.*\//)[0]; // match till the last folder in the path
            bit_folder = bit_folder.replace(/(\/*)$/, ''); // get rid of the trailing '/'
            var bit_folder_name = bit_folder.replace( /.*\//, "" );
            var bit_file = bitPath.replace( /.*\//, "" ); // get the basename

            var user = AXIS.Cookies.read('user');

            if (AXIS._siteData.hosts.limebits != ("http://" + location.host + "/"))
              {
                return;
              }

            if (user)
              {
                var dst_folder = '/home/' + user;
              
                AXIS.WebDAV.COPY({
                  url: bit_folder,
                  destination: dst_folder + '/' + bit_folder_name,
                  tryAlternateNames: true,
                  on201: function(r) {
                    if (r.alternateName) {
                      bit_folder_name = r.alternateName;
                    }
                    var dst_url = AXIS._siteData.hosts.limebits.replace("http://www.", "http://" + user + ".") + bit_folder_name + "/" + bit_file;
                    AXIS.Cookies.create(dst_url, "new_copy", { path: '/apps/bar' });
                    top.location = dst_url + "?bar";
                  }
                });
              }
            else
              {
                var rand = "" + Math.random();
                AXIS.Cookies.create(rand, bitPath, { path: '/apps/bar' });
                AXIS.Login.redirReturnTo = AXIS._siteData.hosts.limebits + 'apps/bar/copy_proxy.html#' + rand;
                AXIS.Login._redirectToAccess("if_new");
              }
          }
      }
      
    this.uri = 
      {
        basename: function(path)
          {
            return path.replace(/.*\//, "");
          },

        /* get folder containing given URL,
         * will return the URL itself if it ends in '/'
         * otherwise strips all non '/' characters at the end */
        getFolder: function(url) {
          return url.replace(/\/[^\/]*$/,'/');
        },

        getParent: function(url) {
            return url.replace(/\/[^\/]*\/?$/, '/');
        },

        findAlternateName: function(name, used_names)
          {
            if (!used_names.indexOf)
            {
              used_names.indexOf = function(item)
              {
                for (var i = 0; i < this.length; i++)
                  if (this[i] === item) return i;
                return -1;
              }
            }
            var new_name, extension;
            if (name.indexOf('.') == -1)
              {
                new_name = name;
                extension = '';
              }
            else
              {
                var name_parts = name.match(/(.*)(\.[^.]*)$/);
                new_name = name_parts[1];
                extension = name_parts[2];
              }

            var start_num = 2;
            var name_parts = new_name.match(/(.*)([\d]+)$/);
            if (name_parts)
              {
                new_name = name_parts[1];
                start_num = parseInt(name_parts[2]) + 1;
              }

            var new_full_name;
            var end_num = start_num + used_names.length + 1;
            for (var i = start_num; i <= end_num; i++)
              {
                var new_full_name = new_name + i + extension;
                if (used_names.indexOf(new_full_name) == -1) break;
              }
            return new_full_name;
          }
      };

    this.xml =
      {
        iterableToArray: function(iterable) {
          if (iterable.toArray) return iterable.toArray();
          var length = iterable.length, results = new Array(length);
          while (length--) results[length] = iterable[length];
          return results;
        },
        getChildrenByNameNS: function(el, name, ns) {
          if (ns == null)
            ns = "DAV:";

          if (typeof el.getElementsByTagNameNS == "function")
            return this.iterableToArray(el.getElementsByTagNameNS(ns, name));
          else {
            el.ownerDocument.setProperty("SelectionLanguage", "XPath");
            el.ownerDocument.setProperty("SelectionNamespaces", "xmlns:pref='"+ns+"'");

            var child_els = el.selectNodes("*//pref:" + name);
            if (child_els.length == 0)
              child_els = el.selectNodes("pref:" + name);

            return this.iterableToArray(child_els);
          }
        },
        getText: function(node)
          {
            var txt = "";
            for (var i = 0; i < node.childNodes.length; i++)
              {
                if (node.childNodes[i].nodeType == AXIS.TEXT_NODE)
                  txt += node.childNodes[i].nodeValue;
              }
            return txt;
          }
      }

    this.lang =
      {
        augmentObject: function(r, s) {
          for(var p in s)
            {
              r[p] = s[p];
            }
        }
      }

  }
/**
 * Copyright 2008, 2009 Lime Labs LLC
 *
 * This file is part of AXIS.
 *
 * AXIS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License
 * version 3 as published by the Free Software Foundation.
 *
 * AXIS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with AXIS.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @fileoverview
 */
function Cookies()
  {
    /**
     * @constructor
     */
    this.__construct = function()
      {
      };
    
    this.create = function(name,value,o) 
      {
        o = o || {};
        name = encodeURIComponent(name);
        value = encodeURIComponent(value);
        o.path = o.path || "/";

        if(o.days) 
          {
            var date = new Date();
            date.setTime(date.getTime()+(o.days*24*60*60*1000));
            var expires = "; expires="+date.toGMTString();
          } 
        else 
          {
            var expires = "";
          }
        
        try
          {
            document.cookie = name+"="+value+expires+"; path="+o.path;
            return true;
          }
        catch(e)
          {
            return false;
          }
      };
      
    this.update = function(name,value,days) 
      {
        /*
         * bridge function to allow more accurate
         * description of script flow
         */
        var n = name || null;
        var v = value || null;
        var d = days || null;
        
        return this.create(n,v,d);
      };
      
    this.read = function(name) 
      {
        var nameEQ = encodeURIComponent(name) + "=";
        var ca = document.cookie.split(';');
        for(var i=0;i < ca.length;i++) 
          {
            var c = ca[i];
            while(c.charAt(0)==' ')
              {
                c = c.substring(1,c.length);
              }
            
            if(c.indexOf(nameEQ) == 0) 
              {
                return decodeURIComponent(c.substring(nameEQ.length,c.length));
              }
          }
        return false;
      };
      
    this.erase = function(name, o) 
      {
        o = o || {};
        o.days = -1;
        return this.create(name,"", o);
      };
  };/**
 * Copyright 2008, 2009 Lime Labs LLC
 *
 * This file is part of AXIS.
 *
 * AXIS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License
 * version 3 as published by the Free Software Foundation.
 *
 * AXIS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with AXIS.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 *  @fileoverview
 *
 *  @requires  AXIS
 *  @requires  Cookies
 */
function User()
  {
    /**
     * @constructor
     */
    this.__construct = function()
      {
      };
    
    /**
     * Loads a client's geodata, via Google's Data API.
     * Note that this is making an XHR call, so the data
     * will be available at some point in the future -- it is likely
     * that you would want to set a callback to inform your application
     * of when this data is available.  
     *
     *  @param      {Function}    [cb]    The callback function.
     *  @example    AXIS.User.getUserGeoData(function() {
     *                alert(AXIS.User.Data.get('city'));
     *              });  
     */
    this.getUserGeoData = function(cb)
      {
        if(AXIS.Modules)
          {
            AXIS.Modules.load({
            	provider: 'google',
            	module:   'gdata',
             	version:  '1',
             	options:  
             	  {
             	    callback: function()
             	      {
             	        var v = google.loader.ClientLocation;
                      var a = v.address;
                      var d = AXIS.User.Data;

                      d.set('latitude', v.latitude || '');
                      d.set('longitude', v.longitude || '');
                      d.set('city', a.city || '');
                      d.set('country', a.country || '');
                      d.set('country_code', a.country_code || '');
                      d.set('region', a.region || '');  
                      
                      cb && cb();
             	      }
             	  }
            }); 
         }
      };

    this.Data = 
      {
        set: function(k,v)
          {
            if(k && v)
              {
                this[k] = v;  
              }    
          },
          
        get: function(k)
          {
            if(k && this[k])
              {
                return this[k];  
              }
            else
              {
                return false;  
              }
          }
      };
      
    /*
     * Checks if user is logged in by reading cookie ('user');
     * NOTE: use #username if possible.
     *
     *  @return    {Mixed} username if logged in; {Boolean} false if not
     */ 
    this.isLoggedIn = function()
      {
        return AXIS.Cookies.read('user');  
      };

    /**
     *  Alias of #isLoggedIn: more readable.
     *
     *  @return    {String} username if logged in; 'unauthenticated' if not
     */
    this.username = function()
      {
        return AXIS.Cookies.read('user') || 'unauthenticated';  
      };
  };/**
 * Copyright 2008, 2009 Lime Labs LLC
 *
 * This file is part of AXIS.
 *
 * AXIS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License
 * version 3 as published by the Free Software Foundation.
 *
 * AXIS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with AXIS.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @fileoverview
 */
function Loader() 
  {
    /**
     * @constructor
     */
    this.__construct = function()
      {
        /*
         * all requests will go into _activeRequests until a locked request.
         * TODO: create locked requests, and request queueing
         */
        this._activeRequests  = [];
        
        AXIS.onContentReady(function()
          {
            this.loadingPanel = document.body;
            this.createLoadingPanel();
          },this);
          
        /*
         * 1:   Supported by all browsers
         * 2:   Supported by rfc2616 (Opera supports these)
         * 3:   Basic methods, supported by IE.
         * n:   Gecko(Firefox) supports all methods
         *
         * NOTE: methods of higher index include all lower indexed methods
         */
        this.supportedMethods     = {
                                      GET:                1,
                                      POST:               1,
                                      HEAD:               2,
                                      PUT:                2,
                                      DELETE:             2,
                                      PROPFIND:           3,
                                      PROPPATCH:          3,
                                      MKCOL:              3,
                                      LOCK:               3,
                                      UNLOCK:             3,
                                      COPY:               3,
                                      MOVE:               3,
                                      REPORT:             3,
                                      SEARCH:             3,
                                      CHECKIN:            3,
                                      CHECKOUT:           3,
                                      'VERSION-CONTROL':  3                                           
                                    };
      };
      
    /**
     * Use this method to initiate an XHR call, mainly if you simply want to 
     * fetch a resource using GET.  A more comprehensive API for sending various
     * types of HTTP calls can be found in AXIS#WebDAV.  A direct call using this
     * method would look like:
     *
     * AXIS.Loader.load({url: foo/bar.html});
     * 
     * NOTE: default method is GET; and TRUE is the default for .asynch property.
     */

    this.load = function(a)
      {  
        /*
         * Validate "core" attributes
         */
        if(!!a.url === false)
          {
            AXIS.Errors.catchError(new Error('XHR_NO_URL_SENT'));
            return false;  
          }
          
        a.method        = (a.method) ? a.method.toUpperCase() : 'GET';
        a.url           = a.url;
        a.callback      = a.callback      || AXIS.F;
        a.onBeforeSend  = a.onBeforeSend  || AXIS.F;
        a.onAfterSend   = a.onAfterSend   || AXIS.F;
        a.onSuccess     = a.onSuccess     || AXIS.F;
        a.onFailure     = a.onFailure     || AXIS.F;
        a.callId        = a.callId        || AXIS.getUniqueId('xhr_');
        a.passThru      = a.passThru      || [];
        a.asynch        = (a.asynch === undefined) ? false : !!a.asynch;
        a.body          = a.body          || null;
        a.username      = a.username      || null;
        a.password      = a.password      || null;
        a.loadingMsg    = a.loadingMsg    || AXIS.defaultLoadingMsg;
        a.headers       = a.headers       || {};
        
        /**
         * To simplify matters for user, allow the sending of succ/fail codes in
         * basic array format ( [403,412,423] ).  However, we'll want to look
         * them up as keys (codes[200] === true).  So translate here.
         *
         *  @see  XMLHTTP#build#main
         */
        a.failureCodes  = a.failureCodes  || [];
        for(var f=0; f<a.failureCodes.length; f++)
          {
            a.failureCodes[a.failureCodes[f]] = true;  
          }

        a.successCodes  = a.successCodes  || [];
        for(var f=0; f<a.successCodes.length; f++)
          {
            a.successCodes[a.successCodes[f]] = true;  
          }
          
        /*
         * If the method is not supported (either by the browser or
         * in general) send as a special query
         */
        if(!this.methodSupported(a.method))
          {
            var auth = document.cookie.match(new RegExp('(^|;)\\s*' + escape("auth") + '=([^;\\s]*)'));
            auth = auth ? unescape(auth[2]) : null;
            if(auth) 
              {
                auth = "&auth=" + auth;
              }
            else
              {
                auth = "";
              }
                  
            a.url += "?webdav-method=" + a.method.toUpperCase() + auth;
            a.method = 'POST';
            
            //alert('special load');
          }
          
        try
          {
           /*
             * Add to the displayed list of queued xhr objects
             */
            if(this.addRequest(a))
              {
                /**
                 * Start the loading.  There is some particular thinking here
                 * around how to treat synch/asynch calls.  In the case of
                 * asynch, we use the Queue to wait for a response, and
                 * to then handle callbacks and so forth.
                 * We could also use synch in this way, and the framework can
                 * handle that.  However, for those who use synch calls, it may
                 * be more appropriate for, instead of using callbacks, for 
                 * Loader#load to return the response directly.  As the 
                 * AXIS.XMLHTTP.send function will be executing the .httpHandle.send()
                 * XHR method, a synch call will block, and since we know that
                 * when the block is cleared we have the response object, simply
                 * call .main() (which does some processing on the response), and
                 * return the result.  Note as well, any callbacks that are set
                 * will still be called *prior* to this returning.  This should
                 * provide the best of both worlds, direct functional responses
                 * (for those who want blocking), and a callback structure (for
                 * those who would prefer a non-blocking, asynchronous model).
                 */
                         
                /**
                 * This gets back the fully formed call object, to be either
                 * queued or not, as described above.
                 */
                var ss = AXIS.XMLHTTP.send(a);

                if(a.asynch === false)
                  {
                    return ss.main();
                  }
                else
                  {
                    return AXIS.Queue.add(ss);  
                  }
              }
          }
        catch(e)
          {
            /*
             * The error is probably with the send attempt, so attempt to clear 
             * the View of any "Loading" messages tied to this attempt.
             */
            if(a && a.callId)
              {
                this.clearLoadingPanelItem(a.callId);   
              }
              
            AXIS.Errors.catchError('XHR_LOADER_ADD_FAIL',e);
            return false;
          }
      };

    this.methodSupported = function(meth)
      {
        var sm = this.supportedMethods[meth];

        if(sm)
          {    
            if(sm < 2)                      return true;
            if(AXIS.isOpera && (sm < 3))    return true;
            if(AXIS.isIE && (sm < 4))       return true;
            if(AXIS.isMoz || AXIS.isGecko)  return true;
          }
          
        return false;
      };
      
    this.addRequest = function(r)
      {
        if(r.callId && this.notDuplicateCall(r.callId))
          {
            this.updateLoadingPanel(r.callId,r.loadingMsg); 
            this._activeRequests.push(r);
            return true;
          }
        return false;
      };
          
    this.notDuplicateCall = function(callId)
      {
        var aR  = AXIS.Loader._activeRequests;
        var i   = aR.length;
        while(i--)
          {
            if(aR[i].callId == callId)
              {
                return(false);
              }
          }
        return(true);
      };
          
    this.afterResponseCleanup = function(retOb)
      {
        var aR  = AXIS.Loader._activeRequests;
        var i   = aR.length;
        while(i--)
          {
            if(aR[i].callId == retOb.callId)
              {
                /*
                 * Clear acitve request
                 */
                aR.splice(i,1);
              }
          }
        AXIS.Loader.clearLoadingPanelItem(retOb.callId);

        //retOb.callback(retOb);
      };

    this.createLoadingPanel = function()
      {
        try
          {
            var b = document.createElement('div');
            b.id          = AXIS.loadingMsgContainerId;
    
            
            document.body.appendChild(b);
          }
        catch(e){}
      };
      
    this.updateLoadingPanel = function(id,msg)
      {
        if(AXIS.settings('showLoadingInfo'))
          {
            try
              {
                var e = document.createElement('div');
                e.setAttribute('id',id);
                e.className = AXIS.loadingMsgItemClass;
                e.appendChild(document.createTextNode(msg));
                 
                var f = document.getElementById(AXIS.loadingMsgContainerId);
                f.style.width     = '100%';    
                f.appendChild(e);
              }
            catch(e){}
          }
      };
      
    this.clearLoadingPanelItem = function(id)
      {
        try
          {
            var z = document.getElementById(id);

            var pn = z.parentNode;
            pn.removeChild(z);
    
            if(pn.childNodes.length == 0)
              {
                var f = document.getElementById(AXIS.loadingMsgContainerId).style;
                f.width = '0%';
              }
          }
        catch(e){}
      };
  };
/**
 * Copyright 2008, 2009 Lime Labs LLC
 *
 * This file is part of AXIS.
 *
 * AXIS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License
 * version 3 as published by the Free Software Foundation.
 *
 * AXIS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with AXIS.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @fileoverview
 *
 * The main routines for XHR.  The main purpose is to provide methods to build 
 * and enhance the transport package, make the call, check status and throw
 * transport errors. 
 *
 *  @throws   Error     XHR_401
 *  @throws   Error     XHR_423
 *  @throws   Error     XHR_500
 *  @requires AXIS
 */
function XMLHTTP()
  {
    /**
     * @constructor
     */
    this.__construct = function()
      {
        /**
         * The headers that will be sent with every request, unless overridden
         *
         * @see #send
         */
        this.defaultHeaders = 
          {
            'X-Requested-With':   'XMLHttpRequest',
            'Content-Type':       'text/html'
          };
          
        /**
         * Caching information and storage for GET calls.
         * 
         *  @see #send
         *  @see #build#main
         */
        this.cache            = {};
        
        
        /**
         * The receipt of a status code *not* present in this list results
         * in the call being treated as failed.
         *
         * NOTE: the `reshuffling` done on this Array following definition.
         *
         * @see #build
         */
        this.statusCodes = 
          [
            100, // Continue
            102, // Processing (WebDAV) (RFC 2518)  
            
            200, // OK
            201, // Created
            202, // Accepted
            203, // Non-Authoritative Information (since HTTP/1.1)
            204, // No Content
            205, // Reset Content
            206, // Partial Content
            207, // Multi-Status (WebDAV)
            208, // Already reported.
            300, // Multiple Choices
            301, // Moved Permanently
            302, // Found
            303, // See Other (since HTTP/1.1)
            304, // Not Modified
            305, // Use Proxy (since HTTP/1.1)
            307, // Temporary Redirect (since HTTP/1.1)
            
            400, // Bad Request
            401, // Unauthorized
            402, // Payment Required
            403, // Forbidden -- Note Opera will often return this instead of 401
            404, // Not Found  
            405, // Method Not Allowed
            406, // Not Acceptable
            407, // Proxy Authentication Required
            408, // Request Timeout
            409, // Conflict
            410, // Gone
            411, // Length Required
            412, // Precondition Failed
            413, // Request Entity Too Large
            414, // Request-URI Too Long
            415, // Unsupported Media Type
            416, // Requested Range Not Satisfiable
            417, // Expectation Failed
            422, // Unprocessable Entity (WebDAV) (RFC 4918)
            423, // Locked (WebDAV) (RFC 4918)
            424, // Failed Dependency (WebDAV) (RFC 4918)
            426, // Upgrade Required (RFC 2817)
            449, // Retry With
            
            500, // Internal Server Error
            501, // Not Implemented
            502, // Bad Gateway
            503, // Service Unavailable
            504, // Gateway Timeout
            505, // HTTP Version Not Supported
            506, // Loop detected (Bind Draft)
            507, // Insufficient Storage (WebDAV) (RFC 4918)
            509, // Bandwidth Limit Exceeded
            510  // Not Extended (RFC 2774)
          ];
          
        /**
         * Create another accessor, creating object properties from values
         * (allowing checks for statusCodes[sc])
         */
        var i = this.statusCodes.length;
        while(i--)
          {
            this.statusCodes[this.statusCodes[i]] = this.statusCodes[i];  
          }
      };
      
    /**
     * It is often the case that an API will set status code handlers
     * for HTTP responses (on404, on200, etc).  As well, it is common
     * for several of these to be set per response.  This function aims
     * to make that process easier, allowing the developer to send a status
     * code and either a handler function or an AXIS.Errors code (which is
     * then translated into a function which fires an error notification).  As
     * well, the developer can set more than one of these to set at a time. 
     * Function must be called status:function||code pairs, such as:
     *           AXIS.XMLHTTP.setStatusHandlerForResponse(xmlhttprespobj,
     *             {
     *               200: function(){ // do something },
     *               404: 'AXIS_ERROR_CODE'
     *             }
     * Notice as well that should there already is a handler, it is not overridden.
     *
     *  @param    {Object}  o   The returned object from AXIS#XMLHTTP#send
     *  @param    {Object}  i   The handler info
     *
     *  @see AXIS#WebDAV
     */
    this.setStatusHandlerForResponse = function(o,i)
      {
        var f = i || {};
        
        var n = function(t,ns,nf)
          {
            t['on' + ns]  = t['on' + ns] || (nf[ns].constructor === String) ? function()
              {
                AXIS.Errors.catchError(new Error(nf[ns]));    
              } : nf[ns];
          }
        
        for(var s in f)
          {
            n(o || {},s,f);
          }
      };
    
    /**
     * Use this to override the default headers for XHR object, or to set others.
     */
    this.setRequestHeader = function(xhr,nm,hd)
      {
        xhr.httpHandle.setRequestHeader(nm, hd);
      };
    
    /**
     * Will set default headers for the call object (see #XMLHTTP). NOTE that
     * should an equivalent header be set in the call object, the default will
     * of course not override it.
     */        
    this.setDefaultHeaders = function(xhr) 
      {
        var ch = xhr.headers;
        
        for(var h in this.defaultHeaders)
          {
            if(!ch[h])
              {
                this.setRequestHeader(xhr, h, this.defaultHeaders[h]);
              }
          }
      };
    
    /**
     * Constructs an XHR object.  It is built to be attached to AXIS#Queue
     *
     * {@link AXIS#Queue}
     * @return    An extended XHR object
     * @type      Object
     */
    this.build = function(a)
      {
        var xob = 
          {
            httpHandle: window.ActiveXObject ? new ActiveXObject("Msxml2.XMLHTTP") : new XMLHttpRequest(),

            /**
             * Interprets the .status property of this.httpHandle, adjusting
             * for various quirks, only allowing acceptable codes, warning if
             * errors happen.
             *
             * @see     #main
             * @return  False on error, a number if successful
             * @type    Mixed
             */
            getStatus: function()
              {
                var hh = this.httpHandle;
                //alert('stat:' + hh.status);
                /*
                 * TODO: testing xbrow
                 */
                if((!hh.status && location.protocol == 'file:') || hh.status)
                  {
                    
                    /**
                     * Check for IE oddity of creating status `1223` when
                     * correct is `204`. Simply return reset value
                     */
                    if(hh.status == 1223)
                      {
                        return 204;  
                      }
                    
                    /**
                     * @see XMLHTTP#XMLHTTP#statusCodes
                     */
                    if(hh.status == 403 && AXIS.isOpera)
                      {
                        return 401; 
                      }

                    /**
                     * Only return on acceptable status codes
                     */
                    if(AXIS.XMLHTTP.statusCodes[hh.status])
                      {
                        return hh.status;
                      }
                  }
                return false;
              },
            
            lifespan: a.lifespan || AXIS._maxXHRLifespan,
            onBeforeDie: a.onBeforeTimeout || function()
              {
                AXIS.Errors.catchError(new Error('XHR_REQUEST_TIMEOUT'))  
              },
            
            /**
             * The XHR object should be attached to the AXIS.Queue, and
             * will wait for the readyState to hit `4`.
             *
             * .readyState values:
             * 0: uninitialized
             * 1: loading
             * 2: loaded
             * 3: interactive
             * 4: complete
             * 
             * {@link Queue}
             * @type  Boolean
             */
            main: function()
              { 
                /**
                 * Synch calls, which block, can only reach here after .main() is
                 * called, which must be after the block.  So we know, if synch, that
                 * we have a response, and don't need to check readyState.   This also
                 * fixes issues with Safari and perhaps some other browsers and how
                 * they may or may not properly handle .readyState when using synch.
                 */
                if((this.httpHandle.readyState == 4) || (this.asynch === false)) 
                  {     
                    var T = this;
                    
                    /**
                     * Ensure that we snip this instance off the queue, in case
                     * of some error happening where the main function fails
                     * to return false. NOTE: relevant only to asynch calls (Queue)
                     */
                    this.die && this.die();

                    /**
                     * Handles the loading notifications cleanup
                     */
                    AXIS.Loader.afterResponseCleanup(T);
       
                    var stat = T.getStatus();

                    T.responseText = T.httpHandle.responseText || ''; 
                    T.responseXML = '';
                    
                    if(T.httpHandle.responseXML)
                      {
                        T.responseXML = T.httpHandle.responseXML;
                        T.serializedXML = function()
                          {
                            return AXIS.isIE ? this.responseXML.xml : (new XMLSerializer()).serializeToString(this.responseXML);
                          }
                      }

                    if(stat == 304)
                      {
                        /**
                         * Not modified response is 304; in those cases we want to send back
                         * the cached version, if any, to the callback.  So we first make sure we handle
                         * the 304 (to replace current response with cached response).  The callback also
                         * needs to be modified, in order to provide caching for fresh responses.
                         */
                         //alert('304: '+T.url);

                         try
                           {
                             var cac              = AXIS.XMLHTTP.cache[T.url];
                             
                             T.responseText       = cac.responseText;
                             T.responseXML        = cac.responseXML;
                           }
                         catch(e)
                           {
                             alert('cache error');
                           }
                      }
                    else if(stat === 200 && T.method === 'GET') // only cache successful GET's
                      {                        
                        /**
                         * On first load, cache info.
                         * Fetch the Etag and use to store match flag.
                         * Unfortunately response header not always available on all browsers.
                         */
                        try
                          {
                            T.__ETAG__ = T.httpHandle.getResponseHeader("Etag");
                          }
                        catch(e)
                          {
                            T.__ETAG__ = '"0"'; 
                          }
                          
                        AXIS.XMLHTTP.cache[T.url]  = T;
                      }
                    
                    /**
                     * Call the response processor, if any.  Note that this will be returned
                     * as the second argument of a response handler.  
                     */ 
                    var procResp =  T.responseProcessor 
                                    ? T.responseProcessor.call(T.scope, T) 
                                    : false;
                      
                    /**
                     * NOTE how we call onXXX handlers prior to other response handlers.
                     */
                    T['on' + stat] && T['on' + stat].call(T.scope, T, procResp);

                    /**
                     * Various status code checking, errors, successes, etc.
                     */
                    if((stat === false) || T.failureCodes[stat])
                      {
                        T.onFailure.call(T.scope, T, procResp);
                      }
                    /**
                     * Check for special status errors.  These are critical errors that
                     * should not happen, or are worthy of ensuring that the user is notified
                     * of their occurrence (ie. 401).  If the user has defined an error handler 
                     * for the specified status codes, that is handled below.
                     */
                    else if(  (stat == 401)
                            ||(stat == 423)
                            ||(stat >= 500)) 
                      {
                        /**
                         * Do not fire default errors if user has own handler
                         */
                        if(!T['on' + stat])
                          {
                            AXIS.Errors.catchError(new Error('XHR_' + stat + '~~' + T.url));
                          }
                        T.onFailure.call(T.scope, T, procResp);
                      }
                    /**
                     * A call is successful only if it satisfies this conditional.
                     */
                    else if(  ((stat > 199)
                            &&(stat < 209))
                            || stat == 304
                            ||T.successCodes[stat])
                      { 
                        T.onSuccess.call(T.scope, T, procResp);
                      }
                    
                    /**
                     * NOTE: .callback is always called whether success or failure, useful
                     * for creating a single handler.
                     */
                    T.callback.call(T.scope, T, procResp);
   
                    return procResp || T;  
                  } 
                return true;
              }
          };

        /**
         * Now attach request args to the returned object
         */
        for(var p in a)
          {
            xob[p] = a[p];  
          }

        xob.httpHandle.onreadystatechange = AXIS.F;
        
        return(xob);
      };
    
    this.send = function(a)
      {
        var CB = this.build(a);
        
        CB.onBeforeSend(); 

        /**
         * This scope will be applied to the response handlers, if any.
         *
         * @see #build#main
         */
        CB.scope = CB.scope || CB; 

        CB.httpHandle.open(CB.method,CB.url,CB.asynch,CB.username,CB.password);
        
        this.setDefaultHeaders(CB);
        
        /*
         * GET requests should not send content-type header
         */
        if(CB.method == 'GET') 
          {
            if(CB.headers["Content-Type"]) 
              {
                delete CB.headers["Content-Type"];
              }

            /**
             * Using Etag to check for changes in file, for cacheing.  See #build#main
             */
            CB.headers["If-None-Match"] = (this.cache[CB.url]) ? this.cache[CB.url].__ETAG__ : '"0"';
          }

        /**
         * Override default headers with any sent in call object
         */
        if(CB.headers)
          {
            for(var z in CB.headers)
              {
                this.setRequestHeader(CB, z, CB.headers[z]);
              }
          }

        CB.httpHandle.send(CB.body);  
        CB.onAfterSend();  

        return(CB);
      };       
  };
/**
 * Copyright 2008, 2009 Lime Labs LLC
 *
 * This file is part of AXIS.
 *
 * AXIS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License
 * version 3 as published by the Free Software Foundation.
 *
 * AXIS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with AXIS.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @fileoverview
 *
 * This is a collection of WebDAV methods.  This is a core class, part of the
 * AXIS framework.  All functions or calls which have to do with WebDAV operations
 * are included here.
 *
 * @href  http://greenbytes.de/tech/webdav/rfc4918.html 
 * 
 * Dead Properties
 * ---------------
 * creationdate
 * displayname
 * getcontentlanguage
 * getcontentlength
 * getcontenttype
 * getetag
 * getlastmodified
 * lockdiscovery
 * resourcetype
 * supportedlock
 *
 *  @requires   AXIS
 *  @requires   Loader
 *  @throws     DAV_MOVE_403
 *  @throws     DAV_MOVE_404
 *  @throws     DAV_MOVE_409
 *  @throws     DAV_MOVE_412
 *  @throws     DAV_COPY_403
 *  @throws     DAV_COPY_404
 *  @throws     DAV_COPY_409
 *  @throws     DAV_COPY_412
 *  @throws     DAV_PUT_NO_BODY
 *  @throws     DAV_DELETE_404
 *  @throws     DAV_BAD_PROPSET_ARGS
 *  @throws     DAV_BAD_PROPREMOVE_ARGS
 *  @throws     DAV_LOCK_412
 *  @throws     DAV_UNLOCK_400
 *  @throws     DAV_UNLOCK_403
 *  @throws     DAV_UNLOCK_409
 *  @throws     DAV_NO_RANGE
 *
 */

function WebDAV()
  {
    /**
     * @constructor
     */
    this.__construct = function()
      {
        this.currentProperties  = [];
        
        /**
         * Default headers for all DAV calls.
         *
         * @see #_prepare
         */
        this.defaultHeaders     = 
          {
            'Content-type': 'application/xml; charset="utf-8"',
            'X-Requested-With': "XMLHttpRequest"
          };

        /**
         * In seconds.
         * 
         *  @see #LOCK
         */
        this._defaultLOCKTimeout      = 86400;
        
        /**
         * Only write is supported.
         * 
         *  @see #LOCK
         */
        this._defaultLOCKType         = 'write';
        
        /**
         *  @see #LOCK
         */
        this._defaultLOCKScope        = 'exclusive';
        
        /**
         *  @see #LOCK
         */
        this._defaultLOCKDepth        = 'infinity';
        
        this.PRINCIPAL_ALL =                '1';
        this.PRINCIPAL_AUTHENTICATED =      '2';
        this.PRINCIPAL_UNAUTHENTICATED =    '3';
        this.PRINCIPAL_SELF =               '4';
        this.PRINCIPAL_PROPERTY =           '5';
        this.PRINCIPAL_USER =               '6';
        this.PRINCIPAL_GROUP =              '7';
        this.PRINCIPAL_HREF =               '8';
        
        // run this anonymous function immediately to create the operators to be used in search
        (function() {
            var search = this.SEARCH;
            search._prop_to_xml = function (prop) {
                if (prop.constructor != Array) {
                    prop = [prop];
                    prop.unshift("DAV:") // set dav as default if no namespace is present
                }
                return '<R:' + prop[1] + ' xmlns:R="' + prop[0] + '"/>';
            };
            // logical operators
            var logicalOps = ["and", "not", "or"];
            var logialGenerator = function(oper) {
                search[oper] = function() {
                    return "<" + oper +">" + Array.prototype.join.call(arguments, "")  + "</" + oper + ">";
                }
            }
            for (var i=0; i < logicalOps.length; i++) {
                logialGenerator(logicalOps[i])
            };
  
            // comparison operators
            var comparisonOps = ["gt", "lt", "eq", "gte", "lte", "gti", "lti", "like", "is_defined", "is_collection"];
            var comparisonGenerator = function(oper) {
                search[oper] = function(prop, literal) {
                    if (oper === "is_defined")
                        return "<is-defined><prop>" + search._prop_to_xml(prop) + "</prop></is-defined>"; 
                    if (oper === "is_collection")
                        return "<is-collection/>";
                    if (literal !== undefined) {
                        if (oper.indexOf("i") === (oper.length - 1)) {
                            oper = oper.slice(0, -1);
                            literal = '<typed-literal xsi:type="xs:integer" \
                                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" \
                                xmlns:xs="http://www.w3.org/2001/XMLSchema">' + literal + "</typed-literal>";
                        } else {
                            literal = "<literal>" + literal + "</literal>";
                        }
                    }
                    return "<" + oper + "><prop>" + search._prop_to_xml(prop) + "</prop>" + literal + "</" + oper + ">";
                }
            }
            for (var i=0; i < comparisonOps.length; i++) {
                comparisonGenerator(comparisonOps[i])
            };
        }).apply(this);
      };

    /**
     * This is where the storage and organization of locks is handled.
     * The general idea is that given a resource url, a user, a locktype,
     * and a locktoken, we can uniquely identify a lock.  
     */
    this.Locks  =
      {
        _locks:         [],
        
        /**
         * Store lock info on successful lock.
         *
         *  @param    {Object}    s   An option object containing the .url, the .owner, 
         *                            the .token and the  .locktype for this lock.  All 
         *                            required.  Note that there is additional info passed
         *                            by WebDAV#LOCK which may be useful.           
         *  @type     {Boolean}
         *  @see      WebDAV#LOCK
         */
        store:          function(s)
          {
            if(     s.url
                &&  s.owner
                &&  s.locktype
                &&  s.locktoken)
              {                        
                /**
                 * There isn't a problem if the lock exists already.  There isn't
                 * any harm in re-storing (we already have all calculated values and
                 * a consistent key, and this function is not run extensively, expensively),
                 * so just (re)build the store.
                 */
                this._locks[s.locktoken] = s;  
                
                return true;
              }
            else
              {
                AXIS.Errors.catchError('DAV_BAD_LOCK_ARGS');  
                
                return false;
              }
          },
          
        /**
         * Get lock info for a key.
         *
         *  @param    {String}    k   A locktoken
         *  @returns                  An object containing values for lock, or false if none.
         *  @type     {Mixed}
         *
         *  @see WebDAV#Locks#store
         */
        fetch:          function(k)
          {
            if(k)
              {
                return this._locks[k];
              }
            return false;
          },
          
        /**
         * Remove lock info for a key.
         *
         *  @param    {String}    k   A locktoken
         *  @returns                  Nothing.
         *
         *  @see WebDAV#Locks#store
         */
        remove:          function(k)
          {
            if(k && this._locks[k])
              {
                delete this._locks[k];
              }
          },
          
        /**
         * Get lock info for ALL locks stored containing a given property.
         *
         *  @param    {String}    p   A lock property, one of those sent to WebDAV#Locks#store
         *  @param    {String}    v   The value to search for on `p` property
         *  @returns                  All objects containing that property, or an empty array.
         *
         *  @type     {Array}
         *
         *  @see WebDAV#Locks#store
         */
        fetchByPropValue:     function(p,v)
          {
            var retArr = [];
            var curL;
            
            if(p)
              {
                for(var w in this._locks)
                  {
                    curL        = this._locks[w];
                    
                    if(curL[p] == v)
                      {
                        retArr.push(curL);  
                      }
                  }
              }
            return retArr;
          }
      };
      
    /**
     * MAIN WEBDAV METHODS         
     * These methods are passed an argument object, defined below.  The only absolute
     * requirement for ALL methods is .url.  See individual methods for specifics.
     *
     * url          ::  {String} a resource url, and MKCOL name.
     * targetUrl    ::  {String} target, when MOVEing or COPYing.
     * asynch       ::  {Boolean} if true, an asynchronous call; defaults to false.
     * callId       ::  {String} a unique id for this call.  defaults to a random id.
     *                    NOTE: The main purpose of this id is to prevent multiple identical
     *                    xhr calls -- until a call with id[x] has returned, subsequent
     *                    calls with id[x] will simply be ignored.
     * callback     ::  {Function} a handler to receive the xhr result object.
     * headers      ::  {Array} an assoc array of name/value pairs. [See XMLHTTP for defaults].
     * propName     ::  {String} with .propPatch, propFind, name of property being accessed.
     * propValue    ::  {String} with .propPatch, propFind, value of property being accessed.
     * body         ::  {String} content body to be sent with request (PUT).
     * acl          ::  {Array}  acl to be set in this request (.setACL).
     */
    
    /**
     * @href http://greenbytes.de/tech/webdav/rfc4918.html#rfc.section.9.4
     */  
    this.HEAD = function(dav)
      {
        var d       = dav || {};
        d.method    = 'HEAD';
        
        return this.send(d);
      };
    
    /**
     * @href http://greenbytes.de/tech/webdav/rfc4918.html#rfc.section.9.4
     */  
    this.GET = function(dav)
      {        
        var d       = dav || {};
        d.method    = 'GET';
          
        return this.send(d);
      };
    
    /**
     * @href http://greenbytes.de/tech/webdav/rfc4918.html#METHOD_POST
     */
    this.POST = function(dav)
      {
        var d       = dav || {};
        d.method    = 'POST';
        
        return this.send(d);
      };
     
    /**
     * NOTE: If no depth header is sent, header is set to depth:0
     
     *  @type   {Mixed}
     *  @href http://greenbytes.de/tech/webdav/rfc4918.html#METHOD_PROPFIND
     */  
    this.PROPFIND = function(dav)
      {
        var d     = dav || {};
        d.method  = 'PROPFIND';
        
        /*
         * Need to ensure that there is header information sent. Mainly,
         * for a PROPFIND there must be a depth property.
         */
        d.headers = d.headers || {};
        d.headers['Depth'] = d.headers['Depth'] || 0;
        
        return this.send(d);
      };
      
      /**
       * Does a PROPPATCH on a resource.  
       * Takes an array of properties to set and remove
       *
       *  @href http://greenbytes.de/tech/webdav/rfc4918.html#METHOD_PROPPATCH
       *  @see    #setProperty
       *  @see    #removeProperty
       *
       *  @example:
       *  setProperties = [{
       *     name: "displayname",
       *     value: "foo.html"   
       *  }, {
       *     ns: "http://www.limebits.com/ns/1.0"
       *     name: "Author",
       *     value: "Fitzgerald"
       *  }];
       *  AXIS.WebDAV.PROPPATCH({url: someURL, setProperties: setProperties});
       */
    
      this.PROPPATCH = function(dav)
        {
          var d              = dav || {};
          d.method           = 'PROPPATCH';
          d.setProperties    = dav.setProperties || [];
          d.removeProperties = dav.removeProperties || [];

          d.body             = '<?xml version="1.0" encoding="utf-8" ?>\
                                  <D:propertyupdate xmlns:D="DAV:">';

          var grouper = function(nm)
            { 
              var props   = d[nm + "Properties"];
              var block   = '<D:' + nm + '><D:prop>';
              for (var i = 0; i < props.length; i++)
                {
                  var prop = props[i];
                  if (prop.name)
                    {
                      block += "<P:" + prop.name + " xmlns:P=\"" + (prop.ns || "DAV:") + "\">" + prop.value + "</P:" + prop.name + ">"; 
                    }
                }
              block += '</D:prop></D:' + nm + '>';
              return block;
            }

          d.body += grouper("set");
          d.body += grouper("remove");

          d.body += '</D:propertyupdate>';
          return this.send(d); 
        };
    
      
    /**
     *  @href http://greenbytes.de/tech/webdav/rfc3253.html#METHOD_REPORT
     *  @href http://greenbytes.de/tech/webdav/rfc3744.html#rfc.section.9.1
     *  @param    {Object}  dav     An object containing options for this call.
     */
    this.REPORT = function(dav)
      {
        var d         = dav || {};
        d.method      = 'REPORT';    

        return this.send(d);  
      };
      
    /**
     *  @href http://greenbytes.de/tech/webdav/rfc4918.html#METHOD_MKCOL
     *  @param    {Object}  dav     An object containing options for this call.
     *  @throws   DAV_MKCOL_405
     */
    this.MKCOL = function(dav)
      {
        var d         = dav || {};
        d.method      = 'MKCOL';    

        AXIS.XMLHTTP.setStatusHandlerForResponse(d, 
          { 
            405: 'DAV_MKCOL_405' 
          });
          
        return this.send(d);  
      };
    
    /**
     * Deletes a resource based on sent url.
     *
     *  @param    {Object}  dav     An object containing options for this call.
     *  @href http://greenbytes.de/tech/webdav/rfc4918.html#METHOD_DELETE
     *  @throws   DAV_DELETE_404
     */
    this.DELETE = function(dav)
      {
        var d         = dav || {};
        d.method      = 'DELETE';    

        AXIS.XMLHTTP.setStatusHandlerForResponse(d, 
          { 
            404: 'DAV_DELETE_404' 
          });
        
        return this.send(d);  
      };
    
    /**
     * Puts a file (writes the content of the sent .body to a url)
     *
     *  @param    {Object}  dav     An object containing options for this call.
     *  @throws   DAV_PUT_NO_BODY
     *  @href http://greenbytes.de/tech/webdav/rfc4918.html#METHOD_PUT
     */
    this.PUT = function(dav)
      {
        var d         = dav || {};
        d.method      = 'PUT'; 
        d.headers     = dav.headers || {};
        
        if(!d.headers['Content-Type'])
          {
            d.headers['Content-Type'] =  'text/html'
          };

        /**
         * There must be a body for a PUT, but it is allowed to be 
         * empty (an empty string).  Check for an undefined body.
         */
        if(d.body === undefined)
          {
            AXIS.Errors.catchError(new Error('DAV_PUT_NO_BODY'));  
            return false;
          }
        
        return this.send(d);
      };
    
    /**
     * Will copy a file.  The call object expects you to send a .destination
     * string (the new resource url).  Optionally, you may send a .overwrite 
     * string (`T` || `F`), indicating what to do re: overwrites.  This defaults
     * to `F`. An optional smart copy feature choosing alternate names for you if
     * the destination url is taken.
     *
     *  @param    {Object}  dav     An object containing options for this call.
     *  @throws   DAV_COPY_403
     *  @throws   DAV_COPY_404
     *  @throws   DAV_COPY_409
     *  @throws   DAV_COPY_412
     *  @href http://greenbytes.de/tech/webdav/rfc4918.html#METHOD_COPY
     */
    this.COPY = function(dav)
      {
        var d         = dav || {};
        d.method      = 'COPY';
        d.origUrl     = d.url;
        
        if(d.destination === undefined)
          {
            AXIS.Errors.catchError(new Error('DAV_COPY_NO_DESTINATION'));
            return false;  
          }
       
        d.headers     = {
                          Destination: d.destination,
                          Overwrite:  d.overwrite ? 'T' : 'F'
                        };

        AXIS.XMLHTTP.setStatusHandlerForResponse(d, 
          { 
            403:  'DAV_COPY_403',
            404:  'DAV_COPY_404',
            409:  'DAV_COPY_409',
            412:  !d.tryAlternateNames ? 'DAV_COPY_412' : function() {
              /* Smart copy handler. Tries to find alternate unused name at destination */
              if (d.overwrite) return 'DAV_COPY_412';
              var dst_folder = AXIS.Util.uri.getFolder(d.destination);
              var dst_name = AXIS.Util.uri.basename(d.destination);
              AXIS.WebDAV.PROPFIND({
                url: dst_folder,
                headers: {
                  'Depth': '1'
                },
                callback: function(r) {
                  var responses = r.responseXMLObject().multistatus.response;
                  var children = [];
                  for (var i = 1; i < responses.length; i++)
                    {
                      children.push(AXIS.Util.uri.basename(responses[i].href));
                    }
                  var alternate_name = AXIS.Util.uri.findAlternateName(dst_name, children);
                  return AXIS.WebDAV.COPY({
                    url: d.origUrl,
                    destination: dst_folder + alternate_name,
                    alternateName: alternate_name,
                    callback: d.callback,
                    on201: d.on201
                  });
                }
              })
            }
          });
          
        /**
         * Need to do more stuff here, reading response for reason
         */
        d.on207 - d.on207 || function()
          {
            // do something here.  
          }
                        
        return this.send(d);
      };
      

    /**
     * Will move a file.  The call object expects you to send a .destination
     * string (the new resource url).  Optionally, you may send a .overwrite 
     * string (`T` || `F`), indicating what to do re: overwrites.  This defaults
     * to `F`.
     *
     *  @param    {Object}  dav     An object containing options for this call.
     *  @throws   DAV_MOVE_403
     *  @throws   DAV_MOVE_404
     *  @throws   DAV_MOVE_409
     *  @throws   DAV_MOVE_412
     *  @href http://greenbytes.de/tech/webdav/rfc4918.html#METHOD_MOVE
     */
    this.MOVE = function(dav)
      {
        var d         = dav || {};
        d.method      = 'MOVE';
     
        if(d.destination === undefined)
          {
            AXIS.Errors.catchError(new Error('DAV_MOVE_NO_DESTINATION'));
            return false;  
          }
       
        d.headers     = {
                          Destination:  d.destination,
                          Overwrite:    d.overwrite ? 'T' : 'F',
                          Depth:        d.depth || 0
                        };
          
        AXIS.XMLHTTP.setStatusHandlerForResponse(d, 
          { 
            403: 'DAV_MOVE_403',
            404: 'DAV_MOVE_404',
            409: 'DAV_MOVE_409',
            412: 'DAV_MOVE_412'
          });
        
        /**
         * Need to do more stuff here, reading response for reason
         */
        d.on207 - d.on207 || function()
          {
            // do something here.  
          }
                        
        return this.send(d);
      };
      
    this.VERSION_CONTROL = function(dav) 
      {
        var d = dav || {};
        d.method = "VERSION-CONTROL";
        this.send(d);
      };
    
    this.CHECKOUT = function(dav) 
      {
        var d = dav || {};
        d.method = "CHECKOUT";
        this.send(d);
      };
    
    this.CHECKIN = function(dav) 
      {
        var d = dav || {};
        d.method = "CHECKIN";
        this.send(d);
      };
    
    /**
     *   Search ported from search.js
     *
     *  @param    {Object}  dav     An object containing options for this call.
     */
    this.SEARCH = function(dav) 
      {
        // utility
        var _prop_to_xml = this.SEARCH._prop_to_xml;
        
        // options
        var d = dav || {};
        
        d.props = dav.props || ["allprop"],
        d.depth = dav.depth || 1,
        d.where = dav.where || null,
        d.limit = dav.limit || null,
        d.offset = dav.offset || null,
        d.orderby = dav.orderby || null
        
        d.url = dav.url;
        d.method = "SEARCH";
        
        
        /*
          xml builder functions
        */
        
        // TODO  this could probably be abstracted out a bit
        var builder = {
          _propXML: function() {
            var props = d.props;
            if (props[0] == 'allprop') {
              return '<allprop/>';
            } else {  
              var propXML = '';
              for (var i=0; i < props.length; i++) {
                propXML += _prop_to_xml(props[i]);
              }
              return '<prop>' + propXML + '</prop>';
            }
          },
          _whereXML: function() {
            var whereXML = '';

            if(d.where) {
                whereXML = '<where>' + d.where + '</where>';
            }

            return whereXML;
          },

          _orderbyXML: function() {
            var orderbyXML = '';

            if (d.orderby) {
                orderbyXML += '<orderby><order>';
                orderbyXML += '<prop>' + _prop_to_xml(d.orderby) + '</prop>';
                if (d.order) {
                    orderbyXML += '<' + d.order + '/>';
                }
                orderbyXML += '</order></orderby>';
            }

            return orderbyXML;
          },

          _limitXML: function() {
            var limitXML = '';

            if(d.limit) {
                limitXML = '<limit><nresults>' + d.limit + '</nresults></limit>';
            }

            return limitXML;
          },

          _offsetXML: function() {
            var offsetXML = '';

            if(d.offset) {
                offsetXML = '<offset>' + d.offset + '</offset>';
            }

            return offsetXML;
          }
        }
        
        
        d.body = dav.body || '<?xml version="1.0"?> \
          <searchrequest xmlns="DAV:"> \
            <basicsearch> \
              <select>' + builder._propXML() + '</select> \
              <from> \
                <scope> \
                  <href>' + d.url + '</href> \
                  <depth>' + d.depth + '</depth> \
                </scope> \
              </from> \
              ' + builder._whereXML() + ' \
              ' + builder._orderbyXML() + ' \
              ' + builder._limitXML() + ' \
              ' + builder._offsetXML() + ' \
            </basicsearch> \
          </searchrequest> \
        ';
        
        return this.send(d);
      };
      
    /**
     * Locks a resource.  
     *
     *  @param    {Object}  dav     An object containing options for this call.
     *  @href http://greenbytes.de/tech/webdav/rfc4918.html#METHOD_LOCK
     *  @throws   DAV_LOCK_412
     */
    this.LOCK = function(dav)
      {
        var d = dav || {};
        
        d.url           = d.url       || false;
        d.method        = 'LOCK';
        d.timeout       = d.timeout   || 'Second-' + this._defaultLOCKTimeout;
        d.depth         = d.depth     || this.defaultLOCKDepth;
        d.lockscope     = d.lockscope || this._defaultLOCKScope;
        d.locktype      = d.locktype  || this._defaultLOCKType;
        
        /**
         * If-Match will stop a lock from being taken on an unmapped
         * url -- which would normally create an locked, empty, resource.
         */
        d.headers       = {
                            Timeout:          d.timeout,
                            Depth:            d.depth,
                            'If-Match':       '*'
                          };
        
        /**
         * Create the <owner> block.  If the user is logged in,
         * this becomes '/home/usernamehere'; otherwise, it is unauthenticated.
         */
         
        var user = AXIS.User.username();
        var ublk = (user) ? '<D:owner>/home/' + user + '</D:owner>' : '<D:unauthenticated>';
        
        d.owner   = (user) ? '/home/' + user : 'unauthenticated';
        
        d.body          = 
          '<?xml version="1.0" encoding="utf-8" ?>\
            <D:lockinfo xmlns:D="DAV:">\
              <D:lockscope>\
                <D:' + d.lockscope + '/>\
              </D:lockscope>\
              <D:locktype>\
                <D:' + d.locktype + '/>\
              </D:locktype>\
              ' + ublk + '\
            </D:lockinfo>'; 
            
        AXIS.XMLHTTP.setStatusHandlerForResponse(d, 
          {
            200:  function(r)
              {
                var lob = {};
                
                /**
                 * We have to check if the lock is successful, and store lock
                 * tokens.  200 is a success.  Now parse the data.
                 */
                var t = r.responseXMLObject().prop.lockdiscovery.activelock;
                
                lob.depth       = t.depth;
                lob.owner       = t.owner;
                lob.timeout     = t.timeout;
                lob.token       = t.locktoken.href;
                
                for(var m in t.locktype)
                  {
                    lob.locktype = m;  
                  }

                for(var m in t.lockscope)
                  {
                    lob.lockscope = m;  
                  }
                  
                AXIS.WebDAV.Locks.store(lob);
                
                //alert('locktype:' + locktype + ' lockscope:' + lockscope + ' depth:' + depth + ' owner:' + owner + ' timeout:' + timeout + ' token:' + token);
              },
            
            412:  'DAV_LOCK_412~~' + d.url
            
            /*
             * 423's (locked) are handled automagically by AXIS.XMLHTTP
             */
          });

        return this.send(d);
      };
      
    /**
     * Attempts to unlock a resource
     *
     *  @param    {Object}  dav     An object containing options for this call.
     *  @href http://greenbytes.de/tech/webdav/rfc4918.html#METHOD_UNLOCK
     *  @throws   DAV_UNLOCK_400
     *  @throws   DAV_UNLOCK_403
     *  @throws   DAV_UNLOCK_409
     */
    this.UNLOCK = function(dav)
      {
        var d = dav || {};
        
        d.method      = 'UNLOCK';
        d.headers       = {
                            //'Lock-Token': '<opaquelocktoken:10f79931-3af6-42a2-af63-8a7ae047a9a2>'
                          };
                          
        AXIS.XMLHTTP.setStatusHandlerForResponse(d, 
          {
            400:  'DAV_UNLOCK_400~~' + d.url,
            403:  'DAV_UNLOCK_403~~' + d.url,
            409:  'DAV_UNLOCK_409~~' + d.url
          });
          
        return this.send(d);
      };
    
    /**
     * Helper methods.
     * Functionality that prepares particular flavours of DAV
     * calls.  The requirement of all of these methods is that
     * they ultimately return the response from a standard
     * DAV method, as defined above.
     */
     
    /**
     * Retrieves a range of bytes from a resource.
     *  
     *  @see http://www.faqs.org/rfcs/rfc2616.html
     */
    this.getByteRange = function(dav)
      {
        var d     = dav || {};
        d.method  = 'GET';
        
        /**
         * Must have a byte range...
         */
        if(!d.byteRange)
          {
            AXIS.Errors.catchError(new Error('DAV_NO_RANGE'));
          }  
          
        d.headers             = d.headers || {};
        d.headers['Range']    = 'bytes=' + d.byteRange;
        
        /**
         * A 206 response (partial content) is what you expect;
         * A 416 response (requested range not satisfiable) is of course an error.
         */
        d.failureCodes  = [416];
        
        return this.send(d);
      };  
     
    /**
     * Gets a property on a resource.  Expects a property name.  You may also
     * send an array of properties to fetch.
     *
     * NOTE: This method, whether the prop is found or not, returns the status
     * code of 207.  
     *  
     *  @param    {Object}  dav     An object containing options for this call.
     *  @see  #PROPFIND
     */
    this.xgetProperty = function(dav)
      {
        var d     = dav || {};
        
        d.property        = dav.property || '';
        d.body            = '<?xml version="1.0" encoding="utf-8" ?> \
                              <D:propfind xmlns:D="DAV:">\
                                <D:prop xmlns:D="DAV:">';
                
        var isArr         = (d.property.constructor === Array);                
        if(isArr)
          {
            for(var x=0; x < d.property.length; x++)
              {
                d.body  += '<D:' + d.property[x] + '/>';  
              }
          }
        else
          {
            d.body  += '<D:' + d.property + '/>';
          }
        
        d.body    += '</D:prop></D:propfind>';
        
        /**
         * Usually we leave result handling to the implementor, but here we create
         * a little quick access to result node, ._result, so the implementor
         * doesn't have to worry about setting a 207, etc. (NOTE: that if an on207
         * is set, we still will call it...)
         */
        d._result         = [];
        
        var o2      = d.on207 || AXIS.F;
        
        AXIS.XMLHTTP.setStatusHandlerForResponse(d, 
          {
            207:  function(r)  
              {
                try
                  {
                    var rr = r.responseXMLObject().multistatus.response.propstat.prop;
                    
                    if(isArr)
                      {
                        var dd = d.property;
                        for(var z=0; z < dd.length; z++)
                          {
                            r._result[dd[z]] = rr[dd[z]];  
                          }  
                      }
                    else
                      {
                        r._result[r.property] = rr[r.property];  
                      }
                  }
                catch(e){}
                
                o2(r);
              }
          });

        return this.PROPFIND(d);
      };

    /**
     * Sets a property on a resource.  Expects a property name.  You may also
     * send an array of properties to set.
     *
     * NOTE: This is essentially an alias to the PROPPATCH method, and expects the
     * arguments defined for that method for *setting* values.  The one thing it
     * does add is the ability to send a single (not array) setValues value, which
     * is promptly converted into an array and sent along.
     *  
     *  @param    {Object}  dav     An object containing options for this call.
     *  @see      #PROPPATCH
     *  @throws   DAV_BAD_PROPSET_ARGS
     */
    this.setProperty = function(dav)
      {
        var d     = dav || {};
        
        if(d.setValues && d.setProperty)
          {
            d.setValues = (d.setValues.constructor === Array) ? d.setValues : [d.setValues];  
          }
        else
          {
            AXIS.Errors.catchError(new Error('DAV_BAD_PROPSET_ARGS'));
          }
          
        return this.PROPPATCH(d);
      };
      
    /**
     * Removes a property on a resource.  Expects a property name.  You may also
     * send an array of properties to remove.
     *
     * NOTE: This is essentially an alias to the PROPPATCH method, and expects the
     * arguments defined for that method for *removing* values. The one thing it
     * does add is the ability to send a single (not array) removeValues value, which
     * is promptly converted into an array and sent along.
     *
     *  @param    {Object}  dav     An object containing options for this call.
     *  @see      #PROPPATCH
     *  @throws   DAV_BAD_PROPREMOVE_ARGS
     */
    this.removeProperty = function(dav)
      {
        var d     = dav || {};
        
        if(d.removeValues && d.removeProperty)
          {
            d.removeValues = (d.removeValues.constructor === Array) ? d.removeValues : [d.removeValues];  
          }
        else
          {
            AXIS.Errors.catchError(new Error('DAV_BAD_PROPREMOVE_ARGS'));
          }

        return this.PROPPATCH(d);
      };
      

    /**
     * @href http://greenbytes.de/tech/webdav/rfc3253.html#REPORT_version-tree
     *
     *  @param    {Object}  dav     An object containing options for this call.
     *  @see #REPORT
     *  @return  The response from #REPORT
     *  @type  Object
     */
    this.getVersionTreeReport = function(dav)
      {
        var d     = dav || {};
        d.body    = '<?xml version="1.0" encoding="utf-8" ?>\
                      <D:version-tree xmlns:D="DAV:">\
                        <D:prop>\
                          <D:version-name/>\
                          <D:creator-displayname/>\
                          <D:successor-set/>\
                          <D:getlastmodified/>\
                        </D:prop>\
                      </D:version-tree>';
                      
        d.headers =  {
                       'Depth': 0
                     };

        return this.REPORT(d);
      };
    
    /**
     * @href http://greenbytes.de/tech/webdav/rfc3253.html#REPORT_expand-property
     *
     *  @param    {Object}  dav     An object containing options for this call.
     *  @see #REPORT
     *  @return  The response from #REPORT
     *  @type  Object
     */
    this.getExpandPropertyReport = function(dav)
      {
        var d     = dav || {};
        d.body    = '<?xml version="1.0" encoding="utf-8" ?>\
                      <D:expand-property xmlns:D="DAV:">\
                        <D:property name="version-history">\
                          <D:property name="version-set">\
                            <D:property name="creator-displayname"/>\
                            <D:property name="activity-set"/>\
                          </D:property>\
                        </D:property>\
                      </D:expand-property>';

        return this.REPORT(d);
      };

    /**
     * @href http://greenbytes.de/tech/webdav/rfc3744.html#REPORT_acl-principal-prop-set
     *
     *  @param    {Object}  dav     An object containing options for this call.
     *  @see #REPORT
     *  @return  The response from #REPORT
     *  @type  Object
     */
    this.getAclPrincipalPropSetReport = function(dav)
      {
        var d     = dav || {};
        d.body    = '<?xml version="1.0" encoding="utf-8" ?>\
                      <D:acl-principal-prop-set xmlns:D="DAV:">\
                        <D:prop>\
                          <D:displayname/>\
                        </D:prop>\
                      </D:acl-principal-prop-set>';
                      
        d.headers =  {
                       'Depth': 0
                     };

        return this.REPORT(d);
      };

    /**
     * @href http://greenbytes.de/tech/webdav/rfc3744.html#REPORT_principal-match
     *
     *  @param    {Object}  dav     An object containing options for this call.
     *  @see #REPORT
     *  @return  The response from #REPORT
     *  @type  Object
     */
    this.getPrincipalMatchReport = function(dav)
      {
        var d     = dav || {};
        d.body    = '<?xml version="1.0" encoding="utf-8" ?>\
                      <D:principal-match xmlns:D="DAV:">\
                        <D:principal-property>\
                          <D:owner/>\
                        </D:principal-property>\
                      </D:principal-match>';
                      
        d.headers =  {
                       'Depth': 0
                     };

        return this.REPORT(d);
      };

    /**
     * @href http://greenbytes.de/tech/webdav/rfc3744.html#REPORT_principal-property-search
     *
     *  @param    {Object}  dav     An object containing options for this call.
     *  @see #REPORT
     *  @return  The response from #REPORT
     *  @type  Object
     */
     /*
    this.getPrincipalPropertySearchReport = function(dav)
      {
        var d     = dav || {};
        d.body    = '';
                      
        d.headers =  {
                       'Depth': 0
                     };

        return this.REPORT(d);
      };
*/

    /**
     * @href http://greenbytes.de/tech/webdav/rfc3744.html#REPORT_principal-search-property-set
     *
     *  @param    {Object}  dav     An object containing options for this call.
     *  @see #REPORT
     *  @return  The response from #REPORT
     *  @type  Object
     */
     
    this.getPrincipalSearchPropertySetReport = function(dav)
      {
        var d     = dav || {};
        d.body    = '<?xml version="1.0" encoding="utf-8" ?>\
                      <D:principal-search-property-set xmlns:D="DAV:"/>';
                      
        d.headers =  {
                       'Depth': 0
                     };

        return this.REPORT(d);
      };

    
    /**
     * Gets all properties on a resource.  A resouce can either be a single "file", 
     * or a collection resource (folder).  For a collection, this would fetch you
     * a collection ("directory") listing, with each "sub" resource described in the 
     * same way it would have been had its properties been requested via this method. 
     *
     * Because this dual nature of resources may be strange or ambiguous to some,
     * and due to the very real possibility that the resource type of the url passed 
     * to this call is not known in advance, we add a useful object with semi-parsed
     * data to the return object, so that the developer can easily determine the
     * type of resource returned, as well as all bound resources in the case
     * of collections.
     *
     *  @param    {Object}  dav     An object containing options for this call.
     *  @see #PROPFIND
     *  @return  The response from #PROPFIND
     *  @type  Object
     */
    this.getResourceProps = function(dav)
      {
        var d         = dav || {};

        d.body        = '<?xml version="1.0" encoding="utf-8" ?>\
           	              <propfind xmlns="DAV:">\
           	                <allprop/>\
           	              </propfind>';
        d.headers     =  {
                            'Depth': 1
                         };
               
        /**
         * Ensure that our new properties are set in the return object by 
         * creating this proxy callback which passes on the info.
         */
        var c2    = d.callback || AXIS.F;
        d.callback  = function(r)
          {
            /**
             * Prepare a response object so that the developer
             * doesn't have to bother with parsing -- we can expect
             * that this function was called to get a collection record,
             * and so this parsing will be useful.
             */
            r._result = 
              {
                type:       'failed',
                href:       '',
                resources:  []  
              };
            
            /**
             * It's likely that we'll get non-responses back.  That's fine,
             * just don't want the code to cause errors, so try...catch.
             */
            try
              {
                var st      = r.responseXMLObject().multistatus.response;
                var mult    = (st.constructor === Array);
                var res     = mult ? st : [st];
                var rt      = res[0].propstat.prop.resourcetype;
                
    
                /**
                 * Determine if we've just fetched a collection or not by reading
                 * the `resourcetype` of the first `resource` returned, ex.:
                 *
                 * <D:multistatus xmlns:D="DAV:" xmlns:ns0="DAV:">
                 *   <D:response>
                 *     <D:href>/</D:href>
                 *     <D:propstat>
                 *       <D:prop>
                 *         <D:resourcetype>
                 *           <D:collection />
                 *        </D:resourcetype>
                 *        ...
                 *
                 * If it's NOT a collection, set .type to 'resource' and pass the 
                 * single element array, resRoot; if a collection, set .type to 'collection'.  
                 * We also pass along the .href of the first result, which will describe
                 * the url the PROPFIND executed on.
                 */
                 
                r._result.type  = (rt && rt.hasOwnProperty('collection')) ? 'collection' : 'resource'; 
                r._result.href  = res[0].href;
                              
                /**
                 * Build the result list, pushing the resource object's propstat.prop(s)
                 */
                for(var x=0; x < res.length; x++)
                  {
                    r._result.resources.push(res[x].propstat.prop);
                  }
              }
            catch(e){};
            
            c2(r);
          }
        
        return this.PROPFIND(d);
      };
      
    /**
     * Alias of {@link #getReourceProps}
     *
     *  @param    {Object}  dav     An object containing options for this call.
     */
    this.readFolder = function(dav)
      {
        return this.getResourceProps(dav);  
      };

    AXIS.Errors.register('DAV_ACE_CONFLICT', "An ace conflict occured", 2);
    AXIS.Errors.register('DAV_ACE_INVALID_PRIVILEGES', "Privileges argument is invalid.", 3);
    AXIS.Errors.register('DAV_ACL_CANNOT_REMOVE_INHERITED_ACE', "Inherted aces can only be removed on the ancestor.", 2);
    AXIS.Errors.register('DAV_ACL_CANNOT_REMOVE_PROTECTED_ACE', "Protected aces cannot be removed.", 2);
    AXIS.Errors.register('DAV_ACL_CORRUPTED', "The acl has been corrupted. Have you been editing it directly? ", 3);
    AXIS.Errors.register('DAV_ACL_CHANGED', "The resource's acl has changed since you last fetched it. Try again and use a lock if possible.", 3);

    var Acl = function(aceElements)
      {
        if (AXIS.isIE)
          {
            var acl = [];
            AXIS.Util.lang.augmentObject(acl, this);
            acl.parseAceElements(aceElements);

            acl.indexOf = function(e, i) 
              {
                i = i || 0;

                if (i < 0) 
                  {
                    i = this.length + (i % this.length);
                  }

                for (; i < this.length; i++) 
                  {
                    if (this[i] === e) 
                      {
                        return i;
                      }
                  }

                return -1;
              }
            return acl;
          }

        this.parseAceElements(aceElements);
      }

    Acl.prototype = new Array;

    AXIS.Util.lang.augmentObject(Acl,
      {
        printPrincipal: function(prin,p)
          {
            var pretty = p || false;
            var p_str = pretty ? '' : '<D:principal>';
            var t;
            
            switch(prin[0])
              {
              case AXIS.WebDAV.PRINCIPAL_ALL:
                p_str += pretty ? 'All' : '<D:all/>';
                break;
              case AXIS.WebDAV.PRINCIPAL_AUTHENTICATED:
                p_str += pretty ? 'Authenticated' : '<D:authenticated/>';
                break;
              case AXIS.WebDAV.PRINCIPAL_UNAUTHENTICATED:
                p_str += pretty ? 'UnAuthenticated' : '<D:unauthenticated/>';
                break;
              case AXIS.WebDAV.PRINCIPAL_SELF:
                p_str += pretty ? 'Self' : '<D:self/>';
                break;
              case AXIS.WebDAV.PRINCIPAL_PROPERTY:
                if(prin[1][1] == "DAV:")
                  {
                    t = "<D:" + prin[1][0] + "/>";
                  }
                else
                  {
                    t = "<" + prin[1][0] + " xmlns='" + prin[1][1] + "'/>";
                  }
                  
                if(pretty)
                  {
                    p_str += t;
                  }
                else
                  {
                    p_str += '<D:property>' + t + '</D:property>';
                  }
                break;
              case AXIS.WebDAV.PRINCIPAL_USER:
                t = AXIS._siteData.hosts.limebits + "users/" + prin[1];
                if(pretty)
                  {
                    p_str += t;
                  }
                else
                  {
                    p_str += '<D:href>' + t + '</D:href>';
                  }
                break;
              case AXIS.WebDAV.PRINCIPAL_GROUP:
                t = AXIS._siteData.hosts.limebits + "groups/" + prin[1];
                if(pretty)
                  {
                    p_str += t;  
                  }
                else
                  {
                    p_str += '<D:href>' + t + '<D:href>';
                  }
                break;
              case AXIS.WebDAV.PRINCIPAL_HREF:
                p_str += pretty ? prin[1] : '<D:href>' + prin[1] + '</D:href>';
                break;
              }
            p_str += "</D:principal>";
            return p_str;
          },

        printPrivileges: function(privs)
          {
            var p_str = "";
            for (var i = 0; i < privs.length; i++)
              {
                var priv = privs[i];
                p_str += "<D:privilege>";
                if (priv[0] == "DAV:")
                  p_str += "<D:" + priv[1] + "/>";
                else
                  p_str += "<" + priv[1] + " xmlns='" + priv[0] + "'/>";
                p_str += "</D:privilege>";
              }
            return p_str;
          },

        /**
         * Get an array of privilge strings from the corresponding XML elements
         *
         *  @param    {Array}       an array of D:privilege XML elements
         */
        getPrivilegesFromElements: function(elems)
          {
            var privs = [];
            for (var i = 0; i < elems.length; i++)
              {
                var j;
                for (j = 0; j < elems[i].childNodes.length && elems[i].childNodes[j].nodeType != AXIS.ELEMENT_NODE; j++);
              
                var priv = elems[i].childNodes[j].nodeName;
                priv = priv.split(":").pop();
                var priv_ns = elems[i].childNodes[j].namespaceURI;

                privs.push([priv_ns, priv]);
              }

            return privs;
          }
      });

    AXIS.Util.lang.augmentObject(Acl.prototype, 
      {
        parseAceElements: function(aces_el) {
          this.protectedAces = [];
          this.editableAces = [];
          this.inheritedAces = [];

          for (var i = 0; i < aces_el.length; i++)
            {
              var ace_el = aces_el[i];
              var principal = this.getPrincipalFromElem(AXIS.Util.xml.getChildrenByNameNS(ace_el, "principal")[0]);

              var grant;
              var inherited = false;
              var isProtected = false;
              if (AXIS.Util.xml.getChildrenByNameNS(ace_el, "protected").length)
                isProtected = true;

              var inherited = AXIS.Util.xml.getChildrenByNameNS(ace_el, "inherited")[0];
              inherited = inherited ? AXIS.Util.xml.getText(AXIS.Util.xml.getChildrenByNameNS(inherited, "href")[0]) : false;

              grant = AXIS.Util.xml.getChildrenByNameNS(ace_el, "grant").length ? true : false;

              var privs = Acl.getPrivilegesFromElements(AXIS.Util.xml.getChildrenByNameNS(ace_el, "privilege"));

              var ace = this.makeAce(grant, privs, principal, isProtected, inherited);

              if (isProtected)
                this.protectedAces.push(ace);
              else if (inherited)
                this.inheritedAces.push(ace);
              else
                this.editableAces.push(ace);
            }

          for (var i = 0; i < this.protectedAces.length; i++)
            this.push(this.protectedAces[i]);
          for (var i = 0; i < this.editableAces.length; i++)
            this.push(this.editableAces[i]);
          for (var i = 0; i < this.inheritedAces.length; i++)
            this.push(this.inheritedAces[i]);

          this._origACL = this.printXML();
        },

        /**
         * Gets an internal representation of the principal element in XML.
         *
         *  @param    {Object}  el     A principal element in an xml doc.
         *  @return   An internal represention of the element using an array.
         *  @href http://greenbytes.de/tech/webdav/rfc3744.html#ace.principal
         */
        getPrincipalFromElem: function(el)
          {
            var principal = null;
            var href = AXIS.Util.xml.getChildrenByNameNS(el, "href", "DAV:");
            if (href.length > 0)
              {
                href = AXIS.Util.xml.getText(href[0]);
                if (href.indexOf(AXIS._siteData.hosts.limebits + "users/") == 0)
                  principal = [AXIS.WebDAV.PRINCIPAL_USER, AXIS.Util.uri.basename(href)];
                else if (href.indexOf(AXIS._siteData.hosts.limebits + "groups/") == 0)
                  principal = [AXIS.WebDAV.PRINCIPAL_GROUP, AXIS.Util.uri.basename(href)];
                else
                  principal = [AXIS.WebDAV.PRINCIPAL_HREF, href];
                return principal;
              }

            if (AXIS.Util.xml.getChildrenByNameNS(el, "all").length == 1)
              principal = [AXIS.WebDAV.PRINCIPAL_ALL];
            else if (AXIS.Util.xml.getChildrenByNameNS(el,"authenticated").length == 1)
              principal = [AXIS.WebDAV.PRINCIPAL_AUTHENTICATED];
            else if (AXIS.Util.xml.getChildrenByNameNS(el, "unauthenticated").length == 1)
              principal = [AXIS.WebDAV.PRINCIPAL_UNAUTHENTICATED];
            else if (AXIS.Util.xml.getChildrenByNameNS(el, "self").length == 1)
              principal = [AXIS.WebDAV.PRINCIPAL_SELF];
            else
              { 
                var prop_el = AXIS.Util.xml.getChildrenByNameNS(el, "property")[0];
                if (prop_el)
                  {
                    var j;
                    for (j = 0; j < prop_el.childNodes.length && prop_el.childNodes[j].nodeType != AXIS.ELEMENT_NODE; j++);
                    prop_el = prop_el.childNodes[j];

                    principal = [AXIS.WebDAV.PRINCIPAL_PROPERTY, [prop_el.nodeName.split(":").pop(), prop_el.namespaceURI]];
                  }
              }
            return principal;
          },

        /**
         * Creates an internal representation of an ace
         *
         *  @param    {Boolean}        grant         If true, creates a grant ace. If false, creates a deny ace.
         *  @param    {Array/String}   privs         An array of privileges to be managed by this ace. Each privilege is itself an array of the form ["namespace", "privilege"]. For convenience, if the namespace is "DAV:", the privilege can be represented by the string "privilege". If providing exactly one privilege, and it is in the "DAV:" namespace, this parameter can be the string "privilege". Egs: [["http://limebits.com/ns/1.0/", "read-private-properties"]]; [["DAV:", "read"], "write-content"]; "write-content"; 
         *  @param    {Acl.prin_t}     principal     Principals affected by this ace
         *  @param    {Boolean}        isProtected (optional)  If true, create an ace marked as protected. Defaults to false
         *  @param    {String}         inherited (optional)    A string containing the href of the resource this ace is inherited. Defaults to creating an uninherited ace.
         *  @return   {Object}         An object representing the ace
         *  @href http://greenbytes.de/tech/webdav/rfc3744.html#PROPERTY_acl
         */
        makeAce: function(grant, privs, principal, isProtected, inherited)
          {
            isProtected = isProtected ? true : false;
            inherited = inherited ? inherited : false;

            if (privs instanceof Array)
              {
                for (var i = 0; i < privs.length; i++)
                  {
                    var priv = privs[i];
                    if (!(priv instanceof Array))
                      privs[i] = ["DAV:", priv];
                  }
              }
            else if (typeof privs === 'string')
              privs = [["DAV:", privs]];
            else
              throw new Error('DAV_ACE_INVALID_PRIVILEGES');

            privs.hasPriv = function(priv, priv_ns)
              {
                if (priv_ns == null)
                  priv_ns = "DAV:";

                for (var i = 0; i < privs.length; i++)
                  {
                    var p = privs[i];
                    if (priv_ns == p[0] && priv == p[1] )
                      return true;
                  }
                return false;
              }

            if (!(principal instanceof Array))
              principal = [principal];

            return {
              principal: principal,
              isProtected: isProtected,
              inherited: inherited,
              grant: grant,
              privs: privs
            };
          },

        findAces: function(grant, privs, principal)
          {
            var ace = this.makeAce(grant, privs, principal);
            var matchingAces = [];
            for (var i = 0; i < this.length; i++)
              {
                if (this[i].grant == ace.grant)
                  {
                    if (Acl.printPrincipal(this[i].principal) == Acl.printPrincipal(ace.principal))
                      {
                        for (var j = 0; j < ace.privs.length; j++)
                          if (this[i].privs.hasPriv(ace.privs[j][1], ace.privs[j][0]))
                            matchingAces.push(this[i]);
                      }
                  }
              }
            return matchingAces;
          },

        /**
         * Checks if a provided ace conflicts with any of the existing protected or editable aces
         *
         *  @param    {Object}         ace         Object containing the details of the ace that needs to be added
         *  @throws   DAV_ACE_CONFLICT
         *  @href http://greenbytes.de/tech/webdav/rfc3744.html#rfc.section.8.1.3
         */
        checkAceConflict: function(ace)
          {
            for (var i = 0; i < this.length; i++)
              {
                if (this[i].inherited && !this[i].isProtected)
                  continue;

                if (this[i].grant ? !ace.grant : ace.grant)
                  {
                    if (Acl.printPrincipal(this[i].principal) == Acl.printPrincipal(ace.principal))
                      {
                        for (var j = 0; j < ace.privs.length; j++)
                          if (this[i].privs.hasPriv(ace.privs[j][1], ace.privs[j][0]))
                            throw new Error('DAV_ACE_CONFLICT~~' + "Ace " + this.printACE(ace)+ " conflicts with" + (this[i].isProtected ? " protected" : "") + " ace " + this.printACE(this[i]));
                      }
                  }
              }
          },

        /**
         * Checks if a provided ace conflicts with any of the existing protected or editable aces
         *
         *  @param    {Boolean}        grant         If true, adds a grant ace. If false, adds a deny ace.
         *  @param    {Array/String}   privs         see @param privs of makeAce 
         *  @param    {Acl.prin_t}     principal     see @param principal of makeAces
         *  @param    {Integer}        index (optional)   The position in the acl at which to add the ace. The index must be within the editable aces
         *  @return   {Integer}                      The position at which the ace was actually added
         *  @throws   DAV_ACE_CONFLICT
         *  @href http://greenbytes.de/tech/webdav/rfc3744.html#rfc.section.8.1.3
         */
        addAce: function(grant, privs, principal, index)
          {
            var ace = this.makeAce(grant, privs, principal);
            this.checkAceConflict(ace);

            if (index == null || index < this.protectedAces.length) /* Insert at the head of the editable aces */
              index = this.protectedAces.length;
            else if (index > this.protectedAces.length + this.editableAces.length) /* Insert at the tail of the editable aces */
              index = this.protectedAces.length + this.editableAces.length;

            this.editableAces.splice(index - this.protectedAces.length, 0, ace);
            this.splice(index, 0, ace);

            return index;
          },

        /**
         * Removes the editable ace at a given index
         *
         *  @param    {Integer}   index       The index of the ace which is to be removed
         *  @throws   DAV_ACL_CANNOT_REMOVE_PROTECTED_ACE
         *  @throws   DAV_ACL_CANNOT_REMOVE_INHERITED_ACE
         *  @throws   DAV_ACL_CORRUPTED
         */
        removeAce: function(index)
          {
            if (this[index].isProtected)
              throw new Error('DAV_ACL_CANNOT_REMOVE_PROTECTED_ACE');

            if (this[index].inherited)
              throw new Error('DAV_ACL_CANNOT_REMOVE_INHERITED_ACE');

            var edIndex = index - this.protectedAces.length;

            if (this[index] != this.editableAces[edIndex])
              throw new Error('DAV_ACL_CORRUPTED');

            this.editableAces.splice(edIndex, 1)
            return this.splice(index, 1)[0];
          },

        /**
         * Removes all the editable aces in this acl
         *
         *  @throws   DAV_ACL_CORRUPTED
         */
        clearEditableAces: function()
          {
            this.splice(this.protectedAces.length, this.editableAces.length);
            this.editableAces = [];
            if (this.length != this.protectedAces.length + this.inheritedAces.length)
              throw new Error('DAV_ACL_CORRUPTED');
          },

        printACE: function(ace)
          {
            var aceXML = "";
            aceXML += "<D:ace>";
            aceXML += Acl.printPrincipal(ace.principal);
            aceXML += "<D:" + (ace.grant ? "grant" : "deny") + ">";
            aceXML += Acl.printPrivileges(ace.privs);
            aceXML += "</D:" + (ace.grant ? "grant" : "deny") + ">";
            if (ace.isProtected)
              aceXML += "<D:protected/>";
            else if (ace.inherited)
              aceXML += "<D:inherited><D:href>" + ace.inherited + "</D:href></D:inherited>";
            aceXML += "</D:ace>";
            return aceXML;
          },

        /**
         * Print the aces of the acl
         *
         *  @param   {Boolean}    editableOnly (optional)   If true, outputs only the editable aces.
         *  @return  {String}     The acl property as a string
         */
        printXML: function(editableOnly)
          {
            var aclArray = editableOnly ? this.editableAces : this;
            var aclXML = '<D:acl xmlns:D="DAV:">';

            for (var i = 0; i < aclArray.length; i++)
              aclXML += this.printACE(aclArray[i]);
            aclXML += "</D:acl>";
            return aclXML;
          }
      });
    this.Acl = Acl;

    /**
     * Gets the Current User Privilege Set for a resource.
     *
     *  @param    {Object}  dav     An object containing options for this call.
     *  @href http://greenbytes.de/tech/webdav/rfc3744.html#PROPERTY_current-user-privilege-set
     */
    this.getCUPS = function(dav)
      {
        var d     = dav || {}

        d.body    = '<?xml version="1.0" encoding="utf-8" ?>\
                    <D:propfind xmlns:D="DAV:">\
                      <D:prop>\
                        <D:current-user-privilege-set/>\
                      </D:prop>\
                    </D:propfind>';

        /**
         * On a successful response, we create a simple accessor for CUPS, extending
         * the response with an .allows method, letting the developer now check
         * directly if a user has a given priviledge by checking value of 
         * response.allows('write'), for example.
         */  
        d.responseProcessor = function(r)
          {
            var ps = AXIS.Util.xml.getChildrenByNameNS(r.responseXML.documentElement, "propstat", "DAV:")[0];
            var st = AXIS.Util.xml.getChildrenByNameNS(ps, "status", "DAV:")[0].firstChild.nodeValue;

            if (st.indexOf("200 OK") < 0)
              return null;

            var privs = Acl.getPrivilegesFromElements(AXIS.Util.xml.getChildrenByNameNS(ps, "privilege"));

            privs.hasPriv = function(priv, priv_ns)
              {
                if (priv_ns == null)
                  priv_ns = "DAV:";

                for (var i = 0; i < privs.length; i++)
                  {
                    var p = privs[i];
                    if (priv_ns == p[0] && priv == p[1] )
                      return true;
                  }
                return false;
              }

            r.privs = privs;
            r.allows = privs.hasPriv; // backward compatibility
            return privs;
          };

        return this.PROPFIND(d);
      };

    /**
     * Gets the ACL for a resource.
     *
     *  @param    {Object}  dav     An object containing options for this call.
     *  @href http://greenbytes.de/tech/webdav/rfc3744.html#rfc.section.5.5.5
     */
    this.getACL = function(dav)
      {
        var d     = dav || {};
        
        d.body    = '<?xml version="1.0" encoding="utf-8" ?>\
                      <D:propfind xmlns:D="DAV:">\
                        <D:prop>\
                          <D:acl/>\
                        </D:prop>\
                      </D:propfind>';
                      
        /**
         * On a successful response, we create an array of ACE's, to facilitate
         * access for the developer.  Could probably do more here.
         */  
        d.responseProcessor = function(r)
          {
            var ps = AXIS.Util.xml.getChildrenByNameNS(r.responseXML.documentElement, "propstat")[0];
            var st = AXIS.Util.xml.getChildrenByNameNS(ps, "status")[0].firstChild.nodeValue;

            /* FIXME: handle this error better */
            if (st.indexOf("200 OK") < 0)
              return null;

            var acl = new AXIS.WebDAV.Acl(AXIS.Util.xml.getChildrenByNameNS(ps, "ace"));

            r.aces = acl;
            return acl;
          }

        return this.PROPFIND(d);
      };


    /**
     * Attempts to set the ACL on a resource
     *
     *  @param    {Object}  dav     An object containing options for this call.
     *  @href http://greenbytes.de/tech/webdav/rfc3744.html#METHOD_ACL
     *  @throws   DAV_ACL_CHANGED
     */
    this.setACL = function(dav)
      {
        var d = dav || {};
        d.method      = 'ACL';

        return this.getACL(
          {
            url: d.url,
            asynch: d.asynch,
            callback: function(r, newAcl)
              {
                if (newAcl.printXML() != d.acl._origACL)
                  throw new Error("DAV_ACL_CHANGED");
                var acl = d.acl.printXML(true);

                d.body        = '<?xml version="1.0"?>' + acl;
                return AXIS.WebDAV.send(d);
              }
          });
      };
     
    this.getProperty = function(dav) 
      {
        var d       = dav || {};
        var props   = d.properties;
        var xml     = "";
        
        for (var i=0; i < props.length; i++) 
          {
            var prop = props[i];
          
            if (prop.constructor !== Array) 
              {
                prop = [prop];
              }
          
            if (prop.length == 1) 
              {
                prop.unshift(d.ns || "DAV:"); // namespace
              }
            
            xml += "<P:" + prop[1] + " xmlns:P='" + prop[0] + "'/>";
          }
        
        d.body      = '<?xml version="1.0" encoding="utf-8" ?>\
     	                <propfind xmlns="DAV:">\
     	                  <prop>\
     	                    ' + xml + '\
     	                  </prop>\
       	              </propfind>';
       	              
       	return this.PROPFIND(d)
      };
      
    /**
     *  @href http://greenbytes.de/tech/webdav/rfc4918.html#rfc.section.9.1.4  
     *
     *  @param   {Object}  dav  Contains the arguments necessary for call.  At least .url.
     *  @see #PROPFIND
     *  @return  The response from #PROPFIND
     *  @type  Object
     */  
    this.getPropertyNames = function(dav)
      {
        var d         = dav || {};
        d.body        = '<?xml version="1.0" encoding="utf-8" ?>\
           	              <propfind xmlns="DAV:">\
           	                <propname/>\
           	              </propfind>';
	            
        return this.PROPFIND(d);
      };
      
    /**
     *  @href http://greenbytes.de/tech/webdav/rfc4918.html#rfc.section.9.1.5
     *
     *  @param   {Object}  dav Contains the arguments necessary for call.  At least .url.
     *  @see #PROPFIND
     *  @return  The response from #PROPFIND
     *  @type  Object
     */  
    this.getAllProperties = function(dav)
      {
        var d         = dav || {};     
        d.body        = '<?xml version="1.0" encoding="utf-8" ?>\
           	              <D:propfind xmlns:D="DAV:">\
           	                <D:allprop/>\
           	              </D:propfind>';

        return this.PROPFIND(d);
      };
     
    /**
     *  @href http://greenbytes.de/tech/webdav/rfc4918.html#rfc.section.9.1.6 
     *
     *  @param   {Object}  dav Contains the arguments necessary for call.  At least .url.
     *  @see #PROPFIND
     *  @return  The response from #PROPFIND
     *  @type  Object
     */ 
    this.getAllIncludedProperties = function(dav)
      {
        var d         = dav || {};      	 
        d.body        = '<?xml version="1.0" encoding="utf-8" ?>\
           	              <D:propfind xmlns:D="DAV:">\
           	                <D:allprop/>\
           	                  <D:include>\
           	                    <D:supported-live-property-set/>\
           	                    <D:supported-report-set/>\
           	                  </D:include>\
           	              </D:propfind>';

        return this.PROPFIND(d);
      };
    
    this.mkcolParents = function(dav) 
      {
        var self = this;
        var d = dav || {};
        var parentUrl = d.url.replace(/\/[^\/]*\/?$/, '/');

        var d2 = AXIS.merge(dav, {
            on409: function() {
                self.mkcolParents({ 
                    url: parentUrl,
                    onSuccess: function() {
                        self.MKCOL(d);
                    }
                });
            }
        });

        self.MKCOL(d2);
    }
    
    /**
     * Does checks on send arguments, prepares a proper  
     * call object, and returns it.  NOTE that since this object is a DAV-specific
     * preparation of an XHR call, and will ultimately be sent as an XHR call,  
     * the object is further (more strongly) validated within Loader.load().
     *
     *  @private
     *  @param   {Object}  dav    This is passed through by each DAV method, and
     *                            is the original arg object sent by user
     *  @return  A properly prepared call.
     *  @type  Object
     */
    this._prepare = function(dav)
      {
        dav.asynch        = (dav.asynch === undefined) ? false : !!dav.asynch;
        dav.loadingMsg    = dav.loadingMsg || 'DAV::' + dav.method + '::' + dav.url;
        
        /**
         * Make sure sent object has .headers set; if not, empty object.
         */
        dav.headers       = dav.headers || {};
        for(var p in this.defaultHeaders)
          {
            dav.headers[p] = dav.headers[p] || this.defaultHeaders[p];
          }

        /*
         * Gives the returned object the ability to fetch the
         * response in object form (allowing for easy iteration)
         */
        dav.responseXMLObject = this._responseXMLObject;
        
        /**
         * Do some pruning of empty space if we can.
         */
        dav.body && dav.body.replace(/(\>\s*\<)/g, "><");
        
        return dav;
      };
      
    /**
     * Makes the Loader call, passing the DAV call object.
     *
     *  @param   {Object}  dav A WebDAV call object.
     *  @return  Reference to call object in AXIS.Queue if successful; false if not
     *  @type  Object
     */
    this.send = function(dav)
      {
        var p = this._prepare(dav || {});
        if(p)
          {
            return AXIS.Loader.load(p);
          }
        
        return false;
      };
    
    /**
     * Function attached as a public method of an XML response.
     * Freely distributable under the terms of an MIT-style license.
     *
     *  @private
     *  @see #prepare.
     *  @author Shinichi Tomita <shinichi.tomita@hotmail.com>
     */
    this._responseXMLObject = function() 
      {
        if(this.httpHandle && this.httpHandle.responseXML)
          {
            var o = dom2obj(this.httpHandle.responseXML.documentElement);
            var obj = {};
            obj[o.tag] = o.value;
            return obj; 
          }
        else
          {
            return false;  
          }
          
        function dom2obj(elem) 
          {
            if(elem.nodeType == AXIS.TEXT_NODE) 
              {
                return elem.data;
              }
            else if(elem.nodeType == AXIS.ELEMENT_NODE) 
              {
                var obj = {};
                var hasprops = false;
                for(var i=0; i<elem.attributes.length; i++) 
                  {
                    hasprops = true;
                    var attr = elem.attributes[i];
                    if(attr.nodeName.match(/^xmlns/)) 
                      {
                        if (elem.attributes.length == 1) 
                          {
                            hasprops = false;
                          }
                        continue;
                      }
                    obj[attr.nodeName] = attr.nodeValue;
                  }
                for(var i=0; i<elem.childNodes.length; i++) 
                  {
                    var childObj = dom2obj(elem.childNodes[i]);
                    if(childObj.tag) 
                      {
                        hasprops = true;
                        if(obj[childObj.tag]) 
                          {
                            if(obj[childObj.tag].constructor != Array) 
                              {
                                obj[childObj.tag] = [ obj[childObj.tag] ];
                              }
                            obj[childObj.tag].push(childObj.value);
                          } 
                        else 
                          {
                            obj[childObj.tag] = childObj.value;
                          }
                      } 
                    else 
                      { // text content
                        // don't fill the obj with empty linebreaks
                        // - wil
                        if (!childObj.match(/^\n$/i)) 
                          {
                            obj['#'] = childObj;
                          }
                      }
                  }
                if(!hasprops) 
                  {
                    obj = obj['#'] ? obj['#'] : null;
                  }
                return { tag : elem.tagName.replace(/^\w+:/, ''), value : obj };
              }
            return {};
          }
      };
  };
/**
 * Copyright 2008, 2009 Lime Labs LLC
 *
 * This file is part of AXIS.
 *
 * AXIS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License
 * version 3 as published by the Free Software Foundation.
 *
 * AXIS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with AXIS.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @fileoverview
 *
 * @requires  AXIS
 * @requires  WebDAV
 * @requires  Cookies
 * @requires  User
 */
function Login()
  {
    /**
     * @constructor
     */
    this.__construct = function()
      {
        this.forceLogin       = false;
        this.redirReturnTo    = window.location.href;
        this.authCookie       = false;
        this._authState       = null;
        this._loginFrameWindow = {};
        this._returnTo        = location.protocol + "//" + location.host + "/!lime/root/logout";
        this._baseHomePath    = "http://limebits.com/apps/finder/#folder=/home/";
        
        AXIS.Errors.register('LB_LOGOUT_INVALID_SIGNOUT', "Couldn't log out of all domains", 3);

        this.onAuthUpdate = AXIS.CustomEvents.create();
        
        this.beforeLogout = AXIS.CustomEvents.create();
        
        /*
         * Fetch site/user login data and store. Note that this is
         * synchronous.
         */     
        var resp = AXIS.WebDAV.GET({
                  			method:       'GET',
                     	  url:          '/!lime/root/lib/site.json',
                     	  asynch:				false,
                     	  callId:       'LOGIN_FETCH_DATA'
                     	});

        AXIS._siteData = eval('('+resp.responseText+')');

        if(AXIS.settings('noLogin') === false)
          {
            AXIS.onContentReady(function() {
              this._createAutoLoginFrame();
              this.login();
            },this);
          }
      };

    this._authStateUpdate = function(state)
      {
        if (state === this._authState)
          return;

        this._authState = state;

        this.onAuthUpdate.fire([state]);
      }
      
    /*
     * The login/auth check, executed on every page
     */
    this.login = function()
      {    //alert('logging in');
        
       	/*
       	 * This is a non-cached resource. Ensures that we have the latest cookies.
       	 */
    		AXIS.WebDAV.GET({
    			method:       'GET',
       	  url:          '/!lime/root/logout',
       	  asynch:       false,
       	  callId:       'LOGIN_CHECK'
       	});
       	
        if(AXIS.Cookies.read('auth') && !AXIS.Cookies.read('user'))
	        {
	          AXIS.Cookies.erase('auth');
	        }
       
        // return if already logged in
        if(AXIS.User.isLoggedIn())
          {
            return this._authStateUpdate(AXIS.Cookies.read('user'));
          }

        AXIS.Login.setAuthCookie(
          function()
            {
              AXIS.Login._submitAutoLoginFrame();
            });                 

        return true;
      };
    
    this.logout = function()
      {
        AXIS.Login.beforeLogout.fire({
          args: [] 
        });

        if (location.protocol + "//" + location.host + "/" == AXIS._siteData.hosts.limebits)
          {
            AXIS.Login._submitAutoLogoutFrame();
          }
        else
          {
            AXIS.Cookies.erase('auth');
            AXIS.Cookies.erase('user');
            this._authStateUpdate(false);
          }
      };

    this.setAuthCookie = function(cb)
      {
        this.authCookie = AXIS.Cookies.read('auth');
        if(!this.authCookie) 
          {
            AXIS.WebDAV.GET(
              {
                method:       'GET',
                url:          '/!lime/root/authonly',          	    
           	on401:        function(r)
           	  {
           	    AXIS.Login.authCookie = AXIS.Cookies.read('auth');
           	    //alert(r.responseText);
           	  },
           	callback:     function(r)
                  {
           	    AXIS.Login.authCookie = AXIS.Cookies.read('auth');
                    cb();
                  },
           	asynch:       false, 
           	callId:       'GET_AUTH_COOKIE'
              }
            );
          }
      };

    /**
     * Creates the autologin frame in DOM
     *
     *  @see #_loadFrameHandler
     */
    this._createAutoLoginFrame = function()
      {
        var loginFrame = document.createElement('iframe');
        loginFrame.style.display = 'none';
        var frm = document.body.appendChild(loginFrame);

        if (frm.contentDocument && frm.contentDocument.defaultView)
          this._loginFrameWindow = frm.contentDocument.defaultView;
        else
          this._loginFrameWindow = frm.contentWindow;
        this._loginFrameEl = frm;

        AXIS.attachEvent('load',this._loadFrameHandler,frm);
      };
     
    /**
     * When the login frame (referenced by #_loginFrameWindow; @see #_createAutoLoginFrame)
     * has its src changed, this is the handler fired when given src is loaded.
     *
     *  @private
     *  @see #_createAutoLoginFrame
     */
    this._loadFrameHandler = function()
      {
        var loc   = AXIS.Login._loginFrameWindow.location;
        var hsh;
        
        try 
          {
            hsh   = loc.hash;
            if (hsh === undefined || loc.pathname != '/!lime/root/logout')
              throw "error";
          } catch(e) { return; }
            
        hsh       = hsh.split('#')[1] || false;
        
        //alert('loc: ' + loc + ' - ' + 'hash: ' + hsh);
        
        var cook  = AXIS.Cookies.read('user');

        switch(hsh)
          {
          case false:
            if (cook)
              {
                AXIS.Login._authStateUpdate(cook);
                break;
              }
          case 'noauth':
          case 'noallow':
            // TODO: look into handling no_allow with a modal iframe popup

            if(AXIS.Login.forceLogin)
              {
                AXIS.Login._redirectToAccess();
              }
            else
              {
                AXIS.Login._authStateUpdate(false);
              }
            break;
          case "signout":
            AXIS.Cookies.erase('auth');
            AXIS.Cookies.erase('user');
            AXIS.Login._authStateUpdate(false);
            break;
          case "nosignout":
            throw new Error('LB_LOGOUT_INVALID_SIGNOUT');
            break;
          default:
            break;
          }
      };
    
    /**
     * Submits the login frame created with #_createAutoLoginFrame
     *
     *  @see #_loadFrameHandler
     */
    this._submitAutoLoginFrame = function() 
      {
        //alert('submitting frame');
        AXIS.Login._loginFrameWindow.location.replace(AXIS._siteData.hosts.secure + 'secure/auth_validator.html#auth_cookie=' + AXIS.Login.authCookie  + '&return_to=' + escape(AXIS.Login._returnTo));
      };

    this._redirectToAccess = function(join)
      {
        AXIS.Login.authCookie = AXIS.Cookies.read('auth');
        top.location = AXIS._siteData.hosts.secure + 'secure/access.html#auth_cookie=' + AXIS.Login.authCookie + (join ? ('&sign_up=' + join) : '') + (AXIS.Login.redirReturnTo ? ('&return_to=' + escape(AXIS.Login.redirReturnTo)) : '');
      }
         
    this._submitAutoLogoutFrame = function() 
      {
        AXIS.Login.authCookie = AXIS.Cookies.read('auth');
        AXIS.Login._loginFrameWindow.location.replace(AXIS._siteData.hosts.secure + 'secure/auth_validator.html#logout=1&www_auth_cookie=' + AXIS.Login.authCookie + '&return_to=' + escape(AXIS.Login._returnTo));
      }

    /*
     * @return    {String} full path to user home folder
     */
    this.homePath = function()
      {
        return this._baseHomePath + AXIS.User.isLoggedIn();
      };
  };
/**
 * Copyright 2008, 2009 Lime Labs LLC
 *
 * This file is part of AXIS.
 *
 * AXIS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License
 * version 3 as published by the Free Software Foundation.
 *
 * AXIS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with AXIS.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @fileoverview
 *
 * AXIS is the core framework that all other objects register with.  It is
 * inherited as the prototype of any class attached via its #register method.
 * This is an essential library, and nothing will work without it.  It is 
 * expected that the core of your build will be the AXIS supplemented by several
 * registered classes.  
 *
 * The goal of the library is to make the caretaking of large javascript applications
 * easier.  At the most basic level, this involves making it easy to add new code
 * as classes and objects without creating namespace conflicts, and to do so in a way
 * which is natural and easy to follow.  
 *
 *  @throws  Error   AXIS_REG_FAIL
 *  @throws  Error   AXIS_BROW_NOT_SUPP
 *  @throws  Error   AXIS_FRAMEWORK_LOAD_TIMEOUT
 *  @throws  Error   AXIS_ERR_REG_FAIL
 *  @throws  Error   AXIS_INCLUDE_SCRIPT_ERROR
 *  @throws  Error   AXIS_INCLUDE_SCRIPT_NO_SRC
 *  @throws  Error   AXIS_INCLUDE_CSS_ERROR
 *  @throws  Error   AXIS_INCLUDE_CSS_NO_HREF
 */ 

var $AXIS = function()
  {      
    /**
     * Check if this is a combined/minified version of the AXIS library
     */
    this.minFName = (typeof XMLHTTP == 'function') ? 'AXIS.combined.js' : 'AXIS.js';
       
    /**
     * Set up environment info
     */
    this.uA =                 window.navigator.userAgent.toLowerCase();
    this.browserVersion =     parseFloat(this.uA.match(/.+(?:rv|it|ml|ra|ie)[\/: ]([\d.]+)/)[1]);
    this.isSafari =           /webkit/.test(this.uA);
    this.isOpera =            /opera/.test(this.uA);
    this.isIE =               /msie/.test(this.uA) && !/opera/.test(this.uA);
    this.isMoz =              /mozilla/.test(this.uA) && !/(compatible|webkit)/.test(this.uA);
    this.isWebKit =           /AppleWebKit/.test(this.uA);
    this.isGecko =            /Gecko/.test(this.uA) && (/KHTML/.test(this.uA) === false);
    this.isKHTML =            /KHTML/.test(this.uA);
    this.isMobileSafari =     !!this.uA.match(/Apple.*Mobile.*Safari/);
    
    this.isMac =              /mac/.test(this.uA);
    this.isWindows =          /win/.test(this.uA);
    this.isLinux =            /linux/.test(this.uA);
    this.isUnix =             /x11/.test(this.uA);

    /**
     * The shortcut path to the root directory of system (containing such key folders
     * as /css, /library, /home, libraries/ etc)
     */
    this.ROOT   = function()
      {
        return '/!lime/root/';
      };
    
    /**
     * Returns the path to a given script. 
     *
     *  @param    {String}    [s]   The script filename.  Defaults to this.minFName
     *  @type     {String}
     *  @example: AXIS.PATH('AXIS.js')
     *  @see      #initialize
     */
    this.PATH = function(s)
      {
        var script = s || this.minFName;
        var src = this.getScriptSrc(s);
        if (src)
                return src.replace(new RegExp(script + ".*"), '');
        /**
         * Script not found, path unknown
         */
        return '';
      };

    this.getScriptSrc = function(s) {
        var script = s    || this.minFName;
        
        var scripts = document.getElementsByTagName("script");    
        for(var i=0; i < scripts.length; i++) 
          {
            var src = scripts[i].getAttribute("src");
            if (src && src.match(script)) {
                return src;
            }
          }

        return '';
    }

    /**
     * Escape CSS selector
     */
    this.escapeCSS = function(s) {
        return s.replace(/([.:/%* >+~])/g, "\\$1");
    }
      
    /**
     * General null function, used variously.
     */
    this.F  = function(){};

    /**
     *
     *  @private
     *  @see #settings
     *  @see #initialize
     */

    /**
      merge properties of primary object with secondary 
      */
     this.merge = function(primary, secondary) {
         var result = {};
         for(var p in primary) {
             result[p] = primary[p];
         }

         for(var p in secondary) {
             result[p] = secondary[p];
         }

         return result;
     };

    this._settings =         [];
    
    /**
     * Accessor for #_settings.
     *
     *  @param      {String}    q   The query property
     *  @returns                    The query property value, or false.
     *  @type       {Mixed}
     */
    this.settings = function(q)
      {
        if(q && this._settings[q])
          {
            return this._settings[q];  
          }   
        return false;
      };
      
    /**
     * @private
     * @see #enableNotifications
     * @see #disableNotifications
     */
    this._notificationsEnabled  = true;
    
    /**
     * Site data file, used variously, mainly for Login
     *
     *  @private
     *  @see #initialize
     *  @see __build__.js
     */  
    this._siteData  =           {};
    
    /**
     *  @private
     *  @see #contentReady
     */
    this._contentReady  =       false;
    
    /**
     *  @private
     *  @see #windowLoaded
     */
    this._windowLoaded =        false;
    
    /**
     *  @private
     *  @see #onContentReady
     */
    this._contentReadyFuncs  =  [];
    
    /**
     *  @private
     *  @see #onWindowLoaded
     */
    this._windowLoadedFuncs  =  [];
       
    /**
     *  @private
     *  @see #isActivated
     */
    this._isActivated  =        false;
    
    /**
     * The css files which every page gets
     *
     *  @private
     */
    this._generalCSS  =         [this.ROOT() + 'css/AXIS.css'];
    
    /**
     * These are the objects which together w/ AXIS represent the base framework.  Except
     * for the special case of the google api, the others are all AXIS objects that
     * will be instantiated PRIOR to onContentReady (ie. ready immediately following
     * the <script> tag which includes AXIS).  You may add other scripts.
     * NOTE: order is important.  Be sure to leave CustomEvents.js as the first script
     * after google api, and Login.js as the very last.  When adding it is a good idea
     * to simply insert before Login.js.
     *
     * In order to be included in AXIS.combined.js, be sure to put your script on a separate
     * line and have your line be of the form "this.PATH() + 'yourscript.js'".
     * 
     * Please do not remove the BEGIN LIMEBITS_.... line below.  It is used by our Rakefile
     * to identify where to start parsing out the names of the scripts to include in
     * AXIS.combined.js
     *
     * BEGIN LIMEBITS_SITE_AXIS_COMBINED_FRAMEWORK_LIST
     *
     *  @private
     */
     
    this._framework =  [
      this.PATH() + 'CustomEvents.js',
      this.PATH() + 'Util.js',
      this.PATH() + 'Cookies.js',
      this.PATH() + 'User.js',
      this.PATH() + 'Loader.js',
      this.PATH() + 'XMLHTTP.js',
      this.PATH() + 'WebDAV.js',
      this.PATH() + 'Login.js'    
     ];
    /* END LIMEBITS_SITE_AXIS_COMBINED_FRAMEWORK_LIST */
                      
    /**
     * There is a debugging library available, Errors, which is made use of variously
     * in the AXIS (and elsewhere). You can completely disable error handling, and save 
     * yourself the overhead of loading the Errors file (which contains a lot of text), simply
     * by NOT including Errors.  However, you will probably still have calls in your code
     * to the Error object, which you should leave in so that debugging can be done (by
     * including Errors).  So for cases where you haven't included Errors, we need to still
     * handle calls to Errors -- mainly, .register and .catchError.  So here we
     * create a dummy object to do that.
     */
    this.Errors =
      {
        register:     this.F,
        catchError:   this.F
      };
      
    /**
     * A reference to every script include object is registered here, indexed by its .id.
     *
     *  @private
     *  @see #includeScript
     */                      
    this._registeredScripts =    [];
    
    /**
     * The # of ms allowed before login is considered to have failed, at
     * which point a AXIS_FRAMEWORK_LOAD_TIMEOUT error is thrown.
     *
     *  @private
     *  @see #initialize
     */
    this._maxTimeToLogin  =       60000;
    
    /**
     * The # of ms that an XHR request will poll for readystate 4 before dying.
     *
     *  @private
     *  @see Queue#add
     *  @see XMLHTTP#build
     */
    this._maxXHRLifespan  =       15000;

    /**
     * ID and CLASS attributes of the DOM containers for messages (both
     * notifications and loading messages).  These should be defined in base.css. 
     *
     *  @see Loader#load
     *  @see Loader#createLoadingPanel
     *  @see Loader#updateLoadingPanel
     *  @see Loader#clearLoadingPanelItem
     *  @see #showNotification
     *  @see #_onContentReady
     */
    this.loadingMsgContainerId  =           'AXIS_loading_panel';
    this.loadingMsgItemClass  =             'AXIS_loading_panel_item';
    this.defaultLoadingMsg  =               'Loading...';
    this.notificationContainerId  =         'AXIS_notification_container';
    this.notificationItemClass  =           'AXIS_notification';
    this.notificationCloseButtonClass =     'AXIS_notification_close_button';
    
    /**
     *  @see #setNotificationDelay
     *  @see #fadeTo
     *
     * NOTE: Default is to keep notification visible until user closes it, so we
     * have an absurdly large number. 
     */
    this._notificationDelay  =            86400000;
    
    /**
     * Ms between each step of -opacity in #fadeTo, as used in notifications.
     *
     *  @see #fadeTo
     */
    this.notificationFadeSpeed  =         5; // Ms between each step of -opacity 
    
    /**
     * Readable Node values
     */
    this.ELEMENT_NODE =                   1;
    this.ATTRIBUTE_NODE =                 2;
    this.TEXT_NODE =                      3;
    this.CDATA_SECTION_NODE =             4;
    this.ENTITY_REFERENCE_NODE =          5;
    this.ENTITY_NODE =                    6;
    this.PROCESSING_INSTRUCTION_NODE =    7;
    this.COMMENT_NODE =                   8;
    this.DOCUMENT_NODE =                  9;
    this.DOCUMENT_TYPE_NODE =             10;
    this.DOCUMENT_FRAGMENT_NODE =         11;
    this.NOTATION_NODE =                  12;

    /**
     * Will parse any string sent as one having a possible querystring -- that a `?`
     * character exists after which there are query arguments, in the format one
     * would expect with standard http querystrings.  If such a querystring is found,
     * it will be parsed.  An example:
     *
     * general.js?specialArgs=one+two+three&moreArgs=foobar
     *
     * - `specialArgs` will be a new index in returned array;
     * - `+` is coverted to space ` `, so value of `specialArgs` is
     *   a string with spaces: `one two three`;
     * - `moreArgs` is another key in returned array, value `foobar`.
     *
     *  @param      {String}      qst     A string.
     *  @returns                          An array filled as described above.
     *  @type       {Array}
     *  @see      #initialize
     */
    this.fetchBuildArguments = function()
      {
        var s;
        var ret = [];
        var args;
        
        var scripts = document.getElementsByTagName("*");    
        for(var i=scripts.length; i--;) 
          {
            var src = scripts[i].src;
            if(src && src.match(this.minFName)) 
              {
                args = scripts[i].getAttribute('arguments');
                args = args ? args.split('+') : [];
                ret['extensions']  = scripts[i].getAttribute('extensions') || '';
                ret['libraries']   = scripts[i].getAttribute('libraries') || '';
        
                for(var i=0; i < args.length; i++)
                  {
                    s = args[i].split('=');
                    ret[s[0]] = s[1] || true;
                  }
                this._settings = ret; 
                break;
              }
          }
      };

    /**
     * Will parse any string sent as one having a possible querystring -- that a `?`
     * character exists after which there are query arguments, in the format one
     * would expect with standard http querystrings.  If such a querystring is found,
     * it will be parsed.  An example:
     *
     * general.js?specialArgs=one+two+three&moreArgs=foobar
     *
     * - `specialArgs` will be a new index in returned array, value of 'one';
     * - `+` is coverted to space ` `, so value of `specialArgs` is
     *   a string with spaces: `one two three`;
     * - `moreArgs` is another key in returned array, value `foobar`.
     *
     *  @param      {String}      qst     A string.
     *  @returns                          An array filled as described above.
     *  @type       {Array}
     */
    this.parseQueryString = function(qst)
      {
        var str     = qst || '';
        /**
         * Get any `?` querystring
         */
        var query   = str.replace(/^[^\?]+\??/,'');                  
        var ret     = [];
        var v;
        
        if(query)
          {
            var z = query.split('&');
            
            for(var w=0; w < z.length; w++) 
              {
                var kv = z[w].split('=');
                if(kv.length > 0) 
                  { 
                    var k = unescape(kv[0]);
                    
                    /**
                     * Query args don't necessarily have to have a value; there
                     * may be no kv[1]
                     */
                    if(kv[1])
                      {
                        v = unescape(kv[1]);
                    
                        /**
                         * Change `+` to space
                         */
                        v = v.replace(/\+/g, ' ');
                      }
                    else
                      {
                        v = true;
                      }
                    
                    ret[k] = v;
                  }
              }
          }
        return ret;
      };

    /**
     * Extension of the AXIS is done by registering classes via this function. Any
     * class so registered inherits (via prototype chain) this method, allowing the
     * registered class to further register `subclasses`.
     *
     *  @param   {String}  scr   A String representation of the class to be registered
     *  @throws  Error   AXIS_REG_FAIL
     */
    this.register = function(scr)
      {
        /**
         * Do not re-register. NOTE: The Errors extension is a special case.  AXIS
         * has a "dummy" Errors object, which handles bug reports quietly when
         * the actual Errors extension is not requested.  So, if the user has 
         * requested the Errors extension, given below, it won't actually be loaded,
         * as there already exists the mentioned dummy Errors object.  
         */
        if(AXIS.hasOwnProperty(scr) && (scr != 'Errors'))
          {
            return;  
          }
          
        try
          {
            /**
             * Set prototype of object definition to caller
             */
            window[scr].prototype = this;    
          
            /**
             * Add new object to this collection
             */
            this[scr] = new window[scr]();
            
            /**
             * If `createGlobals` has been passed via query, set global.  
             * NOTE: What you are doing by creating globals is creating a shortcut
             * to any registered framework object in the global namespace.  For example,
             * if you register an object `MyStuff`, which when registered is now 
             * accessbible via `AXIS.MyStuff`, you will also be able to access it
             * via `$$Mystuff`.  This should be ok, but in general globals can cause
             * conflicts, so it is up to you to make sure you aren't creating collisions.
             *
             *  @see #PATH
             *  @see #initialize
             */
            if(this.settings('createGlobals'))
              {
                window['$$' + scr] = this[scr];
              }

            /**
             * Mark original class def for cleanup
             */
            window[scr] = null;

            /**
             * Call constructor, if any.
             */
            this[scr].__construct && this[scr].__construct();

            return this[scr];
          }
        catch(e)
          {
            this.Errors.catchError('AXIS_REG_FAIL',e);
          }
      };

    /**
     * Namespace storage. 
     *
     *  @param  {String}    ns  The namespace, form of `chain.like.this`, which 
     *                          creates $AXIS.chain.like.this namespace.
     *  @return                 If no argument sent, will get back an empty {}
     *  @type   {Object}  
     */
    this.createNamespace  =  function(ns)
      {    
        try
          {            
            var a = arguments;
            var x, y, f;
            
            /**
             * Allowing for multiple namespace `strings` to be sent
             */
            for(var i=0; i < a.length; i++)
              {
                x = a[i].split(".");
                y = $AXIS;
    
                for(var z=0; z < x.length; z++) 
                  {
                    /**
                     * Simply adding to the chain, and repointing y to the new node.
                     * NOTE that pre-existing nodes are preserved.
                     */
                    y = y[x[z]] = y[x[z]] || {};
                  }
              }
            
            return eval('$AXIS.' + ns);
          }
        catch(e)
          {
            return {};
          }
      };

    /**
     * Sets the number of milliseconds that a notification should
     * display prior to fading out.
     */    
    this.setNotificationDelay = function(tm)
      {
        if(tm && (typeof tm == 'number'))
          {
            this._notificationDelay = tm;
          } 
      };
    
    /**
     * Shows a notification message.  The behaviour is as follows:
     * 1. Show notification and set its fading behaviour.  The default of the
     *    AXIS is to have an absurdly large delay before fading (24 hours), which
     *    means the user will not miss the notification if away from desk.  You
     *    can change this value via .setNotificationDelay().
     * 2. Each notification is given a dismiss button ('OK') to its rightmost, and
     *    clicking this button will get rid of not only the current notice, but
     *    ALL notices. This follows the logic of next behaviour.
     * 3. Any click on the screen will terminate all existing notices.
     *
     * NOTE that the notification is only shown after content is ready.
     *
     *  @param      {Object}      v       The notification info object:
     *    {
     *      content   {String}    The content of the message. Can be HTML.
     *      but       {Boolean}   Whether to show a close button. Default true.
     *      onDismiss {Function}  A function to execute when notification dismissed.
     *      typ       {String}    The type of notification (TODO)
     *    }
     */
    this.showNotification = function(v)
      {
        if(v && v.content && this._notificationsEnabled)
          {
            AXIS.onContentReady(function(){
              var b           = v.but || true;
              var t           = v.typ || 'default';
              b               = b ? '<input class="' + AXIS.notificationCloseButtonClass + '" type="button" value="OK" onclick="this.parentNode.nClose()" />' : '';
              var n           = document.getElementById(AXIS.notificationContainerId);
              var d           = n.appendChild(document.createElement('div'));
              d.id            = AXIS.getUniqueId('notification_');
              d.className     = AXIS.notificationItemClass;
              d.innerHTML     = v.content + b;
      
              var f = AXIS.fadeTo({
                'element':  d
              });
              
              d.nClose = function(e)
                { 
                  AXIS.detachEvent('click',d.nClose);
                  f.forceFade();  
                  
                  v.onDismiss && v.onDismiss();
                }
              
              /**
               * This event will force a close of all visible notifications.
               * You can change the event, or simply comment this out.
               */  
              AXIS.attachEvent('dblclick',d.nClose);
            });
          }
      };
    
    /**
     * Turns off all notifications.
     */
    this.disableNotifications = function()
      {
        this._notificationsEnabled = false;  
      };
      
    /**
     * Turns on all notifications.  AXIS default is on.
     */
    this.enableNotifications = function()
      {
        this._notificationsEnabled = true;  
      };

    /**
     * Creates a unique id, suitable for id="" usage, and elsewhere
     *  @param   {String}  pref  An optional prefix.  defaults to 'id_'
     *  @return  A unique id
     *  @type    String
     */
    this.getUniqueId  = function(pref)
      {
        var d = new Date;
        return (pref || 'id_') + parseInt(Math.random(d.getTime())*Math.pow(10,10));
      };
      
    /**
     * Whether the framework initialization is completed
     *
     *  @type  Boolean
     */
    this.isActivated  = function()
      {
        return this._isActivated;
      };
    
    /**
     * This manages the window.onload and contentReady collection, and is
     * called by the public function #onWindowLoad and #onContentReady.  Both
     * load handlers store passed function, scope argument in a private AXIS
     * array, and execute that array upon determination that given event has
     * occurred.  The only difference is the naming of the arrays:
     * - window loaded:     #_windowLoadFuncs
     * - content ready:     #_contentReadyFuncs,
     * ... and the the functions which return the state of a given event:
     * - window loaded:     #windowLoaded
     * - content ready:     #contentReady
     *
     * So this function is passed the event name and does the common work.
     *
     *  @param    {Object}    ob    {
     *                                ev:     One of 'windowLoaded' || 'contentReady'
     *                                func:   The function.
     *                                scope:  Scope if any.  Default is `window`.
     *                              }
     *  @returns                    Nothing.
     *  @see      #onWindowLoad
     *  @see      #onContentReady
     *  @see      #windowLoaded
     *  @see      #contentReady
     *  @see      #_windowLoadedFuncs
     *  @see      #_contentReadyFuncs
     */
    this._setEventReady = function(ob)
      {
        /**
         * It is possible for a function to be attached after this
         * event has fired, resulting in that function never being
         * executed.  Check condition, and fire if that is the case.
         */
        if(AXIS[ob.ev]())
          {
            ob.func.apply(ob.scope);
          }
        else
          {
            AXIS['_'+ob.ev+'Funcs'].push(ob);
          }
      };
      
    /**
     * Whether window.onload event has been handled
     *
     *  @see #_onWindowLoad
     *  @type  Boolean  
     */
    this.windowLoaded = function()
      {
        return this._windowLoaded;
      };

    /**
     * Informs whether DOMContentLoaded has been handled.
     *
     *  @see #_onContentReady
     *  @see #start
     *  @type  Boolean
     */
    this.contentReady = function()
      {
        return this._contentReady;  
      };
      
    /**
     * Public function allowing the attachment of functions to run 
     * when window loaded.  Functions executed in order attached.
     *
     *  @param    {Function}  f   The function to call
     *  @param    {Mixed}     s   The scope to call the function in. Default to window.
     *  @return   Success/failure of attachment
     *  @type     Boolean
     *  @see      #_setEventReady
     */
    this.onWindowLoad = function(f,s)
      {
        return this._setEventReady({
          ev:       'windowLoaded',
          func:     f || AXIS.F,
          scope:    s || window
        });
      };
      
    /**
     * Public function allowing the attachment of functions to run
     * when DOM ready.  Functions executed in order attached.
     *
     *  @param    {Function}  f   The function to call
     *  @param    {Mixed}     s   The scope to call the function in. Default to window.
     *  @return   Success/failure of attachment
     *  @type     Boolean
     *  @see      #_setEventReady
     */
    this.onContentReady = function(f,s)
      {          
        return this._setEventReady({
          ev:       'contentReady',
          func:     f || AXIS.F,
          scope:    s || window
        });
      };
   
    /**
     * Handles the window.onload event
     *
     * @private
     * @see #start
     */
    this._onWindowLoad  = function()
      {     
        /**
         * Window loaded. 
         */
        AXIS._windowLoaded    = true;

        var wlf = AXIS._windowLoadedFuncs;
        for(var f=0; f < wlf.length; f++)
          {
            wlf[f].func.apply(wlf[f].scope);
          }
      };

    /**
     * Sets up handler for DOMContentLoaded event.
     *
     *  @private
     *  @author Diego Perini (diego.perini at gmail.com)
     *  @updated Sandro  14.08.08
     *  @license MIT
     *  @version 1.1
     *  @url http://javascript.nwbox.com/ContentLoaded/
     *  @url http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE
     *  @see #start
     *  @see #onContentReady
     *  @throws  Error   AXIS_BROW_NOT_SUPP
     */
    this._onContentReady  = function() 
      {     
        var w   = window;
        var d   = w.document;
        var D   = 'DOMContentLoaded';
        var dl  = false;
      
        function init(e) 
          {      
            if(dl === false)
              {
                dl = true;
                
                AXIS._contentReady = true;
                
                /**
                 * Create the notification element.  See .showNotification()
                 */
                var n   = document.body.appendChild(document.createElement('div'));
                n.id    = AXIS.notificationContainerId;

                /**
                 * Execute any stored onContentReady functions, once
                 * AXIS is activated.  
                 */
                var crf = AXIS._contentReadyFuncs;
                for(var f=0; f < crf.length; f++)
                  {
                    crf[f].func.apply(crf[f].scope,[e || {}]);
                  }
              }
          };
          
        /**
         * In special cases where the AXIS is included dynamically (ie. not included
         * as a <script> tag directly, but injected via the DOM), we can expect that
         * document.body.appendChild will exist.  If the AXIS is loaded via <script>
         * document.body.appendChild will NOT exist.  If it does exist, in other words,
         * we fire the contentLoaded event (by calling init())
         */
        if(document && document.body && document.body.appendChild) 
          { 
            init({}); 
            return;
          }
              
        /*-------------------------------------*
         * DOM Loaded Trigger                  *
         * Based on the work of Jesse Skinner, *
         * Dean Edwards, Matthias Miller,      *
         * John Resig, and Aaron Gustafson     *
         *-------------------------------------*/
        var __load_timer = false, __old_onload;
        // for Mozilla/Opera9
        if(document.addEventListener)
          {
            document.addEventListener( 'DOMContentLoaded', function()
              {
                document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
                init();
              }, false );
          }
        // for Internet Explorer
        /*@cc_on @*/
        /*@if (@_win32)
          document.write("<script id=__ie_onload defer src=//0><\/scr"+"ipt>");
          script = document.getElementById("__ie_onload");
          script.onreadystatechange = function() {
            if (this.readyState == "complete"){ init(); } // call the onload handler
          };
        /*@end @*/
        // for Safari
        if(AXIS.isSafari) // sniff
          {
            __load_timer = setInterval( function()
              {
                if ( /loaded|complete/.test( document.readyState ) )
                  {
                    clearInterval( __load_timer );
                    init();
                  }
              }, 10);
          }
        
        // for other browsers set the window.onload, but also execute the old window.onload
        __old_onload = window.onload;
        window.onload = function()
          {
            init();
            if ( __old_onload ){ old_onload(); }
          };
      };
    
    /**
     * Upon instantiation of AXIS object (see bottom of file), a call to #initialize
     * is made, which:
     * - Sets a timeout to handle framework loading timeouts;
     * - Calls #PATH on AXIS.js, which is done to fetch any query args regarding
     *   requested extensions to AXIS;
     * - Add any extensions to the core #_framework array;
     * - Load all core files, including extensions, and when they are loaded, load
     *   the initialization script (__build__.js), which does registration of objects,
     *   fetches user domain data, fetches user data via google api, and calls #start.
     *   
     *  @see          __build__.js
     *  @throws       AXIS_FRAMEWORK_LOAD_TIMEOUT
     */
    this.initialize = function()
      { 
        /**
         * We want to check here if this is minified.  When minified, the #_framework
         * files are NOT to be loaded.  However, the #_framework array is still needed,
         * as it will be added to, below, if there are extensions, etc, to load.  When
         * minified, the #_framework files are added to the top of this file (>cat). We
         * don't want to load them again.  So we clear the #_framework array if minified.
         * The check is for the XMLHTTP object; it could be any essential file.
         */
        if(typeof XMLHTTP == 'function')
          {
            AXIS._framework = [];      
          }
        
        var AF = AXIS._framework;
        
        /**
         * Load any general CSS
         */
        for(var f=0; f < this._generalCSS.length; f++)
          {
            this.includeCSS({
              id:   'css_' + f,
              href: this._generalCSS[f]
            });
          }
          
        /**
         * Get any query args first
         */
        AXIS.fetchBuildArguments();
        
        /**
         * Add the google api loader on request, simply by adding to the framework array. 
         * Note: need to pass `useGoogleAPI` to AXIS arguments.
         *
         *  @see AXIS#Modules#load
         *  @see AXIS#User#getUserGeoData
         */
        if(AXIS.settings('useGoogleAPI'))
          {
            AF.push('http://www.google.com/jsapi?key=ABQIAAAABH14nUM9IOGSATH59A8PIxTtVJmlcGkc8uAjvGT8FSkFC9SscxRd4KeXJgXC39BF8yapmiOggBEOdg');
          }
        
        var AP  = AXIS.PATH();
        var lib = AXIS.settings('library');
        
        /**
         * .isActivated is set in #start.  When #start fires, this means
         * that the core components of the AXIS have loaded.  If #start has 
         * not fired by _maxTimeToLogin, something is probably wrong, so notify.
         *
         *  @see start
         */
        setTimeout(function()
          {
            if(AXIS.isActivated() === false)
              {
                AXIS.Errors.catchError(new Error('AXIS_FRAMEWORK_LOAD_TIMEOUT'));
              }
          },this._maxTimeToLogin);

        /**
         * Private, local helper function to parse queries for various types of 
         * extension lists, add to loading array, etc... see below.
         *
         * NOTE: There is a check for the query argument, `forceScriptInsertion`.
         *
         *  @param      {String}    qN    The query arg name (extensions, libraries, etc)
         *  @param      {String}    aP    Added subpath ('libraries', which means axis/libraries/)
         */
        var _qL = function(qN,aP)
          {
            var q = AXIS._settings;
            var aPath = aP ? aP + '/' : '';

            if(q[qN] && (q[qN] != ''))
              {
                var a  = q[qN].split('+');
                for(var i=0; i < a.length; i++)
                  {
                    AF.push(AP + aPath + a[i] + '.js'); 
                  }
              }
          };
        
        _qL('extensions');
        _qL('libraries','libraries');

        /**
         * When loading an extension `Extension` there is expected to exist a file, `Extension.js`,
         * which contains a constructor function named `Extension`.  That `Extension` constructor
         * is intantiated and attached to the AXIS (via #register) as AXIS.Extension.  At which point
         * the constructor function `Extension` is "destroyed" by having its value set to null 
         * (Extension = null).  There is a possibility, though unlikely, that the name of a constructor
         * function for an extension has already been defined in the DOM prior to the AXIS loading process.
         * That is, what if `Extension` had already been defined prior to AXIS loading process?  In that
         * case we want to store the original value, do our business with extensions, and then replace the
         * original value once `Extension` has been instantiated.  So, prior to doing our object registrations,
         * we store any existing values here, and replace them after instantiation (in __build__.js).
         *
         *  @see __build__.js
         */
        AXIS.createNamespace('__TVAR');
        $AXIS.__TVAR = [];
        for(var i=0; i < AF.length; i++)
          {
            var nm  = AF[i].split('/');
            nm      = nm[nm.length-1].split('.')[0];
            $AXIS.__TVAR[nm] = window[nm] || false;            
          }
          
        var build = function()
          {
            if((AXIS.minFName == 'AXIS.js') || (AXIS.isIE && AXIS._framework.length))
              {
                AXIS.includeScript(AP + '__build__.js');
              }
            else 
              {
/**
 * Begins the startup process for the AXIS.  Mainly, registers any extensions that
 * were passed as query args, executes anything stored in the AXIS <script> block,
 * restores any global vars overwritten in the load process, and tells the AXIS to start.
 *
 *  @param    {Object}    [exts]      Extensions that need to be registered.    
 */

(function(){
  
var fi,oI,exts,libs,dlist;
var fs            = AXIS._framework;
var AP            = AXIS.PATH();
var extNames      = [];
var allNames      = [];

/**
 * Go through the framework list, strip out paths and such, 
 * and get an array of object names.  At this point, check for dependencies.
 * After we have established dependencies, initiate the load of those, if any,
 * and when we have all our shizzle ready, register the objects and start
 * the AXIS.
 */
for(var r=0; r < fs.length; r++)
  {
    /**
     * Strip out the filename; lose all path info, and extension.
     */
    fi = fs[r].substring(fs[r].lastIndexOf('/')+1, fs[r].lastIndexOf('.'));

    if(fi.charAt(0) != '_')
      {
        /**
         * If this file exists in the AXIS folder, then this is an extension.
         */
        if(fs[r].indexOf(AXIS.PATH() + fi)!=-1)
          {
            extNames[fi] = fs[r].src;
          }
          
        if(fs[r].indexOf(AXIS.PATH())!=-1)
          {
            allNames[fi] = fs[r];
          }
      }
  }

var rObs = extNames || []; 

/**
 * Add forced dependencies (objects written into main AXIS file) in
 * case of using crunch method (ie. these objects have been appended 
 * to the top of this file).
 */
rObs['CustomEvents'] = 1;
rObs['Util'] = 1;
rObs['Cookies'] = 1;
rObs['User'] = 1;
rObs['Loader'] = 1;
rObs['XMLHTTP'] = 1;
rObs['WebDAV'] = 1;
rObs['Login'] = 1;

/**
 * Register the objects
 */
for(var n in rObs)
  {
    AXIS.register(n);
  }

/**
 * A custom event to be fired when the AXIS is loaded, initialized... ready
 *
 *  @see AXIS#start
 */
AXIS.onReady = AXIS.CustomEvents.create({
  name: 'onReady'
});

/**
 * This allows the inclusion of executable code immediately in the original script block which
 * includes the AXIS.js.  
 */
var scripts = document.getElementsByTagName('*');
for(var i=0; i < scripts.length; i++) 
  {
    var src = scripts[i].src;
    if(src && src.match(AXIS.minFName)) 
      {
        eval(scripts[i].innerHTML);
        break;
      }
  }
           
/**
 * Replace any vars that have been replaced
 *
 *  @see #initialize
 */
for(var p in $AXIS.__TVAR)
  {
    window[p] = $AXIS.__TVAR[p] || null;
  }   

delete $AXIS.__TVAR;

AXIS.start();
    
})();
              }
          }

        /**
         * Final initialization of the framework is done by the code in
         * __build__.js.  This file will be loaded and executed following
         * successful inclusion of core script group.  
         */
        if(AF.length > 0)
          {
            AXIS.includeScriptGroup(AF,function()
              {
                build();
              });
          }
        else
          {
            build();
          }
      };
      
    /**
     * Load any system css, various initializations. This starts the system.
     *
     *  @see #initialize
     *  @see #_onContentReady
     *  @see #_onWindowLoad
     *  @see #Queue
     */
    this.start  = function()
      {      
        /**
         * fire onReady custom event and indicate that all has loaded correctly.
         */
        this.onReady.fire();
               
        /**
         * Attach base DOMReady event
         */
        this._onContentReady();
        
        /**
         * Set window.onload handling
         */
        this.attachEvent('load',this._onWindowLoad,window);
          
        this.Queue.start();
        
        /**
         * At this point all necessary files have been loaded, and the AXIS 
         * environment is established.
         */
        AXIS._isActivated = true;
      };

    /**
     * Checks if a script has already been loaded.  
     *
     *  @param      {String}      s       A script path
     *  @type       {Boolean}
     *  @see  #_includeScript
     *  @see  #includeScriptGroup
     *  @see  #includeScriptChain
     */
    this.scriptIsRegistered = function(s)
      {
        var ss = s || '';
        for(var k in AXIS._registeredScripts)
          {
            if(AXIS._registeredScripts[k].src == ss)
              {
                return true;  
              }
          } 
        return false;
      };
      
    /**
     * Allows the execution of a single function following the load of a
     * group of scripts.  Unlike #includeScriptChain, this method can load 
     * all scripts asynchronously (faster), with any code dependent on these 
     * scripts executing only following load of entire group.
     *
     *  @param   {Boolean} chain   An array of scripts.
     *  @see #_includeScript
     */
    this.includeScriptGroup = function(group, fc)
      {
        var finalCall = fc || AXIS.F;
        if(group && (group.constructor === Array) && (group.length > 0))
          {
            var grpCount = group.length;
            var grp = function()
              {
                --grpCount;
                if(grpCount < 1)
                  {
                    finalCall();
                  }   
              };
              
            for(var i=0; i < group.length; i++)
              {
                if(group[i].constructor === String)
                  {
                    this._includeScript({
                      src:      group[i],
                      onload:   grp
                    });
                  }
                else
                  {
                    this._includeScript({
                      id:         group[i].id,
                      src:        group[i].src,
                      method:     group[i].method || false,
                      register:   group[i].register || false,
                      onload:     grp
                    });
                  }
              }
          }
        else
          {
            /**
             * No members of group.  It is possible there will still be a finalCall() set.
             * This won't fire via normal operation, above, which relies on the group
             * being non-empty.  So, fire the finalCall() here, if any.
             */  
            finalCall();
          }
      };

    /**
     * Takes passed script array, and chains inclusions, so that
     * script[0] is certain to be loaded prior to script[1]...script[n].
     *
     *  @param   {Boolean} chain   An array of scripts.
     *  @see #_includeScript
     */
    this.includeScriptChain = function(chain)
      {
        if(chain && (chain.constructor === Array) && (chain.length > 0))
          {
            /**
             * Shift off the first script object.  This gives us the current
             * script to load, and the remaining collection.  Get current script's
             * onload (if any), store it, and create a new onload handler that
             * fires stored onload handler (first!), and then simply passes the
             * remaining collection to this script, which forces ordering.
             * Then include current script.
             */
            var d     = chain.shift();
            var z     = d.onload || AXIS.F;
            d.onload = function()
              {
                z();
                AXIS.includeScriptChain(chain);      
              }  
            AXIS._includeScript(d);  
          }
      };
      
    /**
     * Takes script definitions passed, and includes script in page. Public
     * interface to #_includeScript.  Mainly sorts argument types.
     *
     *  @public
     *  @param   {Mixed}   ob      The loading object
     *  @see _includeScript
     */
    this.includeScript  = function(ob)
      {
        if(ob)
          {
            if((ob.constructor === Array) && (ob.length > 0))
              {
                for(var x=0; x < ob.length; x++)
                  {
                    AXIS._includeScript(ob[x]);  
                  }
              }
            else if(ob.constructor === Object)
              {
                AXIS._includeScript(ob);
              }
            else if(ob.constructor === String)
              {
                AXIS._includeScript({
                  src: ob
                  });
              }
          }
      };
    
    /**
     * Includes a script via DOM HEAD insert if the document is loaded
     * or via document.write if not.
     *
     *  @param   {Object} ob - The loading object:
     *                          {
     *                            [id] -- The id for the <script> tag
     *                            src  -- The src value of the <script> tag
     *                            [type] -- Default is 'text/javascript'
     *                            [onload] -- To fire when script loaded
     *                            [method] -- 'write' || 'insert'
     *                          }
     *  @private
     *  @see #contentReady
     *  @throws  Error   AXIS_INCLUDE_SCRIPT_NO_SRC
     *  @throws  Error   AXIS_INCLUDE_SCRIPT_ERROR
     */
    this._includeScript = function(ob)
      {    
        var onloadStr = '';
        try
          {  
            if(!ob.src || ob.src.toString() == "")
              {
                throw new Error('AXIS_INCLUDE_SCRIPT_NO_SRC');  
              }
              
            ob.id = ob.id || AXIS.getUniqueId('script_'); 
            
            /**
             * Get the basename, in case this is an extension that needs registration.
             * ie. 'foo/bar/file.js' > 'file'
             */
            ob.baseName = ob.src.substring(ob.src.lastIndexOf('/')+1, ob.src.lastIndexOf('.'));
            
            /**
             * If before DOM loaded, write directly (assume we are building the head)
             * You can force this behaviour. 
             */
            if(ob.method === 'write' || (AXIS.contentReady() === false))
              {
                if(ob.register)
                  {
                    onloadStr += "AXIS.register('" + ob.baseName + "');";
                  }
                onloadStr +=   'AXIS._registeredScripts["' + ob.id + '"].onload();';
                
                ob.onload       = ob.onload || AXIS.F;
                /**
                 * We store a copy of the call object mainly to store the onload
                 * handler reference (if any) to be called when written script loads.
                 * However, it is useful, and stored on DOM insert as well, below.
                 */
                AXIS._registeredScripts[ob.id] = ob;
    
                document.write('<' + 'script' + ' id="' + ob.id + '" type="text/javascript" src="' + ob.src + '"> </' + 'script' + '>'
                + '<' + 'script' + ' type="text/javascript">' + onloadStr + '</' + 'script' + '>');

                return true;
              }
            
            /**
             * If the DOM is loaded, it is expected that we are loading user scripts.
             * As we don't want the user to bother with too much, we create an onload
             * handler that will autoregister the script if it is in our Class.Class
             * format.
             */
            var hT    = document.getElementsByTagName('head')[0]; 
            var s     = document.createElement('script'); 
            s.id      = ob.id; 
            s.type    = 'text/javascript';
            s.src     = ob.src; 

            if(ob.onload)
              {
        				s.onload = s.onreadystatechange = function()
        				  {
        					  if(!this.__loaded__  && 
        					    (!this.readyState || this.readyState == "loaded" || this.readyState == "complete")) 
        					    {
        					      this.__loaded__ = true;
        					      AXIS._registeredScripts[ob.id] = ob;
        					      
        					      /**
        					       * If this is an extension, and immediate registration is requested,
        					       * do that here, prior to firing onload.  
        					       */
        					      if(ob.register)
        					        {
        					          AXIS.register(ob.baseName);
        					        }
        					      
        						    ob.onload();
        					    }
        				  };
              } 
              
            hT.appendChild(s);
            AXIS._registeredScripts[ob.id] = ob;
          }
        catch(e)
          {
            AXIS.Errors.catchError('AXIS_INCLUDE_SCRIPT_ERROR',e);
            return false;
          }
      };
    
    /**
     * Include CSS file (.css) in <head> of document.
     *
     *  @param   {String}  ob - The include object
     *                          {
     *                            [id] -- The id for the <css> tag
     *                            href  -- The src value of the <css> tag
     *                            [media] -- Default is 'screen'
     *                          }
     *  @throws  Error   AXIS_INCLUDE_CSS_NO_HREF
     *  @throws  Error   AXIS_INCLUDE_CSS_ERROR
     */
    this.includeCSS = function(ob)
      {
        try
          {
            if(ob && ob.href.toString() == "")
              {
                throw new Error('AXIS_INCLUDE_CSS_NO_HREF');  
              }
              
            var hT    = document.getElementsByTagName('head')[0]; 
            var css   = document.createElement('link'); 
            css.id    = ob.id || AXIS.getUniqueId('css_'); 
            css.rel   = 'stylesheet'; 
            css.type  = 'text/css';
            css.href  = ob.href; 
            css.media = ob.media || 'screen';

            hT.appendChild(css);
          }
        catch(e)
          {
            AXIS.Errors.catchError('AXIS_INCLUDE_CSS_ERROR',e);
            return false;
          }
      };

    /**
     * Management class for the Queue
     */

    this.Queue  =
      {   
        /**
         * This is the array which contains the queue.
         *
         *  @private
         */  
        _queue:   [],
        
        /**
         * This is the reference to the timer which runs the queue.
         *
         *  @see  #start
         */
        _timer:   null,
    
        /**
         * Adds an object to the Queue.  These objects must contain a .main()
         * method, which is called on each run of the queue.  If .main returns
         * true, it will be kept on and continue to run; returning false (or 
         * returning nothing) results in the object being popped off the queue.
         * Note the special attributes attached (and updated on each execution)
         * to the object.  These are accessed within your .main function via 
         * `this` operator. They are:
         * __TIMESTART__    : The time that the object was attached (timestamp).
         * __TIMECURRENT__  : The current time at execution.
         * __TIMELAST__     : The time of immediately previous execution.
         * __ITERATIONS__   : The number of times that the routine has run, inclusive.
         * __LASTXINDEX__   : The position of object in queue at current execution (zero(0)-base).
         *
         *  @see AXIS#fadeTo.
         *  @return  The modified and stacked object
         *  @type    Object
         */
        add: function(obj)
          {
            var ob = obj || {};
            var d                 = new Date();
            var gt                = d.getTime();
            ob.__TIMESTART__      = gt;
            ob.__TIMECURRENT__    = gt;
            ob.__TIMELAST__       = gt;
            ob.__ITERATIONS__     = 0;
            ob.__LASTXINDEX__     = null;
            ob.lifespan           = obj.lifespan || Math.pow(10,10);
            ob.maxIterations      = ob.maxIterations || Math.pow(10,10);
            ob.once               = ob.once || false;
            ob.main               = ob.main || function() { return false; };
            
            /**
             * This is what to call should you want to terminate a process.
             */
            ob.die                = function()
              {
                AXIS.Queue.remove(this);
              };

            /**
             * Execute the main method immediately, and if it is to remain
             * on the queue, add it.
             */
            if(ob.main() && (ob.once === false))
              {
                AXIS.Queue._queue.unshift(ob);
              }
              
            return ob;
          },

        /**
         * This is the queue walker.  Runs the .main of each object in queue,
         * handles the detachment (or not) and updates the internal special vars.
         *
         *  @see #Queue#start
         */
        walk: function()
          {  
            var q = AXIS.Queue._queue;
            var c = q.length;
            var d = new Date();
            var qc;
            /**
             * Going in reverse allows the loss of members during
             * iteration, and is a faster executing loop besides.
             */       
            while(c--)
              {
                qc = q[c];
                qc.__ITERATIONS__++;
                qc.__TIMELAST__     = qc.__TIMECURRENT__;
                qc.__TIMECURRENT__  = d.getTime();     
                qc.__TIMETOTAL__    = qc.__TIMECURRENT__ - qc.__TIMESTART__;
                qc.__LASTXINDEX__   = c;

                if(     (qc.lifespan < qc.__TIMETOTAL__) 
                    ||  (qc.maxIterations < qc.__ITERATIONS__))
                  {
                    qc.onBeforeDie && qc.onBeforeDie(qc);
                    qc.die();  
                  }            
                qc.main() || q.splice(c,1);
              }
          },
          
        /**
         * Used to check whether a particular object exists in the Queue
         * 
         *  @param   {Object}  A Queue object reference
         *  @type  Boolean
         */
        exists: function(r)
          {
            var i = this._queue.length;
            while(i--)
              {
                if(this._queue[i] === r)
                  {
                    return true;  
                  }  
              }
            return false;
          },

        /**
         * Allows the killing of any objects with the given property/value.
         * Note that this will kill ALL objects which satisfy
         * the property/value condition!
         *
         *  @param   {String}  p   The property name.
         *  @param   {Mixed}   v   The property value.
         *
         *  @return  The # killed, or false if none.
         *  @type  Mixed
         */
        killByPropertyValue: function(p,v)
          {
            var q = this._queue;
            var i = q.length;
            var h = 0;

            while(i--)
              {
                if(q[i][p] && q[i][p] === v)
                  {
                    q[i].die();
                    ++h;
                  }  
              }
              
            return (h>0) ? h : false;
          },
         
        /**
         * Removes sent object instance, if any
         *
         *  @param      {Object}    r     A Queue object
         *  @type       {Boolean}
         */
        remove: function(r)
          {
            var i = this._queue.length;
            while(i--)
              {
                if(this._queue[i] === r)
                  {
                    /**
                     * We mark for cleanup. Note that this assignment breaks all 
                     * references to the related Queue instance.
                     */
                    this._queue[i] = {main:function(){ return false; }};  
                    
                    return true;
                  }  
              }
            return false;
          },
        
        /**
         * Destroys all objects in the Queue
         */
        clear: function()
          {
            var i = this._queue.length;
            while(i--)
              {
                this.remove(this._queue[i]);  
              }
          },
          
        start: function()
          {
            AXIS.Queue._timer = setInterval(AXIS.Queue.walk,20);
          },
          
        stop: function()
          {
            clearInterval(AXIS.Queue._timer);
          }
      };
      
    /**
     * Regular Expressions library.  Used variously.     
     *
     */
    this.Regexes  =
      {
        text:         /^[\S\ ]$/,
        varchar:      /^[\S\ ]{0,1000}$/,
        datetime:     /^([0-9]{2,4})-([0-1][0-9])-([0-3][0-9]) (?:([0-2][0-9]):([0-5][0-9]):([0-5][0-9]))?$/,
        date:         /^([0-9]{2,4})-([0-1][0-9])-([0-3][0-9])$/,
        time:         /^([0-2][0-3]):([0-5][0-9]):([0-5][0-9])$/,
        integer:      /^\d+$/,
        year:         /^[+-]?\d+$/,
        email:        /^([\w_\-\.]+)@((\[[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.)|(([\w\-]+\.)+))([a-zA-Z]{2,4}|[\d]{1,3})(\]?)$/,
        zip:            /^[\d]{5}$/,
        zip4:           /^[\d]{5}-[\d]{4}$/,
        DAVBASEField:   /^\S{1,100}$/,
        
        allDigits:      /^\d+$/,
        allAlpha:       /^[a-zA-Z]+$/,
        username:       /^[A-Za-z-_!:~\w]{5,63}$/,
        password:       /^[A-Za-z-_!:~\w]{5,255}$/
      };
      
    /**
     * CUSTOM API FUNCTIONS                                  
     * These are the sorts of functions that will likely be     
     * included in 3rd party javascript libraries, but are      
     * necessary for the operation of the AXIS. 
     */
     
    /**
     * An accurate way of checking whether a given value is an Array.
     *
     *  @param    {Mixed}     a     The value to check
     *  @type     {Boolean}
     */
    this.isArray  = function(a)
      {
        return (a.constructor === Array) || (Object.prototype.toString.apply(a) === '[object Array]');
      };
      
    /**
     * A general-purpose function to enable a function to use memoization
     * func: the function to be memoized
     * context: the context for the memoized function to execute within
     * Note: the function must use explicit, primitive parameters (or objects
     * that generate unique strings in a toString() method).
     * From code found at: http://unscriptable.com/index.php/2009/05/01/a-better-javascript-memoizer/ 
     */
    this.memoize  = function(func, context) 
      {
        function memoizeArg (argPos) 
          {
            var cache = {};
            return function() 
              {
                if(argPos == 0) 
                  {
                    if(!(arguments[argPos] in cache)) 
                      {
                        cache[arguments[argPos]] = func.apply(context, arguments);
                      }
                    return cache[arguments[argPos]];
                  }
                else 
                  {
                    if(!(arguments[argPos] in cache)) 
                      {
                        cache[arguments[argPos]] = memoizeArg(argPos - 1);
                      }
                    return cache[arguments[argPos]].apply(this, arguments);
                  }
              }
          }
        // JScript doesn't grok the arity property, but uses length instead
        var arity = func.arity || func.length;
        return memoizeArg(arity - 1);
      }
    
    /** 
     * document.getElementById() shortcut. 
     *  @param     {String}    id    An id attribute of a document element
     *  @returns                     Always returns an Array, even if nothing found.
     *  @type      {Array}
     */
    this.find     = function(id)
      {
        if(id && AXIS.contentReady())
          {
            return document.getElementById(id) || false;  
          }  
        return false;
      };
            
    /**
     * A general event attaching script.  
     *
     *  @param   {String}    ev    The event name -- NOTE: sans 'on': `onclick` == `click`
     *  @param   {Function}  f     A function to attach as event handler
     *  @param   {Object}    ob    An element to attach to. Default to `document`
     */
    this.attachEvent  = function(ev,f,ob)
      {
        var a = ob || document;
        if(AXIS.isIE) 
          {
            a.attachEvent('on' + ev, f);
          } 
        else 
          {
            a.addEventListener(ev, f, false);
          }	 
      };
      
    /**
     * A general event detaching script.  
     *
     *  @param   {String}    ev    The event name -- NOTE: sans 'on': `onclick` == `click`
     *  @param   {Function}  f     A function to remove as event handler
     *  @param   {Object}    ob    The element to detach from. Default to `document`
     */
    this.detachEvent  = function(ev,f,ob)
      {
        var a = ob || document;
        if(AXIS.isIE) 
          {
            a.detachEvent('on' + ev, f);
          }
        else 
          {
            a.removeEventListener(ev, f, false);
          }	 
      };
          
    /**
     * Curry a function.
     *
     *  @param    {Function}    fnc     The function to curry.
     *  @param    {Object}      [scp]   The scope to execute in. Defaults to window.
     *
     *  @return   The curried function, or null function on error.
     *  @type     {Function} 
     */
    this.curry  = function(fnc, scp)
      {
        if(fnc)
          {
            var _scp = scp || window;
            var args = [].slice.call(arguments,2)
            return function() 
              {
                return fnc.apply(_scp, args.concat([].slice.call(arguments,0)));
              };  
          }
        else
          {
            return AXIS.F;  
          }
      };
    
    /**
     * Changes the opacity of an element over time.
     *
     *  @param   {Object}  ob   Object in this form:
     *                          element -- Either an object reference, or element id.
     *                          [startOpacity]  The opacity to set the object to
     *                          [endOpacity]  The opacity to be achieved
     *                          [step]  Ms between each opacity change
     *                          [startDelay] Ms prior to beginning of fade
     *                          [deleteOnEnd] Whether to remove the object from DOM
     *                                        collection when endOpacity is reached.    
     */
    this.fadeTo = function(ob) 
      {
        var el                = (typeof ob.element == 'object') 
                              ? ob.element : document.getElementById(ob.element);
        var startOpacity      = ob.startOpacity || 100;
        var endOpacity        = ob.endOpacity || 0;
        var step              = ob.step || -AXIS.notificationFadeSpeed;
        var startDelay        = ob.startDelay || AXIS._notificationDelay;
        var deleteOnEnd       = (ob.deleteOnEnd === undefined) ? true : !!ob.deleteOnEnd;
        var onComplete        = ob.onComplete || AXIS.F;
        
        var doOpacity = function(newop)
         {
           el.style.opacity = newop/100;
           el.style.filter = 'alpha(opacity=' + newop + ')';
         };
        doOpacity(startOpacity);
        
        return AXIS.Queue.add({
          _forceFade:   false,
          forceFade:    function()
            {
              /**
               * Forcing a fade.  As there will normally be a start delay,
               * we need to eliminate that, starting as if the fade
               * is starting right now.  Set start time to now and delay to zero.
               */
              var d                 = new Date();
              this.__TIMESTART__    = d.getTime();
              startDelay            = 0;
              this._forceFade       = true;
            },
          main: function()
            { 
              var elapsed = this.__TIMECURRENT__ - this.__TIMESTART__;
                
              if(this._forceFade || (elapsed > startDelay))
                {
                  var dec   = parseInt((elapsed - startDelay) / step);
                  doOpacity(startOpacity + dec);

                  if(Math.abs(dec) >= Math.abs(startOpacity - endOpacity))
                    {
                      if(deleteOnEnd === true) 
                        {
                          el.parentNode.removeChild(el);
                        }
                      
                      onComplete();
                      
                      return false;  
                    }
                }
              return true;
            }
         });
      };
  };

/**
 * Build custom library creation API into AXIS prototype.  This means that the 
 * functions .scope, .extend, etc. are available in any AXIS location (main, registered object,
 * etc) via this. -- although it is recommended that you maintain clarity by declaring 
 * AXIS.scope, AXIS.extend... same amount of characters, less room for confusion.
 */
$AXIS.prototype = new function()
  {    
    this.$$   = new Array(10);

    this.scope = function(sc)
      {
        this.$ = sc || [];
        return this;
      };
    
    this.store = function(nm,asA)
      {
        if(nm)
          {
            var vn = '$' + nm;
            if(this[vn] && AXIS.isArray(this[vn]))
              {
                this[vn].push(this.$);  
              }
            else
              {
                var a = this.$$.shift();
                this.$$.push(nm);
                a && delete this['$' + a]; 
                this[vn] = asA ? [this.$] : this.$;
              }
          }
        return this;  
      };
            
    this.unstore = function(nm)
      {
        delete this['$' + nm];
        return this;
      };
      
    this.setStoreLength = function(len)
      {
        this.$$.length = len || this.$$.length;
        return this;
      };
  
    this.extend = function(nm,func)
      {
        if(!nm || !func || (nm.constructor !== String) || (func.constructor !== Function))
          {
            AXIS.Errors.catchError(new Error('LIBRARY_BAD_ARGS'));
            return false;
          }
          
        if(this[nm])
          {
            AXIS.Errors.catchError(new Error('LIBRARY_METHOD_EXISTS~~' + nm)); 
            return false;  
          }
  
        this[nm] = function()
          {        
            this.$ = func.apply(this.$,[].slice.apply(arguments,[0])); 
            return this;
          };
      };
  };


/**
 * Create AXIS object, and initialize.
 * NOTE: $AXIS is the object base name for any new namespaces.  As such, it is highly
 * unlikely that $AXIS will remain defined as a constructor.  We reuse the name, in 
 * other words.
 *
 *  @see AXIS#createNamespace
 */
var AXIS  = new $AXIS;
/**
 * Now we re-use $AXIS var, as a namespace container.
 *  @see AXIS#createNamespace
 */
$AXIS = {};

AXIS.initialize();


