/**
 * 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.onDOMReady.subscribe({
          callback: 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.makeFolder = function(path, counter, options){
          options = options ? options : {};
          counter = counter ? counter - 1 : 1; // default to once...
          if (counter <= 0) {
              return; // dont go into an infinite loop...
          }
          
          AXIS.WebDAV.MKCOL({
              url: path,
              async: options.async,
              on201: function(r){
                  if( options.callback )
                      options.callback();
              },
              on409: function(){
                  var words = path.split('/');
                  words.pop();
                  AXIS.Util.makeFolder(words.join('/'), counter, options);
              }
          });
      };

    this.bit =
      {
        /** 
          * Duplicates a bit: Copies it to the current folder giving it a new name
          * args : @bitPath: Path to the bit
          *       @destFolder : folder where the bit has to be copied
          *       @options : 
          *         - success : success callback
          *         - failure : failure callback
          *         - scope : scope for the callbacks
          * TODO : include scope / failure / success callbacks
          */
        copy: function(bitPath, destFolder, options)
          {
            if(!bitPath || !destFolder) {
                return;
            }
            
            options = options || {};
            
            if(bitPath.charAt(bitPath.length - 1) == '/') {
              bitPath = bitPath.substring(0, bitPath.length - 1);
            }

            AXIS.WebDAV.COPY({
              url: bitPath,
              destination: destFolder,
              tryAlternateNames: true,
              on201: function(r) {
                if(options.success && ((typeof(options.success)).toLowerCase() == "function")) {
                  options.success.call(options.scope, r);
                }
              }
            });
            
          }, 
          
        getBitFromUserDomain: function(bitUrl)
          {
            AXIS.Cookies.create(bitUrl, "copy", { path: '/!lime/root/apps/bar/' });
            location = AXIS._siteData.hosts.limebits + 'apps/copier/#bitUrl=' + encodeURIComponent(bitUrl) +
              '&requestor=' + encodeURIComponent('http://'+location.host+'/');
          },

        getUUID: function(url, callback)
          {
            AXIS.WebDAV.getProperty({
             url: url,
             properties: ['resource-id'],
             async: true,
             onSuccess: function(r) {
               var resource_id;
               try {
                 resource_id = r.responseXMLObject().multistatus.response.propstat.prop["resource-id"].href.replace(/urn:uuid:/, '').replace(/-/g, '');
               }
               catch(e) {
                   resource_id = null;
               }

               callback(resource_id);
             }
            });
          },
           

        /* copy bitmarks,

           options.src_uuid: Set to uuid of source bit
           options.url: Set to url of source bit 
           only one of src_uuid & url is required, src_uuid is preferred

           options.destination: Set to url of destination bit
           options.dst_uuid: Set to uuid of destination bit
           only one of src_uuid & url is required, src_uuid is preferred
        */
        copyBitmarks: function(options)
          {
            if (!options.src_uuid) {
                AXIS.Util.bit.getUUID(options.url, function(uuid) {
                  if (!uuid) {
                    return options.callback();
                  }
                  options.src_uuid = uuid;
                  AXIS.Util.bit.copyBitmarks(options);
                });
            }
            else if (!options.dst_uuid) {
                AXIS.Util.bit.getUUID(options.destination, function(uuid) {
                  if (!uuid) {
                    return options.callback();
                  }
                  options.dst_uuid = uuid;
                  AXIS.Util.bit.copyBitmarks(options);
                });
            }
            else {
                options.url = '/bitmarks/' + options.src_uuid;
                options.destination = '/bitmarks/' + options.dst_uuid;
                AXIS.WebDAV.COPY(options);
            }
          },

        getBit: function(bitPath, location_replace, target_url)
          {
                AXIS.Bits.Utils.getBit(bitPath, location_replace, target_url);
          },

        /*
           
         */
        bitInfo: function(bitUrl)
          {
            var bit = {};
            
            if (bitUrl.indexOf('/home') === 0) {
                bit.tld_path = bitUrl;
                bit.url = AXIS.Util.uri.getBitUrl(bitUrl);
            }
            else {
                bit.url = bitUrl;
                bit.tld_path = AXIS.Util.uri.getTLDPath(bit.url);
            }
            bit.tld_folder = bit.tld_path.match(/.*\//)[0];
            bit.filename = bit.tld_path.replace(bit.tld_folder, "");

            bit.owner = bit.tld_path.match(/\/home\/([^\/]*)/)[1];

            if (bit.tld_path.match(/\/home\/[^\/]*\/bits\/[^\/]*/))
              {     /* tld path is of the pattern /home/user/bits/bitname/*** */
                bit.tld_root = bit.tld_path.match(/(\/home\/[^\/]*\/bits\/[^\/]*)/)[1];
              }
            else
              {
                bit.tld_root = AXIS.Util.uri.chompSlash(bit.tld_folder);
              }
            bit.tld_path_from_root = bit.tld_path.replace(bit.tld_root, "");
            bit.root_url = AXIS.Util.uri.getBitUrl(bit.tld_root);

            bit.name = bit.tld_root.replace( /.*\//, "" ); // get the folder basename

            return bit;

          },

        getBitmarks: function(bitFolder, cb)
          {
            var dav = AXIS.WebDAV;
            var marks = {
              tags: []
            };

            dav.SEARCH({
              async: true,
              url: bitFolder,
              props: ["displayname", "resource-id", "lastmodified", "popularity"],
              bitmarks: {
                ns: AXIS.WebDAV.ns.bm,
                names: ["tag", "description"]
              },
              depth: "0",
              callback: function(r) {
                var resp = r.responseXMLObject().multistatus.response;

                var pstat = resp.propstat;
                var bstat = resp.bitmarkstat || [];

                pstat = AXIS.isArray(pstat) ? pstat : [pstat];
                bstat = AXIS.isArray(bstat) ? bstat : [bstat];

                for(var j = 0; j < pstat.length; j++)
                  {
                    if(pstat[j].status.match(/200 OK/))
                      {
                        AXIS.Util.lang.augmentObject(marks, pstat[j].prop);
                      }
                  }

                for(var k = 0; k < bstat.length; k++)
                  {
                    if(bstat[k].status.match(/200 OK/))
                      {
                        var bitmark = bstat[k].bitmark;
                        bitmark = AXIS.isArray(bitmark) ? bitmark : [bitmark];

                        for(var l = 0; l < bitmark.length; l++)
                          {
                            if (bitmark[l].tag)
                              {
                                marks.tags.push(bitmark[l].tag);
                              }
                            else
                              {
                                AXIS.Util.lang.augmentObject(marks, bitmark[l]);
                              }
                          }
                      }
                  }
                cb.call(null, marks)                                
              }
            });
          },


        /**
         * Opens the configure tab for a bit.
         * Arguments :: @bitPath: Top-level-domain Path to the bit
         * Currently this is done by setting a cookie where :
         *  cookie-name : Absolute URL of the bit
         *  cookie-value : "new_copy"
         */
        openConfigureTab: function( bitPath )
          {
            if(!bitPath)
              return;

            var bit = AXIS.Util.bit.bitInfo(AXIS.Util.uri.getBitUrl(bitPath));

            // Create the cookie
            AXIS.Cookies.create(bit.tld_root, "openConfigure", { path: '/apps/' });
            window.location.assign(bit.url + "?bar");
          }

      };
      
    this.uri = 
      {
        basename: function(path)
          {
            return typeof(path) == 'string' ? path.replace(/.*\//, "") : path; // return base... or if invalid send back as is
          },

        /* 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(/\/[^\/]*\/?$/, '/');
        },

        /**
         * Normalizes the url by removing redundant slashes (and trailing slashes) and decodes %20
         *
         * @param    {String} url    The url to normalize
         * @returns                  The normalized url
         * @type     {String}
         */
        normalize: function(url, bChomp) {
          url = url.replace(/\/\/*/g, "/"); // replace multiple consecutive slashes with one slash
          url = url.replace(/http:\/(?=[^\/])/,"http://"); // in case we replaced http:// with http:/ 
          url = url.replace(/%20/g, " ");   // decode %20
          if( bChomp )
          {
              url = AXIS.Util.uri.chompSlash(url);
          }
          return url;  // remove any trailing slashes and return
        },

        // returns base+url if <url> doesnt start with http:// or /        
        absolutize: function(url, base)
        {
            return (!url.match(/^http:\/\//) && !url.match(/^\//)) ? AXIS.Bits.Utils.domainize(AXIS.Util.uri.addTrailingSlash(base) + url) : url;   
        },
        
        /**
         * Removes trailing slashes from the url if present
         *
         * @param    {String} url    The url
         * @returns                  The url without any trailing slashes
         * @type     {String}
         */
        chompSlash: function(url) {
          return url.replace(/[\/\\]*$/, '');
        },
        addTrailingSlash: function(url) {
            return (url.charAt(url.length-1) !== '/' ? url + '/' : url);
        },
        
        getBitUrl: function(tld_path) {
          var bit_info = tld_path.match(/home\/([^\/]+)\/bits\/([^\/]+)(.*)/);
          var bit_url;

          if (bit_info && bit_info[2].match(/^[-a-z0-9]*$/))
            {
              bit_url = AXIS._siteData.hosts.limebits.replace("www", bit_info[2] + "." + bit_info[1]) + bit_info[3].substr(1);
            }
          else
            {
              bit_url = this.getUserUrl(tld_path);
            }

            return bit_url;
        },

        /**
         * Converts the www domain path to a FQDN url for accessing the path without redirects
         *
         * @param    {String} tld_path    The www domain path of the folder 
         * @returns                       The FQDN for accessing that path directly
         * @type     {String}
         */
        getUserUrl: function(tld_path) {
          var user_url, user_info;
          url_info = tld_path.match(/home\/([^\/]+)(.*)/);
          if (url_info)
            user_url = AXIS._siteData.hosts.limebits.replace("www", url_info[1]) + url_info[2].substr(1);
          else
            user_url = AXIS._siteData.hosts.limebits + tld_path.substr(1);
          return user_url;
        },

        /**
         * Converts the FQDN url to a www domain path
         *
         * @param    {String} user_url    The FQDN for accessing that path directly
         * @returns                       The www domain path of the folder
         * @type     {String}
         */
        getTLDPath: function(user_url) {
          var bitParts = user_url.match(/http:\/\/([^\/]+)(\/[^#]*)/);
          var hostname_pref = bitParts[1].replace(AXIS._siteData.hosts.limebits.match(/www(\.[^\/]*)/)[1], "");
          var bitOwner;
          var bitPath = bitParts[2];
          if (hostname_pref.split(".").length == 2)
            {
              bitOwner = hostname_pref.split(".")[1];
              return '/home/' + bitOwner + '/bits/' + hostname_pref.split(".")[0] + bitPath;
            }
          else
            {
              bitOwner = hostname_pref;
              return (bitOwner == 'www') ? bitPath: '/home/' + bitOwner + bitPath;
            }
          },
            
            // Navigate to <url>
            // bReplace (false by default) - Whether to replace current history
          navigateTo: function(url, bReplace)
          {
              var locationUpdate = function(){
                  if (bReplace) {
                      window.location.replace(url);
                  }
                  else {
                      window.location.href = url;
                  }
              };
              if (window.location.protocol == 'http:' && url.indexOf('https') > -1 && AXIS.isMoz) {
                  setTimeout(function(){ // a workaround till firefox fixes bug that lets the analytics tracker fire even across pages
                      locationUpdate();
                  }, 100);
              }
              else {
                  locationUpdate();
              }
          },

        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];
            }
        },
        
        // Returns the object containing properties in <r>, that are not present in <s>
        diff: function(r, s){
            var result = {};
            for( var p in r )
            {
                if( !s[p] )
                {
                    result[p] = r[p];
                }
            }
            return result;
        },
        
        // Returns the object containing properties whose value is different
        diffByValue : function(r,s){
            var result = {};
            for( var p in r )
            {
                if( s[p] && r[p] !== s[p] )
                {
                    result[p] = s[p];
                }
            }
            return result;
        },
        
        equals: function(r,s) {
            if (typeof(r.length) != 'undefined' ) {
                if( typeof(s.length) == 'undefined' || r.length != s.length )
                {
                    return false;
                }
                for(var i=0; i<r.length; i++)
                {
                      if( !AXIS.Util.lang.equals(r[i],s[i]) )
                      {
                          return false;
                      }  
                }
            }
            else {
                for (p in r) {
                    if (typeof(s[p]) == 'undefined') {
                        return false;
                    }
                }
                
                for (p in r) {
                    if (r[p] != s[p]) {
                        return false;
                    }
                }
                
                for (p in s) {
                    if (typeof(r[p]) == 'undefined') {
                        return false;
                    }
                }
            }
            return true;
        },
        
        isEmpty : function(ob){
            for (var i in ob) {
                return false;
            }
            return true;
        },

        /**
         * Checks if string is present in array of strings
         * @param {string/regex object} needle
         * @param {Array of string/regex} haystack
         * @return boolean
         */
        inArray: function(needle, haystack){
            if (!AXIS.isArray(haystack)) {
                return false;
            }
            for (var i = 0; i < haystack.length; i++) {
                if( needle instanceof RegExp && haystack[i].match(needle) )
                {
                    return true;
                }
                else if( haystack[i] instanceof RegExp && needle.match(haystack[i]) )
                {
                    return true;
                }
                else if (haystack[i] == needle) { // whatever type they are...
                    return true;
                }
            }
            return false;
        },
        
        indexOfArray: function(value, arr)
        {
            if (arr.length) {
                for (var i = 0; i < arr.length; i++) {
                    if (arr[i] === value) 
                        return i;
                }
            }
            return -1;
        },
        
        /**
         * Utility to trim strings
         */
        trim: function(str){
            if (!str) {
                return "";
            }
            
            str = str.replace(/^\s+|\s+$/g, "");
            return str;
        },

        /**
         * Capitalize first letter of string
         * @param {Object} string
         */        
        capitalizeFirst: function(string){
           return string.charAt(0).toUpperCase() + string.slice(1);
        },

        /**
         * http://yelotofu.com/2008/08/jquery-shuffle-plugin/
         * @param {Object} arr
         */        
        shuffle: function(arr){
            if (arr && arr.length > 1) {
                for (var j, x, i = arr.length; i; j = parseInt(Math.random() * i), x = arr[--i], arr[i] = arr[j], arr[j] = x) 
                    ;
            }
            return arr;
        },
        
        objLength: function(obj) {
          var result = 0;
          for( var item in obj)
          {
            result++;
          }
          return result;
        }
      }

    this.json =
      {
        eval: function(text)
          {
            var json_object = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(text.replace(/"(\\.|[^"\\])*"/g, ''))) && eval('(' + text + ')');
            /*")*/
            return json_object;
          },

        safeEval: function(text)
          {
            if( typeof(text) != 'string' )
            {
                return text; // cant do nothin' if it aint a string
            }
            /* prefer browser supported JSON.parse if available */
            if (JSON && typeof(JSON.parse) == 'function') {
                var r;
                try {
                    r = JSON.parse(text);
                }
                catch(e) {
                    r = {};
                }

                return r;
            };

            /* comes from JSON.parse at http://json.org/json2.js (public domain). */
            var j = {};

            var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
            cx.lastIndex = 0;
            if (cx.test(text))
               {
                 text = text.replace(cx, function (a) {
                   return '\\u' +
                     ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
                 });
               }

            if (/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').replace(/(?:^|:|,)(?:\s*\[)+/g, '')))
              j = eval('(' + text + ')');

             return j;
          }
      }
    /**
     * @param
     *  options: Array of option declarations
     *  dat: Current data for these declarations
     */
    this.dictionary = function(options, dat, version) {

      var ops = jQuery.map(options, function(o) { return jQuery.extend(true, {}, o); });

      // Add #change, #rollback, and #value to each Option object.
      for(var w=0; w < ops.length; w++) {
        // set missing options to default in dat
        var k = ops[w].key;
        var d = typeof(ops[w]["default"]) == 'function' ? ops[w]["default"]() : ops[w]["default"];
        if (k && typeof(dat[k]) == "undefined" && typeof(d) != "undefined") {
            if (typeof(d) == "object" ) {
                if( d.slice ) // this is an array
                {
                    dat[k] = d.slice();
                }
                else
                {
                    dat[k] = jQuery.extend(true, {}, d);    
                }
            }
            else {
                dat[k] = d;
            }
        }

        ops[w].change = function(newval) {
          
          // If there is a validate function, validate first.
          var v = this.validate ? this.validate(newval) : true;
          
          // If valid...
          if(v) {        
            // Here is where we *could* automatically change the values in the model...
            // Instead we only change model when committing.
            this.__newValue = newval;
          }
          
          // Notify of validation result. Interface has option to warn immediately,
          // or wait for #commit.
          return v;
        };
        
        // Remove #__newValue, which returns primacy to original value.
        ops[w].rollback = function() {
          delete this.__newValue;  
        };
        
        // Allows the fetching of individual option value.  Returns either the #__newValue, if
        // any, and if not, the original value.
        ops[w].value = function() {
            if (typeof(this.get) == 'function') {
                var getterVal = this.get(dat[this.key]);
                if( typeof(getterVal) == 'undefined' )
                {
                    getterVal = dat[this.key];
                }
                return getterVal;
            }
            else {
                return typeof(this.__newValue) != 'undefined' ? this.__newValue : dat[this.key];
            }
        };
        
        // Returns true if current value is the same as default
        ops[w].isValueDefault = function() {
            var value = this.value();
            if( typeof(value) == 'string' && value == this.getAttributeAsString('default') )
            {
                return true;
            }
            else if( typeof(value) == 'object' && AXIS.Util.lang.equals(value, this.getAttributeAsString('default')) )
            {
                return true;
            }
            else {
                return false;
            }
        };
        
        // Get option attribute
        // Returns blank string if not present
        ops[w].getAttributeAsString = function(name, defaultReturn) {
            defaultReturn = typeof(defaultReturn) == 'undefined' ? '' :  defaultReturn;
            var optVal = typeof(this[name]) == 'function' ?  this[name]() : this[name];
            if( typeof(optVal) == 'undefined' )
            {
                optVal = '';
            } 
            
            return  optVal; 
        };
     }
     
     // Rollback all changes within dictonary
     this.rollback = function() {
         for (var a = 0; a < ops.length; a++) {
             ops[a].rollback();
         } 
     };   

      
      // Global commit.  This will go through all #ops, call #validate, and
      // if all pass, saves the collected values, and calls #onCommit of all Options.
      // returns true if there was a change to commit, false otherwise
      this.commit = function(url, callback) {
        
        var op, a;
        var changedFields = [];
        
        for(a=0; a < ops.length; a++) {
          
          // If there is a #__newValue, then commit. If no new value, ignore.
          if(AXIS.isUndefined(ops[a].__newValue) === false) {
            
            // Update data with new value. This changes the JSON that will be sent.
            dat[ops[a].key] = ops[a].__newValue;
            
            // Store all the changed fields, which we can send back to the user.
            changedFields.push(ops[a]);

            // We're committing, so lose new value.
            delete ops[a].__newValue;
            
            if (ops[a].onCommit && typeof(ops[a].onCommit) == 'function') {
                ops[a].onCommit();
            }
            if (typeof(ops[a].set) == 'function') {
                ops[a].set(dat[ops[a].key]);
            }
          }
        }
        
        // Now write the changed data, if any.
        if (changedFields.length > 0 && url) {
            // separate out the options that have their own setter
            var optionsToSave = dat;
            for(var i=0; i<ops.length; i++)
            {
                if( typeof(ops[i].set) == 'function' )
                {
                    delete optionsToSave[ops[i].key];
                }
            }
            
            
            if (!AXIS.Util.lang.isEmpty(optionsToSave) && url) {
                AXIS.Util.saveData(url, jQuery.extend(optionsToSave, { version: version }), {
                    callback: callback,
                    async: true
                });
            }
            else 
                if (callback) {
                    callback();
                }
        }
        else // trigger callback if there's no change
            if (callback) {
                callback();
            }
         
         return changedFields.length ? true : false;
      };
      
      // Updates current options from options.url or options.data
      this.update = function(options) {
            var self = this;
            if( options.data ) // use this instead of url
            {
                for (var name in options.data) {
                    dat[name] = options.data[name];
                }
                if( options.callback )
                    options.callback();
            }
            else if( options.url ) {
                AXIS.WebDAV.GET({
                    url: options.url,
                    async: typeof(options.async) != 'undefined' ? options.async: true,
                    onSuccess: function(r){
                        var newOpts = AXIS.Util.json.safeEval(r.responseText);
                        for (var name in newOpts) {
                            dat[name] = newOpts[name];
                        }
                        if (options.callback) {
                            options.callback();
                        }
                    },
                    onFailure: function(r){
                        if (options.callback) {
                            options.callback();
                        }
                    }
                });
            }
      };
      
      // Allows the fetching of individual option objects.
      this.option = function(k) {
        for(var x=0; x < ops.length; x++) {
          if(ops[x].key === k) {
            return ops[x]; 
          }
        }
        return null;
      };
      
      // Allows fetching of options
      // @param bReturnHash - Return as simple name value hash
      this.all = function(bReturnHash) {
          if (bReturnHash) {
              var toReturn = {};
              for( var x=0; x<ops.length; x++)
              {
                  toReturn[ops[x].key] = ops[x].value();
              }
              return toReturn;
          }
          else {
              return ops;
          } 
      };
    };
    
	this.escapeEntities = function(text) {

		if(!text) {
			return "";
		}
	
		text = text.replace(/&/g, "&amp;");
		text = text.replace(/</g, "&lt;");
		text = text.replace(/>/g, "&gt;");
		text = text.replace(/"/g, "&quot;");
	
	    return text;
	};
    
    this.bar = {
        create: function() {
            document.write("<iframe id='_limebar' src='"+AXIS.Util.bar.getBarLocation() +"#"+window.location.href+"&_limebar' style='position: absolute; border: none; visibility:hidden; height: 45px; top: 0px; left: 0px; width: 100%' onload='this.style.visibility = \"visible\";'></iframe>");
        },
        getBarLocation: function() {
            return AXIS._siteData.hosts.limebits + "apps/bar/index.html";
        }
    };
    
    this.random_num = function(from, to){
        from = from ? from : 0;
        to = to ? to : 9999999;
        return from + Math.floor(Math.random() * (from + to - 1));
    };
    
    // Create script tag containing <scriptContent>
    this.attachNewScript = function(scriptContent, targ, attributes) {
        targ = targ || document;
        attributes = attributes || {};
        
      var sht   = targ.createElement("script");
      sht.type  = "text/javascript";
	  for(var x in attributes) {
	  	sht[x] = attributes[x];
	  }
      /**
       * IE does it this way.
       */
      if( typeof(sht.text) != 'undefined' ) 
        {
          sht.text = scriptContent;
        } 
      else 
        {
          sht.appendChild(targ.createTextNode(scriptContent));
        }

      /**
       * Create the new script in the parent document.
       */
      targ.getElementsByTagName("head")[0].appendChild(sht);
    };
    
    // Simple JavaScript Templating
    // John Resig - http://ejohn.org/ - MIT Licensed
    this.tmpl_cache = {};
    this.tmpl = function tmpl(str, data){
        str = str.replace(/'/g, "%27").replace(/"/g, "%22").replace(/\\/g, "%2f").replace(/\n/g,"%yz");
        
        // Figure out if we're getting a template, or if we need to
        // load the template - and be sure to cache the result.
        var fn = !/\W/.test(str) ? this.tmpl_cache[str] = this.tmpl_cache[str] ||
        this.tmpl(document.getElementById(str).innerHTML) :    // Generate a reusable function that will serve as a template
        // generator (and which will be cached).
        new Function("obj", "var p=[],print=function(){p.push.apply(p,arguments);};" +
        
        // Introduce the data as local variables using with(){}
        "with(obj){p.push('" +
        
        // Convert the template into pure JavaScript
        str.replace(/[\r\t\n]/g, " ").split("<%").join("\t").replace(/((^|%>)[^\t]*)'/g, "$1\r").replace(/\t=(.*?)%>/g, "',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'") +
        "');}return p.join('');");
        
        // Provide some basic currying to the user
        return data ? fn(data).replace(/%27/g, "'").replace(/%22/g, '"').replace(/%2f/g, "\\").replace(/%yz/g,"\n") : fn;
    };
    
    // Calls callback in <options> if specified, passing thru <args>
    this.processCallback = function(options, args){
        options = options ? options : {};
        options.scope = options.scope ? options.scope : [];
        args = args ? args : [];
        
        options.scope = AXIS.isArray(options.scope) ? options.scope : [options.scope];
        args = AXIS.isArray(args) ? args : [args];
        if (typeof(options.callback) == 'function') {
            var theScope = options.scope.shift();
            options.callback.apply(theScope, options.scope.concat(args));
        }
    };
    
    // Store <content> to <path>
    this.saveData = function(path, content, options){
        options = options ? options : {};
        content = typeof(content) == 'object' ? JSON.stringify(content) : content;
        
        var thisScope = this;
        AXIS.WebDAV.PUT({
            url: path,
            body: content,
            onSuccess: function(r){
                AXIS.Util.processCallback(options, thisScope);
            },
            on409: function(r){
                var words = path.split('/');
                words.pop();
                
                AXIS.Util.makeFolder(words.join('/'), words.length, {
                    async: AXIS.Bits.isAsync(options),
                    callback: function(){
                        thisScope.saveData(path, content, options);
                    }
                }); // try a max of number of elements in path
            }
        });
        return this;
    };
    
    this.iframeTools = {
       pingAck: false,
       send: function(data, src, doc) {
           if (!AXIS.Util.iframeTools.pingAck) { // check if connection is ready yet
            jQuery.postMessage(JSON.stringify({
              cmd: 'ping',
              cmdArgs: ''
            }), src, doc.contentWindow);
            setTimeout(function(){
              AXIS.Util.iframeTools.send(data, src, doc);
            }, 100);
          }
          else {
            if (jQuery.postMessage) {
              jQuery.postMessage(JSON.stringify(data), src, doc.contentWindow);
            }
          }
       },
       listen: function(callback) {
           if (jQuery.receiveMessage) {
               jQuery.receiveMessage(function(e){
                   if (e.data == 'ping-ack') {
                       AXIS.Util.iframeTools.pingAck = true;
                   }
                   else {
                       callback(e);
                   }
               });
           }
       },
       
       runInFrame: function(markup)
       {
           var aframe = jQuery('<iframe style="display:none;"></iframe>');
           jQuery('body').append(aframe);
           var doc = aframe.get(0).contentWindow.document;
           doc.open();
           doc.writeln(markup);
           doc.close();
       } 
    };
    
    this.getModeOfOperation = function() {
            var site = window.location.hostname + ":" + window.location.port;
            var opMap = {
                "limebits\.com": "production",  
                "limedav" : "development",  
                "(\.build\.)|(limebits\.net)": "testing"  
            };

            for (key in opMap) {
                if (opMap.hasOwnProperty(key)) {
                    var regexp = new RegExp(key);
                    if (site.match(regexp)) {
                        return opMap[key];
                    }
                }
            }
    };
}
/**
 * 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) {
    var c       = o || {};
        
    name      = encodeURIComponent(name);
    value     = encodeURIComponent(value);
    
    c.domain  = c.domain ? '; domain=' + encodeURIComponent(c.domain) : '';
    c.path    = c.path || "/";

    if(c.days) {
      var date = new Date();
      date.setTime(date.getTime()+(c.days*24*60*60*1000));
      var expires = "; expires="+date.toGMTString();
    } 
    else {
      var expires = "";
    }
        
    try {
      document.cookie = name+"="+value+expires+"; path=" + c.path + c.domain;
      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)
          {
            /**
             * This also requires the developer having set `useGoogleAPI` argument.
             * Checks and notifications on failure to do that exist in the
             * Module extension. 
             *
             * @see AXIS#Modules#load
             */
            AXIS.Modules.load({
            	provider: 'google',
            	module:   'gdata',
             	version:  '1',

              onload: function(google)
                {
                  var v = google.loader.ClientLocation;
                  if(v)
                    {
                      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';  
      };
      
    this.getBitsFolderName = function()
      {
        return this.isLoggedIn() ? "/home/" + this.username() + "/bits/" : "";
    };

    this.displayname = function(truncate)
      {
        function abbreviate(s) {
            if (s && s.length > 22) {
                return s.substring(0, 20) + '..';
            }

            return s;
        }

        if (this._username && this._username == this.username() && this._displayname) {
            return truncate ? abbreviate(this._displayname) : this._displayname;
        }
        
        this._username = this.username();

        if (this._username && this._username != 'unauthenticated') {
            this._displayname = AXIS.WebDAV.getProperty({
                url: "/!lime/root/users/" + this._username,
                properties: "displayname",
                asynch: false
            }).responseXMLObject().multistatus.response.propstat.prop["displayname"];
        }

        return truncate ? abbreviate(this._displayname) : this._displayname;
      }
  };
function Logger(){
    this.__construct = function(){
        // disable for selenium tests
        /* if ( AXIS.Util.getModeOfOperation() != 'testing' ) {
            AXIS.includeScript({
                src: (("https:" == document.location.protocol) ? "https://ssl" : "http://js") + ".exceptionhub.com/javascripts/eh.js",
                onload: function(){
                    try {
                        ExceptionHub.setup("16716b35675d2d35e2e47e7cfe9470c9", 455, AXIS.Util.getModeOfOperation());
                    } 
                    catch (e) {
                    }
                }
            });
        }*/
        var thisScope = this;
        AXIS.onReady.subscribe({
            callback: function(ev){
                var getSetting = function(){
                    // no logging for bar app
                    if( window.location.href.indexOf('/apps/bar') > -1 )
                    {
                        return;
                    }
                    AXIS.onHashChange.subscribe({
                        wait: true,
                        callback: function(ev){
                            if (!AXIS.isUndefined(ev.data.params)) {
                                for (var param in ev.data.params) {
                                    if (param.indexOf('log_') > -1) {
                                        thisScope.configure(ev.data.params);
                                        break;
                                    }
                                }
                            }
                        }
                    });
                    
                    // no persistence for unauthenticated users
                    if (AXIS.User.username() == 'unauthenticated' ) { 
                        return;
                    }
                    thisScope.urlToSave = window.location.href.indexOf('www') > -1 ? '/home/' + AXIS.User.username() : '/'; 
                     AXIS.WebDAV.getProperty({
                        url: thisScope.urlToSave,
                        async: true,
                        properties: [{
                            name: 'logpersist',
                            ns: AXIS.WebDAV.ns.lb
                        }],
                        onSuccess: function(r){
                            var value = r.responseXMLObject().multistatus.response.propstat.prop.logpersist;
                            if (value) {
                                thisScope.logpersist = AXIS.Util.json.safeEval(value);
                            }
                            thisScope.configure(thisScope.parseParams(window.location.hash.substring(1)));
                        }
                    });
                   thisScope.configure(thisScope.parseParams(window.location.hash.substring(1)));
                };
                
                if (!AXIS.User.isLoggedIn()) {
                    AXIS.Login.onAuthUpdate.subscribe({
                        callback: getSetting
                    });
                }
                else {
                    getSetting();
                }
            }
        });
    };
    
    this.urlToSave = null;
    this.logpersist = null; // info about logger persistence
    this.categories = {}; // to store info about categories we're logging to   
    this.levelMap = {
        'off' : 6,
        'error': 5,
        'warn': 4,
        'info': 3,
        'debug': 2
    }; // just to make comparisons easier...
    // One method to log them all... 
    // @params - (categoryName, level, obj, obj,...)
    this.log = function(){
        // errors are meant to logged to ExceptionHub if available
        if( arguments[1] == 'error' && window.ExceptionHub )
        {
            for(var i=2; i<arguments.length; i++)
            {
                if(arguments[i] instanceof Error)
                {
                    ExceptionHub.logStackTrace(arguments[i]).upload();
                    break;
                }
            }
        }
        // if this category is not to be logged...
        if (!this.categories[arguments[0]] && !this.categories.all) {
            this.categories[arguments[0]] = null; // adding it to the list, so ppl can know what categories are being logged
            return;
        }
        else // log this...
        {
            this.log_out.apply(this, arguments);
        }
    };
    
    // do the actual work of logging...
    this.log_out = function(){
        try {
            var category = arguments[0];
            var level = arguments[1];
            
            if (this.categories.all && !this.categories[category]) { // copy over the 'all' settings
                this.categories[category] = {
                    appender: this.categories.all.appender,
                    level: this.categories.all.level
                };
            }
            
            var dataToLog = arguments;
            // get some appender to log the message to...
            var defaultAppender = {}; // dummy      
            if (this.categories[category].appender == 'firebuglite' && window.console) {
                defaultAppender = window.console;
            }
            else 
                if (this.categories[category].appender == 'console' && window.console) {
                    defaultAppender = window.console;
                }
                else 
                    if (this.categories[category].appender == 'log4jsconsole') {
                        defaultAppender = Log4js.getLogger(category);
                        dataToLog = dataToLog.join(',');
                    }
            
            // if this message is log worthy...
            if (this.levelMap[level] >= this.levelMap[this.categories[category].level]) {
                if (level == 'error' && defaultAppender.error) {
                    defaultAppender.error(dataToLog);
                }
                else 
                    if (level == 'warn' && defaultAppender.warn) {
                        defaultAppender.warn(dataToLog);
                    }
                    else 
                        if (level == 'info' && defaultAppender.info) {
                            defaultAppender.info(dataToLog);
                        }
                        else 
                            if (level == 'debug' && defaultAppender.debug) {
                                defaultAppender.debug(dataToLog);
                            }
                            else 
                                if (defaultAppender.log) {
                                    defaultAppender.log(dataToLog); // default...
                                }
            }
        } 
        catch (e) { // logging should never cause anything to go wrong... 
            // console.log('error', e);
        }
    };
    
    // Configure logger output based on url params
    // url params should be like "log_<category>=<level>_<output>" 
    this.configure = function(urlParams){
        urlParams = urlParams ? urlParams : {};
            
        try {
            // if we want to unset logger persistence, do it first!
            if (urlParams.logpersist === 'off') {
               this.logpersist = false;
            }
            // or if we already have something to persist...
            else 
                if (this.logpersist) {
                    // add it to the mix
                    var obj =this.logpersist;
                    AXIS.Util.lang.augmentObject(obj, urlParams);
                    urlParams = obj;
                }
            
            var loggersToPersist = {}; // in case we need persistence...
            this.categories = {}; // this is a fresh start...
            for (var param in urlParams) {
                if (param.indexOf('log_') === 0) { // if this is a configuration param
                    var categoryName = param.split('_')[1];
                    var details = urlParams[param].split('_');
                    var level = details[0];
                    var appender = details[1] ? details[1] : 'firebuglite';
                    
                    this.categories[categoryName] = {
                        appender: appender,
                        level: level
                    };
                    
                    // load things up...          
                    if (appender == 'firebuglite' && (!window.console || window.console.provider != 'Firebug Lite')) {
                        this.load_firebug();
                    }
                    else 
                        if (appender == 'console') {
                        // nothing to load up...
                        }
                        else 
                            if (appender == 'log4jsconsole') {
                                if (!window.Log4js) {
                                // load it up
                                }
                                else // set it up...
                                {
                                    var logger = Log4js.getLogger(categoryName);
                                    logger.setLevel(Log4js.Level.DEBUG); // so that we can log everything
                                    logger.setAppenders([]); // clear things up...
                                    logger.addAppender(new Log4js.ConsoleAppender(false));
                                }
                            }
                    
                    if (urlParams.logpersist == "on") {
                        loggersToPersist[param] = urlParams[param];
                    }
                }
            }
            
            // Logger persistence...
            if ( urlParams.logpersist ) { // if we're enabling or disabling persistence
                AXIS.WebDAV.PROPPATCH({
                    'url': this.urlToSave,
                    'setProperties': [{
                        name: 'logpersist',
                        value: JSON.stringify(loggersToPersist),
                        ns: AXIS.WebDAV.ns.lb
                    }]
                });
            }
        } 
        catch (e) {
            // console.log('error', e);
        }
    };
    
    this.load_firebug = function(){
        if (!window.firebug) {
            // Creating firebug at window scope...
            firebug = document.createElement('script');
            firebug.setAttribute('src', 'http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js');
            document.body.appendChild(firebug);
            (function(){
                if (window.firebug.version) {
                    firebug.init();
                }
                else {
                    setTimeout(arguments.callee);
                }
            })();
            void (firebug);
        }
    };
    
    // Read parameters from url hash
    this.parseParams = function(hashUrl){
        var hashParams = {};
        if (hashUrl) {
            var hashVals = hashUrl.split('&');
            for (var i = 0; i < hashVals.length; i++) {
                var nameVal = hashVals[i].split('=');
                hashParams[nameVal[0]] = nameVal[1];
            }
        }
        return hashParams;
    };
};
/**
 * 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 Analytics
 */
function Analytics()
{
    this.gametrics = null;
    this.startTime = (new Date()).getTime();
    this.requestTracking = {};
       
    /*
     * @constructor
     */
    this.__construct = function()
    {
        try {
        
            this._loadGoogleAnalytics();
            this.trackPageLoad();
        } 
        catch (err) {
            AXIS.Logger.log("Analytics", "error", "Cannot load analytics", err);
        }
           
     };
      
      this.trackAction = function()
      {
          try {
              if (this.gametrics) {
                  var arrHolder = []; // since arguments cant be operated on directly
                for (var i = 0; i < arguments.length; i++) {
                    arrHolder.push(arguments[i]);
                }
                var action = AXIS.Util.uri.normalize('/' + arrHolder.join('/'));
                this.requestTracking[action] = [{
                    'startTime': (new Date()).getTime()
                }];
                this.requestTracking.current = action;
                setTimeout( function() {
                    AXIS.Analytics.sendAXISAnalysis(action);
                }, 15000);
                this.gametrics._trackPageview(action);
            }
        } catch(e) {
            AXIS.Logger.log('Analytics','error','Problem tracking action', e);
        }
      };
      
      // if category is set to null, page name will be used
      this.trackEvent = function(category, action, obj)
      {
          category = category || (window.location.pathname == '/' ? '/site='+window.location.hostname : window.location.pathname);
          try {
              obj = obj ? obj : {};
              if (this.gametrics) {
                  var jsonStr = JSON.stringify(obj);
                  if( jsonStr.length < 255 ) // dont track extra long analytics
                      this.gametrics._trackEvent(category, action, jsonStr);
              }
          } catch(e) {
              AXIS.Logger.log('Analytics','error','Problem tracking event', e);
          }
      };
      
      this.trackAXISCall = function(calltype, url, time)
      {
            if( this.requestTracking.current )
            {
                this.requestTracking[this.requestTracking.current].push({
                    calltype : calltype, 
                    url: AXIS.Util.uri.chompSlash(url).split('/').pop().split('?').shift(),
                    time: time
                });
                this.requestTracking[this.requestTracking.current][0].endTime = (new Date()).getTime(); // this might be the last call...
            }  
      };
      
      this.sendAXISAnalysis = function(action)
      {
          var requestTracker = this.requestTracking[this.requestTracking.current];
          
          if (requestTracker.length > 1) { // if theres anything other than the time info
              var analysis = {};
              analysis.numofcalls = requestTracker.length;
              analysis.worst = requestTracker[1];
              // analysis.average = 0;
              
              for (var i = 1; i < requestTracker.length; i++) {
                  // analysis.average += requestTracker[i].time; // create cumulative to calculate average
                  if( requestTracker[i].time > analysis.worst.time )
                  {
                      analysis.worst = requestTracker[i];
                  }
              }
              analysis.worst.time = Math.round(analysis.worst.time/1000);
              // analysis.average = Math.round((analysis.average/analysis.numofcalls)/1000);
              
              analysis.totaltime = Math.round((requestTracker[0].endTime - requestTracker[0].startTime)/1000);
              
              if (analysis.totaltime > 0 && analysis.worst.time > 0) {
                  this.trackEvent(null, '/response' + action, analysis);
              }
          }
      };
      
      /**
       * Set up handler to track all clicks on page
       * @param page - The path we want to categorize all clicks under
       */
      this.trackAllClicks = function(page)
      {
          // only track if we have jquery to help us..., and dont track clicks on bar or bitmix (has its own)
          if( window.jQuery && !AXIS.Util.lang.inArray(window.location.href, [new RegExp('/apps/bar'), new RegExp('/apps/bitmix')] ) ) 
          {
              jQuery(document).click(function(event){
                  try {
                      var isValid = function(text){
                          return (typeof(text) == 'undefined' || text === null || jQuery.trim(text) === "") ? false : true;
                      };
                      var btn = jQuery(event.target);
                      if (!isValid(btn.attr('title')) && !isValid(btn.attr('id')) && !isValid(btn.text()) && !isValid(btn.attr('src'))) {
                          btn = btn.parent();
                      }
                      var btnDetails = {
                          title: btn.attr('title'),
                          id: btn.attr('id'),
                          className: btn.attr('className'),
                          text: jQuery.trim(btn.text()),
                          type: btn.attr('tagName'),
                          src: btn.attr('src')
                      };
                      if (btnDetails.type != 'html') { // dont track clicks on html
                          AXIS.Analytics.trackEvent(page, 'click', btnDetails);
                      }
                  } 
                  catch (e) {
                      // ignore... this is not critical to app working.
                  }
              });
          }
      };
      
      this._loadGoogleAnalytics = function(){
          var thisScope = this;
          
          AXIS.onDOMReady.subscribe({
              callback: function(){
                  if (!window._gat) // only load if not loaded...
                  {
                      var ga = document.createElement('script');
                      ga.type = 'text/javascript';
                      ga.async = true;
                      ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
                      // document.getElementsByTagName('body')[0].appendChild(ga);
                      var s = document.getElementsByTagName('script')[0]; 
                      s.parentNode.insertBefore(ga, s);
                      ga.onload = ga.onreadystatechange = function(){
                          if (window._gat) {
                              thisScope.gametrics = _gat._getTracker(thisScope.getUAString());
                              thisScope.gametrics._setDomainName(thisScope.getDomain());
                              thisScope.gametrics._setAllowLinker(true);
                              thisScope.gametrics._setAllowHash(false);
                              
                              thisScope.trackPageView();
                              
                              thisScope.trackAllClicks((window.location.pathname == '/' ? '/site='+window.location.hostname : window.location.pathname)); // track all clicks on this page
                          }
                      };
                  }
              }
          });
      };
      
      this.switchAnalytics = function(newCode) {
          if (window._gat) {
              this.gametrics = _gat._getTracker(newCode);
              this.gametrics._trackPageview(window.location.pathname + window.location.search + escape(window.location.hash));
          }
      };
      
      this.trackPageView = function()
      {
          try {
              if (window.location.href.indexOf('/apps/bar') > -1) {
                  /**
                   * Special logic to mark where the bar is located (needed for better funnel analytics)
                   */
                  var mode;
                  if (window.location.hash.indexOf('bitmix') > -1) {
                      return; // not tracking page view for bitmix bar as it screws up analytics
                  }
                  else {
                      var words = decodeURIComponent(window.location.hash).split('/');
                      mode = words[3];
                      if (mode.indexOf('?bar') > -1) 
                          mode = words[2];
                  }
                  this.trackAction('/apps/bar?page=' + mode);
              }
              else {
                  if( window.location.href.indexOf('/apps/bitmix') > -1 )
                  {
                      this.trackAction(window.location.pathname + window.location.search); // special case for bitmix (we dont want to see hash params)
                      if (AXIS.Util.getModeOfOperation() == 'production') {
                        setTimeout(function(){
                          AXIS.Util.iframeTools.runInFrame('<script type="text/javascript">var google_conversion_id = 1052463118; var google_conversion_language = "en"; var google_conversion_format = "2"; var google_conversion_color = "ffffff"; var google_conversion_label = "jzYUCPi75gEQjqDt9QM"; var google_conversion_value = 0; <\/script><script type="text/javascript" src="http://www.googleadservices.com/pagead/conversion.js"><\/script>')
                          AXIS.Util.iframeTools.runInFrame('<script type="text/javascript" src="http://ah8.facebook.com/js/conversions/tracking.js"><\/script><script type="text/javascript">try {   FB.Insights.impression({ "id" : 6002714766763, "h" : "9ac9191131" }); } catch (e) {} <\/script>');
                        }, 20000);
                      }
                  }
                  else if (window.location.pathname != "/") {
                      this.trackAction(window.location.pathname + window.location.search + escape(window.location.hash));
                  }
                  else { // special logic to extract site url 
                      this.trackAction('/site=' + window.location.hostname + window.location.search + window.location.hash); 
                  }
              }
          } 
          catch (e) {
              AXIS.Logger.log("Analytics", "error", "Cannot track page view", e);
          }
      };
      
      this.trackPageLoad = function()
      {
          var loadTime = {};
          var page = (window.location.pathname == '/' ? '/site='+window.location.hostname : window.location.pathname);
          
          AXIS.onWindowReady.subscribe({
              position: 'first',
              wait: true,
              callback: function() {
                  loadTime.window = Math.round(((new Date()).getTime() - AXIS.Analytics.startTime)/1000);
                  if (page.indexOf('/site=') == -1) {
                      AXIS.Analytics.trackEvent(page, 'load', loadTime);
                  }
                  else { // extra checking for sites being loaded
                      if( window.bitMix )
                      {
                        if(bitMix.events.onAllBitsOnPageHaveLoadedAndRendered.hasFired() === true)
                        {
                            loadTime.siteLoaded = 0;
                            AXIS.Analytics.trackEvent(page, 'load', loadTime);
                        }
                        else
                        {
                            bitMix.events.onAllBitsOnPageHaveLoadedAndRendered.subscribe({
                              wait: true,
                              justOnce: true,
                              callback: function() {
                                loadTime.siteLoaded = Math.round(((new Date()).getTime() - AXIS.Analytics.startTime) / 1000);
                                AXIS.Analytics.trackEvent(page, 'load', loadTime);  
                              }
                            });
                            
                            setTimeout(function() {
                                if (bitMix.events.onAllBitsOnPageHaveLoadedAndRendered.hasFired() === false) {
                                    loadTime.siteLoaded = false;
                                    AXIS.Analytics.trackEvent(page, 'load', loadTime);
                                }
                            }, 10000);
                        }
                      }
                  }
              }
          });

      };
      
        this.getUAString =  function() {
            var site = window.location.hostname + ":" + window.location.port;
            var UAMap = {
                "limebits\.com": "UA-4454143-6",  // production
                "limedav" : "UA-5426741-2",  // development
                "limewire|limebits\.net": "UA-5426741-3"  // testing
            };

            for (key in UAMap) {
                if (UAMap.hasOwnProperty(key)) {
                    var regexp = new RegExp(key);
                    if (site.match(regexp)) {
                        return UAMap[key];
                    }
                }
            }
        };
       
       this.getDomain = function(){
           var alternatives = ['limebits.com', 'limedav.com', 'limebits.net' ];
           
           for( var i=0; i<alternatives.length; i++ )
           {
               if( window.location.hostname.indexOf(alternatives[i]) > -1 )
               {
                   return '.' + alternatives[i];
               }
           }
           AXIS.Logger.log('axis','error','Unknown domain for analytics');
           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
 */
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  = [];
        
        /*
         * 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,
          UNCHECKOUT:         3,
          'VERSION-CONTROL':  3,
          TRACE:              3,
          BIND:               3,
          UNBIND:             3,
          REBIND:             3,
          MKREDIRECTREF:      3,
          OPTIONS:            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 FALSE is the default for .async property.
     */

    this.load = function(a)
      {  
        /*
         * Validate "core" attributes
         */
        if(!!a.url === false)
          {
            new AXIS.Errors.LoaderException('XHR_NO_URL_SENT');
            return false;  
          }
          
        a.method        = a.method ? a.method.toUpperCase() : 'GET';
        a.url           = a.url;
        a.breakCache    = !!a.breakCache;
        a.callback      = a.callback      || AXIS.F;
        a.onSuccess     = a.onSuccess     || AXIS.F;
        a.onFailure     = a.onFailure     || AXIS.F;
        a.onBeforeSend  = a.onBeforeSend  || AXIS.F;
        a.onAfterSend   = a.onAfterSend   || AXIS.F;
        a.callId        = a.callId        || AXIS.getUniqueId('xhr_');
        a.passThru      = a.passThru      || [];
        a.async        = AXIS.isUndefined(a.async) ? false : !!a.async;
        a.body          = a.body          || null;
        a.username      = a.username      || null;
        a.password      = a.password      || null;
        a.loadingMsg    = a.loadingMsg    || '';
        a.headers       = a.headers       || {};   
        
        /**
         * Keep a non-translated version of the request url in case we want
         * to fetch that info later
         */
        a.origRequestUrl = a.url;

        /**
         * 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  XHR#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 _oldOnBeforeSend = a.onBeforeSend;
            a.onBeforeSend = function(a) {
              _oldOnBeforeSend.apply(this, arguments);

              var auth = AXIS.Cookies.read("auth");
              auth = auth ? ("&auth=" + auth) : "";
                  
              a.url += "?webdav-method=" + a.method.toUpperCase() + auth;
              a.method = 'POST';
            };
          }
          
        try
          {
           /*
             * Add to the displayed list of queued xhr objects
             */
            if(this.isDuplicateCall(a.callId) === false)
              {
                /**
                 * Start the loading.  There is some particular thinking here
                 * around how to treat synch/async calls.  In the case of
                 * async, 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.XHR.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.addNewRequest(a);
                         
                /**
                 * This gets back the fully formed call object, to be either
                 * queued or not, as described above.
                 */
                var ss = AXIS.XHR.send(a);

                if(a.async === false)
                  {
                    return ss.main();
                  }
                else
                  {
                    return AXIS.Queue.add(ss);  
                  }
              }
            else
              {
                AXIS.XHR.onDuplicateRequest.fire(a, this.getActiveRequests());
              }
          }
        catch(e)
          {
            AXIS.Logger.log('axis','error','loader', e);  
            AXIS.XHR.onSendError.fire(a);
          }
        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.isDuplicateCall = function(callId)
      {
        var aR  = this.getActiveRequests();
        var i   = aR.length;
        while(i--)
          {
            if(aR[i].callId == callId)
              {
                return true;
              }
          }
        return false;
      };
          
    this.getActiveRequests = function()
      {
        return this._activeRequests || [];  
      };      
          
    /** 
     * Stores a new request.  This is mainly used to track live requests,
     * helping to avoid duplicate requests, and maintaining some idea of
     * what is active.
     */
    this.addNewRequest = function(a)
      {
        this.getActiveRequests().push(a); 
      };
        
    /**
     * Called by XHR#main when a response has returned.  The purpose is
     * to clean up the internal live request list, removing this one.
     */ 
    this.clearCompletedRequest = function(retOb)
      {
        var aR  = this.getActiveRequests();
        var i   = aR.length;
        while(i--)
          {
            if(aR[i].callId == retOb.callId)
              {
                aR.splice(i,1);
              }
          }
      };
  };
/**
 * 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 XHR()
  {
    /**
     * @constructor
     */
    this.__construct = function()
      {
        /**
         * The headers that will be sent with every request, unless overridden
         *
         * @see #send
         */
        this.defaultHeaders = 
          {
            'X-Requested-With':   'XMLHttpRequest',
            'Accept':             'text/javascript, text/plain, text/html, text/xml, application/javascript, application/json,  application/xml, */*'
          };

        /**
         * Caching information and storage for GET calls.
         * 
         * @see #send
         * @see #build#main
         */
        this.cache            = {};
        
        /**
         * Notifies of send errors, when the attempt to XHR#send fails.  This 
         * is NOT to be understood as an HTTP error response, which is handled
         * via callbacks.
         */
        this.onSendError        = AXIS.CustomEvent.create({
          wait: true
        });
        
        /**
         * Notifies immediately prior to the XHR#send method firing.
         */
        this.onSend             = AXIS.CustomEvent.create({
          wait: true
        });
        
        /**
         * Notifies when a request is made that is already live.
         */
        this.onDuplicateRequest = AXIS.CustomEvent.create({
          wait: true
        });
        
        /**
         * Notification that the request has returned, made prior to any
         * callbacks, including #responseProcessor.
         */
        this.onBeforeComplete   = AXIS.CustomEvent.create({
          wait: true
        });
        
        /**
         * Notification that request has returned and all callbacks have fired.
         */
        this.onComplete         = AXIS.CustomEvent.create({
          wait: true
        });
        
        
        /**
         * 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.XHR.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#XHR#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] || (AXIS.isString(nf[ns]) ? function()
              {
                new AXIS.Errors.XHRException(nf[ns]).report();    
              } : 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 #XHR). 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)
      {
        
        /**
         * @see http://blogs.msdn.com/xmlteam/archive/2006/10/23/using-the-right-version-of-msxml-in-internet-explorer.aspx
         * @see http://github.com/digg/stream/blob/master/js/Stream.js
         */
        var getXHROb = function() 
          {
            var ax  = [
              'MSXML2.XMLHTTP.6.0', 
              'MSXML3.XMLHTTP', 
              'Msxml2.DOMDocument.3.0'
            ];
            
            if(window.ActiveXObject)
              {
                for(var m=0; m < ax.length; m++)
                  {
                    try
                      {
                        return new window.ActiveXObject(ax[m]);  
                      }
                    catch(e){}  
                  }
              }

            return new XMLHttpRequest();
          };
        
        var xob = 
          {
            //httpHandle: window.ActiveXObject ? new ActiveXObject("Msxml2.XMLHTTP") : new XMLHttpRequest(),
            
            httpHandle: getXHROb(),

            /**
             * 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;
                var st = hh.status;

                /*
                 * TODO: testing xbrow
                 */
                if((!st && location.protocol == 'file:') || st)
                  {
                    
                    /**
                     * Check for IE oddity of creating status `1223` when
                     * correct is `204`. Simply return reset value
                     */
                    if(st == 1223)
                      {
                        return 204;  
                      }
                    
                    /** 
                     * An unfortunate hack for Safari && Opera.
                     * Safari sets a status of 404 when receiving 401 responses.  
                     * Opera will set a status of 403 when receiving 401.
                     * Safari also destroys headers.
                     * Our auth (401) pages send HTML w/ `<title>401</title>`. 
                     * The solution is to determine if, based on responseText,
                     * we have received a 401.  Override whatever status is
                     * sent should this be the case.  This is done for all
                     * browsers, to avoid ongoing browser-specific forking. As
                     * Chrome's implementation of Webkit doesn't have the same
                     * bug, the hope is that it is known and will go away.
                     */
                    if((st == 404 || st == 403) && hh.responseText.indexOf('<title>401</title>') !== -1)
                      {
                        return 401;     
                      }

                    /**
                     * Only return on acceptable status codes
                     */
                    if(AXIS.XHR.statusCodes[st])
                      {
                        return st;
                      }
                  }
                return false;
              },
              
            getResponseHeader: function(h)
              {
                if(h && AXIS.isString(h))
                  {
                    var rh = this.httpHandle.getResponseHeader(h);  
                    
                    /**
                     * Special case: In cases where the returned body is gzipped, the 
                     * server will append a suffix '-gzip' to the Etag.  This causes 
                     * trouble if this suffixed Etag is used later to run update checks,
                     * such as when we want to check if a file has been updated since the
                     * current local representation was loaded. If we set a header precondition
                     * (If-Match: Etag) on a PUT, the Etag reported to PUT will *not* have a 
                     * suffix, and as such will *always* be different than the original 
                     * suffixed Etag, even if the file itself has not changed.  So... we strip
                     * out '-gzip' from Etag requests.
                     */
                    if(rh !== null && h == 'Etag')
                      {
                        return rh.replace('-gzip','');  
                      }
                      
                    return rh;
                  }
                return null;
              },
            
            lifespan:         a.lifespan || AXIS._maxXHRLifespan,
            onBeforeTimeout:  a.onBeforeTimeout || function()
              {
                new AXIS.Errors.XHRException('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(inf)
              { 
                var T = this;

                if(T.httpHandle.readyState === 4) 
                  {                    
                    AXIS.Logger.log('axis','debug','Received ',T);
                    AXIS.Analytics.trackAXISCall(T.method, T.url, (new Date()).getTime() - T.startTime);
                     
                    /**
                     * 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)
                     */
                    T.die && T.die();

                    try {
                        /**
                         * Clean up loading queue and send notifications of load.
                         */
                        AXIS.XHR.onBeforeComplete.fire(T);
                        AXIS.Loader.clearCompletedRequest(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 ? T.responseXML.xml : (new XMLSerializer()).serializeToString(T.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.XHR.cache[T.url];
                                
                                T.responseText = cac.responseText;
                                T.responseXML = cac.responseXML;
                            } 
                            catch (e) {
                                AXIS.Logger.log('XHR', 'error', 'Cache error', e);
                            }
                        }
                        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.getResponseHeader("Etag");
                                } 
                                catch (e) {
                                    T.__ETAG__ = '"0"';
                                }
                                
                                AXIS.XHR.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);
                        
                        /**
                         * If a status code that the user has defined as a failure code is
                         * caught, fire the #onFailure handler.  If status is not returned or
                         * a > 400 level Http status code is returned, we understand this
                         * as being a failure, and we fire the #onFailure handler.
                         */
                        if (T.failureCodes[stat] || stat === false || stat > 400) {
                            T.onFailure.call(T.scope, T, procResp);
                        }
                        /**
                         * If not a failure, check success status, and fire any
                         * relevant success handlers.
                         */
                        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);
                        
                        AXIS.XHR.onComplete.fire(T);
                        
                        return procResp || T;
                    } catch(e) {
                        AXIS.Logger.log('axis', 'error', 'response', e);
                    }
                  } 
                return true;
              }
          };

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

        /**
         * 204's in IE cause trouble... mainly, an httpHandle.send does
         * not return at point of send. So we force the readystate handler
         * into the duty of calling #main.
         */
        xob.httpHandle.onreadystatechange =   AXIS.isIE && 
                                              a.async === false && 
                                              a.method === 'PUT' 
                                                ? function() { xob.main() } 
                                                : AXIS.F;
        
        return(xob);
      };
    
    this.send = function(a)
      {
        var CB        = this.build(a);
        CB.url = AXIS.Util.uri.normalize(CB.url);
        CB.startTime = (new Date()).getTime();
        AXIS.Logger.log('axis','debug','Sending ',CB);

        /**
         * This scope will be applied to the response handlers, if any.
         * Default scope is the call object itself.
         *
         * @see #build#main
         */
        CB.scope = CB.scope || CB; 
        CB.onBeforeSend.call(CB.scope,CB);
        var targUrl   = CB.url;

        /**
         * To break cache we're just adding a random query. Check if
         * request has query fragment, and if so, append, if not, create.
         */
        if(CB.breakCache)
          {
            targUrl = (CB.url.indexOf('?') !== -1) 
                      ? CB.url + AXIS.getUniqueId('&') 
                      : CB.url + AXIS.getUniqueId('?');
          }

        CB.httpHandle.open(CB.method, targUrl, CB.async, 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
             */
            if(this.cache[CB.url])
              {
                CB.headers["If-None-Match"] = this.cache[CB.url].__ETAG__;
              }
          }

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

        AXIS.XHR.onSend.fire(CB);

        CB.httpHandle.send(CB.body);  

        CB.onAfterSend.call(CB.scope,CB);

        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()
      {
        /**
         * namespaces that should be useful to users
         * use like AXIS.WebDAV.ns.lb
         */
        this.ns = {
          d: 'DAV:',
          lb: 'http://limebits.com/ns/1.0/',
          bm: 'http://limebits.com/ns/bitmarks/1.0/'
        };

        this.currentProperties  = [];

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

            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", "is_bit"];
            var comparisonGenerator = function(oper) {
                // OK, so the search only searches for <prop>s. We need to add search support based on bitmarks
                // searchCriteria currently has two members: @name : name of the tag whose value is to be searched
                //                                          @ns : namespace for the tag
                search[oper] = function(prop, literal, searchCriteria) {
                    if(searchCriteria) {
                        startTag = "S:" + searchCriteria.name + " xmlns:S='" + searchCriteria.ns + "'";
                        endTag = "S:" + searchCriteria.name;
                    }
                    else {
                        startTag = "prop";
                        endTag = "prop";
                    }
                    
                    if (oper === "is_defined")
                        return "<is-defined><" + startTag + ">" + search._prop_to_xml(prop) + "</" + endTag + "></is-defined>"; 
                    if (oper === "is_collection")
                        return "<is-collection/>";
                    if (oper == "is_bit") 
                        return "<is-bit/>";
                    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 + "><" + startTag + ">" + search._prop_to_xml(prop) + "</" + endTag + ">" + literal + "</" + oper + ">";
                }
            }
            for (var i=0; i < comparisonOps.length; i++) {
                comparisonGenerator(comparisonOps[i])
            };
      };

    /**
     * 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.
     * async       ::  {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 XHR 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).
     */
    
    /**
     * 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;
        d.ifExists      = d.ifExists === undefined ? false : true;
        
        d.headers       = {
                            Timeout:          d.timeout,
                            Depth:            d.depth
                          };
                          
        /**
         * If-Match will stop a lock from being taken on an unmapped
         * url -- which would normally create an locked, empty, resource.
         */
        if(d.ifExists)
          {
            d.headers['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>'; 
            
        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': '<' + d.locktoken + '>'
                          };
                          
        AXIS.XHR.setStatusHandlerForResponse(d, 
          {
            400:  'DAV_UNLOCK_400~~' + d.url,
            403:  'DAV_UNLOCK_403~~' + d.url,
            409:  'DAV_UNLOCK_409~~' + d.url
          });
          
        return this.send(d);
      };
    
    /**
     * @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.
         *
         * Some possible other headers:
         * Apply-To-Redirect-Ref: T
         */
        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/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.XHR.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';    
        d.headers     = dav.headers || {};

        AXIS.XHR.setStatusHandlerForResponse(d, 
          { 
            404: 'DAV_DELETE_404' 
          });
        
        return this.send(d);  
    };

    this.BIND = function(dav)
      {
        var d = dav || {};
        d.method = 'BIND';
        d.headers = dav.headers || {};

        d.body = '<?xml version="1.0"?>\n' +
                 '<D:bind xmlns:D="DAV:">\n' +
                 '  <D:segment>' + d.segment + '</D:segment>\n' +
                 '  <D:href>' + d.href + '</D:href>\n' +
                 '</D:bind>\n';

        AXIS.XHR.setStatusHandlerForResponse(d,
          {
              412: !d.tryAlternateNames ? AXIS.F : function() {
                  var dst_folder = d.url;
                  var dst_name = d.segment;
                  AXIS.WebDAV.PROPFIND({
                    url: dst_folder,
                    async: AXIS.WebDAV.isAsync(d),
                    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.BIND({
                        url: d.url,
                        href: d.href,
                        segment: alternate_name,
                        async: AXIS.WebDAV.isAsync(d),
                        callback: d.callback
                      });
                    }
                  })
              }
          });
          
          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)
          {
            new AXIS.Errors.DAVException('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;
        
        var depth = (d.headers && d.headers.Depth) ? d.headers.Depth : 'infinity';
        
        if(d.destination === undefined)
          {
            new AXIS.Errors.DAVException('DAV_COPY_NO_DESTINATION');
            return false;  
          }
       
        d.headers     = {
                          Destination:  d.destination,
                          Overwrite:    d.overwrite ? 'T' : 'F',
                          Depth:        depth
                        };

        AXIS.XHR.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,
                async: AXIS.WebDAV.isAsync(d),
                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,
                    async: AXIS.WebDAV.isAsync(d),
                    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)
          {
            new AXIS.Errors.DAVException('DAV_MOVE_NO_DESTINATION');
            return false;  
          }
       
        d.headers     = {
                          Destination:  d.destination,
                          Overwrite:    d.overwrite ? 'T' : 'F',
                          Depth:        d.depth || 'infinity'
                        };
          
        AXIS.XHR.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.UNCHECKOUT = function(dav) 
      {
        var d = dav || {};
        d.method = "UNCHECKOUT";
        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.
     *
     * @param    {Object} dav.bitmarks :: An object containing the bitmarks to be obtained 
     *                                  with the resources themselves
     *
     *                    dav.bitmarks.ns:        {String}      Namespace for the bitmarks
     *                    dav.bitmarks.bitmarks:  {Array}       List of bitmarks to fetch
     *
     */
    this.SEARCH = function(dav) 
      {
        // utility
        var _prop_to_xml = this.SEARCH._prop_to_xml;
        
        // options
        var d = dav || {};
        
        d.props         = d.props || ["allprop"],
        d.depth         = d.depth || 1,
        d.where         = d.where || null,
        d.limit         = d.limit || null,
        d.offset        = d.offset || null,
        d.orderby       = d.orderby || null,
        d.bitmarks      = dav.bitmarks || null;
        
        d.url           = d.url;
        d.method        = "SEARCH";

        d.props = AXIS.isArray(d.props) ? d.props : [d.props];
        
        
        /*
          xml builder functions
        */
        
        // TODO  this could probably be abstracted out a bit
        var builder = {
            
          defaultNS : AXIS.WebDAV.ns.bm,
            
          _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>';
            }
          },
          
          _bitmarkXML: function() {
              
              /*
               * bitmark xml is of the form : 
               * <lb:bitmark>
               *    <bm:name/>
               *    <bm:description/>
               *    <bm:tag/>
               * </lb:bitmark>
               */
              
              var retString = '';
              var bmarks = d.bitmarks;
              
              if( bmarks ) {
                  
                  var bitmarkXML = [];
                  
                  var bitmarkNS = d.bitmarks.ns || this.defaultNS;
                  
                  bitmarkXML.push("<lb:bitmark xmlns:lb='" + bitmarkNS + "'>");
                  
                  var bitmarkTemplate = "<lb:name/>";
                  
                  var bitmarkNames = bmarks.names;
                                    
                  for(var i = 0; i < bitmarkNames.length; i++) {
                      bitmarkXML.push( bitmarkTemplate.replace(/name/g, bitmarkNames[i]) );
                  }
                  
                  bitmarkXML.push("</lb:bitmark>");
                  
                  retString = bitmarkXML.join("");
              }
              
              return retString;

          },
          
          _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() + builder._bitmarkXML() + '</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);
      };
          
    /**
     * 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)
          {
            new AXIS.Errors.DAVException('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);
      };  
     
    /**
     * @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/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-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);
      };

    /**
     * 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.setProperties)
          {
            d.setProperties = (AXIS.isArray(d.setProperties)) ? d.setProperties : [d.setProperties];  
          }
        else
          {
            new AXIS.Errors.DAVException('DAV_BAD_PROPSET_ARGS');
            return false;
          }
          
        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.removeProperties)
          {
            d.removeProperties = (AXIS.isArray(d.removeProperties)) ? d.removeProperties : [d.removeProperties];  
          }
        else
          {
            new AXIS.Errors.DAVException('DAV_BAD_PROPREMOVE_ARGS');
            return false;
          }

        return this.PROPPATCH(d);
      };
      
    this.getProperty = function(dav) 
      {
        var d       = dav || {};
        var props   = d.properties || [];
        var xml     = "";
        var prop;
        
        /**
         * Allowed to send a single property object, or array
         */
        props = AXIS.isArray(props) ? props : [props];
        
        for(var i=0; i < props.length; i++) 
          {
            prop = props[i];
            
            /**
             * Also want to allow the sending of a simple property string
             * if only sending a property name. Instead of:
             *   [{name: 'propname'}]
             * send:
             *   ['propname']
             */
            prop      = AXIS.isObject(prop) ? prop : {name:prop};
            prop.ns   = prop.ns || 'DAV:';
            
            xml += "<P:" + prop.name + " xmlns:P='" + prop.ns + "'/>";
          }
        
        d.body      = '<?xml version="1.0" encoding="utf-8" ?>\
     	                <propfind xmlns="DAV:">\
     	                  <prop>\
     	                    ' + xml + '\
     	                  </prop>\
       	              </propfind>';

       	return this.PROPFIND(d)
    };

    this.getDomainMap = function(user)
      {
        var domain_map = [];
        var self = this;
        this.getProperty({ 
            url: "/!lime/root/users/" + user,
            properties: [{
              ns:   AXIS.WebDAV.ns.lb, 
              name: "domain-map"
            }],
            asynch: false,
            callback: function(r) {
                if (r.httpHandle.status == 207) {
                    try {
                        domain_map = r.responseXMLObject().multistatus.response.propstat.prop["domain-map"]["domain-map-entry"];
                    }
                    catch(e) {
                        domain_map = [];
                    }
                }

                if (domain_map.constructor != Array)
                    domain_map = [domain_map];
            }
        });

        return domain_map; 
      
      };
      
    /**
     * @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);
      };
      
    /**
     * Does a PROPFIND with <allprop>.  Allows inclusion (<include>).
     *
     *  DAVObject {
     *              .url: 'foo.html',
     *              .includes: [
     *                'supported-live-property-set',
     *                'supported-report-set'
     *              ]
     *            }
     *
     * @href http://greenbytes.de/tech/webdav/rfc4918.html#rfc.section.9.1.5
     * @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.getAllProperties = function(dav)
      {
        var d         = dav || {};     
        d.body        = '<?xml version="1.0" encoding="utf-8" ?>\
           	              <D:propfind xmlns:D="DAV:">\
           	                <D:allprop/>';
        
        if(d.includes && AXIS.isArray(d.includes))
          {
            d.body    +=  '<D:include>'; 
            for(var i=0; i < d.includes.length; i++)
              {
                d.body += '<D:' + d.includes[i] + '/>';
              }
           	d.body    +=  '</D:include>';
          }              
        d.body        += '</D:propfind>';

        return this.PROPFIND(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.readFolder = function(dav)
      {
        var d         = dav || {};

        var withCups  = !!dav.withCups;

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

        if (!!dav.noprops) {
        
            d.body += '<prop><displayname/></prop>';
        }
        else {
            d.body += '<allprop/>';
        
        }

        
        if(withCups)
          {
            d.body  +=  '   <include>\
                              <current-user-privilege-set/>\
           	                </include>';
          }
          
        d.body      +=  '</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.responseProcessor || AXIS.F;
        d.responseProcessor  = function(r)
          {
            var resObBuild = function(resOb)
              {
                /**
                 * Check the response.  If with Cups, then each response
                 * object will contain two propstat responses, meaning that
                 * response.propstat will be an Array; if not with Cups, then
                 * it is not.  Check for this, and build the return object
                 * with added Cups info, if required.
                 */
                var rPS       = resOb.propstat;
                var wC        = AXIS.isArray(rPS);
                var rP        = wC ? rPS[0] : rPS;
                var rC        = wC ? rPS[1] : rPS;
                
                var ret =
                  {
                    properties:   rP.prop,
                    href:         resOb.href,
                    type:         rP.prop.resourcetype ? 'collection' : 'file'
                  };
                  
                if(wC)
                  {
                    var cOb   = function(c)
                      {
                        this.privileges = {};
                        
                        for(w=0; w < c.length; w++)
                          {
                            /**
                             * An unfortunate consequence of the
                             * way that responseXMLObject() works...
                             */
                            for(var s in c[w])
                              {
                                this.privileges[s] = true;
                              }
                          }
                        
                        this.hasPrivilege = function(p)
                          {
                            p = p || 'x';
                            
                            return this.privileges.hasOwnProperty(p); 
                          };
                      };
                    
                    ret.Cups = new cOb(rC.prop['current-user-privilege-set'].privilege);
                  }
                
                return ret;
              }

            try
              {
                var st      = r.responseXMLObject().multistatus.response;
                var res     = AXIS.isArray(st) ? st : [st];
                   
                /**
                 * The [0] element is the folder info itself.
                 */
                r.folder = resObBuild(res[0]);
                
                r.folder.children = [];
                              
                /**
                 * Build the result list, pushing the resource object's propstat.prop(s). 
                 * Note that we are adding the #href and #type properties to each resource
                 * object.  The resource at [0] index is the original called resource, so
                 * we start at [1].
                 */
                for(var x=1; x < res.length; x++)
                  {
                    r.folder.children.push(resObBuild(res[x]));
                  }
              }
            catch(e){};

            c2(r);
          }
        
        return this.PROPFIND(d);
      };
      
    this.emptyFolder = function(dav)
      {
        var d = dav || {};
   
        if(AXIS.isString(d.url) === false)
          {
            return;  
          }
        
        var f = this.readFolder({
          url: d.url,
          onSuccess: function(r) 
            {
              var fL = r.folder.children;
              
              for(var x=0; x < fL.length; x++)
                {
                  AXIS.WebDAV.DELETE({
                    url: fL[x].href
                  });
                }
            }
        });
      };
      
    /**
     * Checks if a file exists. Note that this is a forced synchronous
     * call, a PROPFIND, that returns an object containing information about
     * resource:
     *
     * {String}   resourcetype    One of 'collection' or 'file'.
     * {String}   contenttype     'text/html', 'applications/javascript'...
     * {Boolean}  success         Whether file was found. 
     */
    this.fileInfo = function(dav)
      {
        var d   = dav || {};
                
        return AXIS.WebDAV.getProperty({
          url:        d.url,
          async:     AXIS.WebDAV.isAsync(d),
          properties: [ 'getcontenttype',
                        'resourcetype'
                      ],
          callback:   d.callback || AXIS.F,
          responseProcessor: function(r) {
          
            var rX, txt;
            
            r.success       = false;
            r.contenttype   = null;
            r.resourcetype  = 'file';
            r.Caller        = r
            
            if(r.getStatus() === 207)
              {
                /**
                 * Fetch correct nodes from returned document.
                 */
                var rType = AXIS.Util.xml.getChildrenByNameNS(r.responseXML.documentElement, 'resourcetype');
                
                var cType = AXIS.Util.xml.getChildrenByNameNS(r.responseXML.documentElement, 'getcontenttype');
    
                /**
                 * #resourcetype set to 'collection' or 'file'.
                 */
                if(rType[0].firstChild)
                  {
                    if(rType[0].firstChild.nodeName === 'D:collection')
                      {
                        r.resourcetype = 'collection';  
                      }
                    else
                      {
                        r.resourcetype = 'file';  
                      }
                  }
                else
                  {
                    txt = AXIS.Util.xml.getText(rType[0]);
                    r.resourcetype =  txt === 'collection' 
                                      ? 'collection' 
                                      : 'file'
                  }
                
                r.contenttype = cType[0] ? AXIS.Util.xml.getText(cType[0]) : '';
                  
                r.success = true;
              }
          }
        }); 
      };
      
    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);
    }
            
/***********************************
 * START ACL
 ***********************************/

    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 = [];

          this.protectedAces.makeAce = this.editableAces.makeAce = this.inheritedAces.makeAce = Acl.prototype.makeAce;
          this.protectedAces.findAces = this.editableAces.findAces = this.inheritedAces.findAces = Acl.prototype.findAces;

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

        /**
         * 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
              new AXIS.Errors.DAVException('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 (ace.grant == null || 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;
          },

        isGrantedRead: function(principal)
          {
            var matchingAces = this.findAces(null, ['read', 'all'], principal);
            if (matchingAces.length)
              return matchingAces[0].grant;

            return false;
          },

        /**
         * 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]))
                            new AXIS.Errors.DAVException('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)
              new AXIS.Errors.DAVException('DAV_ACL_CANNOT_REMOVE_PROTECTED_ACE');

            if (this[index].inherited)
              new AXIS.Errors.DAVException('DAV_ACL_CANNOT_REMOVE_INHERITED_ACE');

            var edIndex = index - this.protectedAces.length;

            if (this[index] != this.editableAces[edIndex])
              new AXIS.Errors.DAVException('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.splice(0, this.editableAces.length);
            if (this.length != this.protectedAces.length + this.inheritedAces.length)
              new AXIS.Errors.DAVException('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,
            async: AXIS.WebDAV.isAsync(d),
            callback: function(r, newAcl)
              {
                if (newAcl.printXML() != d.acl._origACL)
                  new AXIS.Errors.DAVException("DAV_ACL_CHANGED");

                var acl = d.acl.printXML(true);

                d.body        = '<?xml version="1.0"?>' + acl;
                return AXIS.WebDAV.send(d);
              }
          });
      };

/*******************************
 * END ACL
 *******************************/

    
    /**
     * 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.async        = AXIS.WebDAV.isAsync(dav);
        dav.loadingMsg    = dav.loadingMsg || 'DAV::' + dav.method + '::' + dav.url;
        
        /**
         * Make sure sent object has .headers set; if not, empty object.
         */
        dav.headers       = dav.headers || {};

        /*
         * 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>
     * @modified Sandro Pasquali - repackaged as XHR response handler
     */
    this._responseXMLObject = function(withAtts) 
      {
        if(this.httpHandle && this.httpHandle.getResponseHeader('Content-Type').indexOf('xml') > -1 && 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(typeof withAtts == "undefined")
                      {
                        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(AXIS.isArray(obj[childObj.tag]) === false) 
                              {
                                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 {};
          }
      };
      
      // a utility method to check if options has async enabled
      this.isAsync = function(options)
      {
          if( !AXIS.isUndefined(options.async) ) {
              return options.async;
          }
          else if( !AXIS.isUndefined(options.asynch) )
          {
              return options.asynch; // backward compatibility
          }
      };
  };
/**
 * 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.inUserDomain   = 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.registerCode('LB_LOGOUT_INVALID_SIGNOUT', "Couldn't log out of all domains");

        this.onAuthUpdate = AXIS.CustomEvent.create();
        
        this.beforeLogout = AXIS.CustomEvent.create();
        this.afterLogout = AXIS.CustomEvent.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',
                     	  async:				false,
                     	  callId:       'LOGIN_FETCH_DATA'
                     	});

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

        if(AXIS.settings('noLogin') === false)
          {
            AXIS.onDOMReady.subscribe({
              callback: function() {
                this._createAutoLoginFrame();
                this.login();
              },
              scope: 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',
       	  async:       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();
                  },
           	async:       false, 
           	callId:       'GET_AUTH_COOKIE'
              }
            );
          }
      };

    /**
     * Creates the autologin frame in DOM
     *
     * @see #_loadFrameHandler
     */
    this._createAutoLoginFrame = function()
      {
        try
          {
            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;
    
            this._loginFrameWindow.location.replace("about:blank");
    
            AXIS.attachEvent('load',this._loadFrameHandler,frm);
          }
        catch(e){}
      };
     
    /**
     * 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(AXIS.Login.sign_up);
              }
            else
              {
                AXIS.Login._authStateUpdate(false);
              }
            break;
          case "signout":
            AXIS.Cookies.erase('auth');
            AXIS.Cookies.erase('user');
            AXIS.Login._authStateUpdate(false);
            AXIS.Login.afterLogout.fire();
            break;
          case "nosignout":
            new AXIS.Errors.LoginException('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)) : '') + (AXIS.Login.inUserDomain ? '&in_user_domain=true' : '');
      }
         
    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 Everything you ever wanted to know about a bit... but didnt know who to ask
 *
 * @requires  AXIS
 * @requires  Cookies
 * @requires  WebDAV
 * @requires  Util
 */

function Bits() 
  {
    this.__construct = function() 
      {
        this.collection     = [];
        this.cssFiles       = {};
                
        this.cssLoaderFile  = '/!lime/root/apps/bitmix/bitLoader/cssLoader.html';

        this.onBitApiError  = AXIS.CustomEvent.create({
          name: 'onBitApiError',
          wait: true
        });

        /*
        AXIS.onDOMReady.subscribe({
          scope:    this,
          callback: function() {
            this.renderDOMContainers();
          }
        });
        */
      
        AXIS.Bits.onBitApiError.subscribe({
            callback: function(e){
                AXIS.Logger.log('bitapi', 'error', 'onBitAPI', e);
            }
        });
        this.instances = {};
        
        this.ASYNC = null;
        
        this.FETCH_LIMIT = 20;
        
        /**
         * Default bitmarks to be fetched.
         */
        this.BITMARK_NAMES = [ 'description', 'tag', 'rating']; 
        
        /**
         * Default properties to fetch
         */
        this.PROPERTY_NAMES = [[AXIS.WebDAV.ns.d,'resource-id'], [AXIS.WebDAV.ns.d,'lastmodified'], [AXIS.WebDAV.ns.d,'popularity'], [AXIS.WebDAV.ns.d,'owner'], [AXIS.WebDAV.ns.d,'displayname'], [AXIS.WebDAV.ns.lb, 'site-version'], [AXIS.WebDAV.ns.lb, 'tagline'], [AXIS.WebDAV.ns.lb, 'share']]; 
      };
    
    
    /**
     * Returns true if async mode should be used.
     */
    this.isAsync = function( options )
      {
        options = options || {};
        if(typeof(options.async) != 'undefined')
          {
            return options.async;
          }
        else
          {
            return AXIS.Bits.ASYNC;
          }
       };
     
    /**
     * Search for bits at <url>, returns list of bits,
     * @param {Object} url
     * @param {Object} options
     *                                 .callback - @return
     *                                 .customProps {Array of string} - Any extra properties you need
     *                                 .customBitmarks {Array of string} - Any extra bitmarks you need
     *                                 .filterOptions { owner:<val>, tag:<val> } - Any filter options
     *                                 ... - anything you want to override from AXIS.WebDAV
     * @return Array of Bit objects 
     */  
    
    this.findBits = function(url, options)
      {
        options = options || {};
        var defaultOptions = {
          "depth": options.depth ? options.depth : "infinity",
          "async": AXIS.Bits.isAsync(options),
          "where": AXIS.Bits.Utils.search_assists(options.filterOptions),
          "limit": options.limit ? options.limit : AXIS.Bits.FETCH_LIMIT,
          "offset": options.offset ? options.offset : "0",
          "props": options.customProps ? options.customProps.concat( AXIS.Bits.PROPERTY_NAMES ) : AXIS.Bits.PROPERTY_NAMES,
          "url": url,
          "bitmarks": {
                ns: AXIS.WebDAV.ns.bm,
              names: options.customBitmarks ? options.customBitmarks.concat( AXIS.Bits.BITMARK_NAMES ) : AXIS.Bits.BITMARK_NAMES 
          },
          scope: this,
          onSuccess: function(resp) {
              var aBits = [];
              var xmlObj = resp.responseXMLObject();
              if (xmlObj.multistatus) {
                  var response = xmlObj.multistatus.response;
                  response = AXIS.isArray(response) ? response : [response];
                  
                  for (var i = 0; i < response.length; i++) {
                      // depth 1 search includes the URL being queried. If we only need the children, we set ignorePrincipal to true in options
                      if ((options.filterOptions && options.filterOptions.ignorePrincipal) && (response[i].href == url)) {
                          continue;
                      }
                      
                      var j, newBit;
                      var metadata = AXIS.Bits.Metadata.parse_bitmarks(response[i]);
                      for (j = 0; j < AXIS.Bits.collection.length; j++) {
                          if (AXIS.Bits.collection[j].location == response[i].href + '/') {
                              AXIS.Bits.collection[j].metadata = metadata;
                              newBit = AXIS.Bits.collection[j];
                              break;
                          }
                      }
                      if (j == AXIS.Bits.collection.length) { //couldnt find in cache
                          newBit = new AXIS.Bits.Bit();
                          newBit.load(response[i].href, {
                              metadata: metadata
                          });
                      }
                      aBits.push(newBit); // add to list to return
                  }
                  // sort returned list by display name
                  aBits.sort(function(a, b){
                      var x = a.getDisplayName().toLowerCase();
                      var y = b.getDisplayName().toLowerCase();
                      return ((x < y) ? -1 : ((x > y) ? 1 : 0));
                  });
              }
              resp.result = aBits;
              
              AXIS.Util.processCallback(options, [resp.result]);
          },
           
          onFailure: function(resp) {
            AXIS.Logger.log('bitapi', 'info', 'WebDAV search failed', resp);
          }
      	};
          
        var resp = AXIS.WebDAV.SEARCH(defaultOptions);
        if(resp.result)
          {
            return resp.result;
          }
      };
    
    // Return true if bit exists at <url>
    this.exists = function(url, options)
      {
        options = options || {};
        var result = false;
        AXIS.Bits.get(url, {
          async: options.async,
          breakCache: true,
          onFailure: function() {
             result = false; 
          },
          callback: function(){
            result = true;
          }
        });
        return result;
      };
    
    /**
     * Get bit located at <location>
     * @param {Object} options
     *   .bDontFetchMarks - Set to true if you dont want properties/bitmarks with your order
     *   .fresh {boolean} - Set to true if you want to load from backend again
     */
    this.get = function(location, options){
        options = options || {};
        
        if (location.charAt(location.length - 1) !== '/') {
            location += '/';
        }

        if( !options.fresh )
        {
            // try to see if we already have this bit
            for(var i=AXIS.Bits.collection.length-1; i>=0 ; i--) // start from the end so that we get latest one, in case of duplicates 
            {
                if (AXIS.Bits.collection[i].key == location) {
                    AXIS.Util.processCallback(options, AXIS.Bits.collection[i]);
                    return AXIS.Bits.collection[i];
                }
            }
        }
        
        var bit = new AXIS.Bits.Bit(); // create obj to hold bit info
        return bit.load(location, options); // Load obj with info from <location>
      };
    
    /**
     * Bit Class
     */
    this.Bit = function()
    {
        var rand = Math.floor(Math.random()*100000);
        this.location = null;
        this.key = null;
        this.options = {};
        this.metadata = {};
        this.api = {};
        this.loaded = false;
        this.loading = false;
        this.failedLoading = false;
        this.events = {};
        this.events.onLoad = AXIS.CustomEvent.create({ name: 'onLoadBit_' + rand });
        this.manifest = {
            js: [],
            css: [],
            html: [],
            edit: []
        };
        this.content = {
            js: [],
            css: [],
            html: []
        };
        this.events.bitChannel = AXIS.CustomEvent.create({ name: 'bitChannel_' + rand });
    };
        
    this.Bit.prototype = 
    {
         /**
          * Load bit 
          * @param {Object} options
          *   .callback - The function to call after object is loaded (with the bit object as an argument)
          *   .onFailure - What to call if there was a failure 
          *   .customBitmarks - Array of names of additional bitmarks to get
          *   .customProperties - Array of names of additional properties to get
          *   .location - Where bit is located
          * @return this object                                 
          */
         load: function(location, options)
         {
            options = options || {};

            if (location.charAt(0) != '/' && !location.match(/^http:\/\//)) {
                this.location = AXIS.Util.uri.getFolder(window.location.pathname) + location;
            }
            else {
                this.location = location;
            }

            /**
             * Want to be accomodating with path definition: instead of forcing
             * user to have (or not have) a trailing '/' at the end of bit folder,
             * just ensure that there is one.
             */
            if(this.location.charAt(this.location.length-1) !== '/')
              {
                this.location += '/';
              }

            this.key = this.location;

             /**
             * Get the bit folder (which represents the bit name).+
             */
            this.name = AXIS.Util.uri.basename(
                         AXIS.Util.uri.chompSlash(
                          AXIS.Util.uri.getTLDPath(window.location.href.replace(/(http:\/\/[^/]*)(.*)/, '$1') + this.location)));
 
             // If metadata is provided, use that instead of querying for it
            if (options.metadata) {
                this.metadata = options.metadata;
            }
            else if(!options.bDontFetchMarks){
                var defaultOptions = {
                    "depth": "0",
                    "async": AXIS.Bits.isAsync(options),
                    "where": "",
                    "limit": AXIS.Bits.FETCH_LIMIT,
                    "offset": "0",
                    "props": AXIS.Bits.PROPERTY_NAMES,
                    "url": this.location,
                    "bitmarks": {
                        ns: AXIS.WebDAV.ns.bm,
                        names: AXIS.Bits.BITMARK_NAMES
                    },
                    scope: this,
                    callback: function(r){
                        var xmlObj = r.responseXMLObject();
                        if (xmlObj.multistatus) {
                            this.metadata = AXIS.Bits.Metadata.parse_bitmarks(xmlObj.multistatus.response);
                            AXIS.Util.processCallback(options, [this]);
                        }
                    },
                    onFailure: function(r){
                        if (options.onFailure) {
                            options.onFailure();
                        }
                        else {
                            AXIS.Logger.log('bitapi', 'error', 'WebDAV search failed', r);
                        }
                    }
                };
                
                if (options.customBitmarks) {
                    defaultOptions.bitmarks.names = defaultOptions.bitmarks.names.concat(options.customBitmarks);
                }
                
                if (options.customProperties) {
                    defaultOptions.props = defaultOptions.props.concat(options.customProperties);
                }
                
                AXIS.WebDAV.SEARCH(defaultOptions);
            }
            else
            {
                AXIS.Util.processCallback(options, [this]);
            }
            
            // add ourselves to the Bits collection
            this.id = AXIS.Bits.collection.push(this) - 1;
            
            return this;
         },

         /** 
          * Duplicates a bit: Copies it to the current folder giving it a new name
          * @options :
          *  .destFolder (optional) : folder where the bit has to be copied
          *  .bDontFetchMarks : Set to true if you dont want to fetch copied instance with properties/bitmarks 
          *  .callback
          * @return new bit obj 
          */
        copy : function(options)
          {
            options = options || {};
    
            var result;
            var thisScope = this;
            var words = this.location.split('/');
            var folderName = words[words.length-1];
            options.destFolder = options.destFolder ? options.destFolder : '/home/' + this.getOwner() + '/bits/' + folderName;

            AXIS.WebDAV.COPY({
                url: this.location,
                destination: options.destFolder,
                tryAlternateNames: (typeof(options.tryAlternateNames) != 'undefined') ? options.tryAlternateNames : true,
                async: AXIS.Bits.isAsync(options),
                on201: function(r){
                    var destination = r.destination;
                    if (r.alternateName) {
                        destination = AXIS.Util.uri.getParent(destination) + r.alternateName;
                    }

                    AXIS.Bits.get(destination, {
                        async: AXIS.Bits.isAsync(options),
                        bDontFetchMarks: options.bDontFetchMarks,
                        callback: function(bitObj)
                        {
                            result = bitObj;
                            AXIS.Util.processCallback(options, result);
                        }
                    });
                },
                on409: function(r){
                    var words = options.destFolder.split('/');
                    words.pop();
                    
                    AXIS.Util.makeFolder(words.join('/'), words.length, {
                        async: AXIS.Bits.isAsync(options),
                        callback: function(){
                            thisScope.copy(options);
                        }
                    }); // try a max of number of elements in path
                }
            });
            
            return result;                
          }, 

        /**
         * Move bit to /home/<owner>/trash folder
         * @param {Object} options
         *   .callback - After bit is deleted
         */
        remove : function(options) {
            var thisScope = this;
            
            // trash folder is : /home/username/trash
            var trashFolderUrl = "/home/" + this.getOwner() + "/trash";
            
            // Need to ensure that the /trash/ folder exists. Create it if it doesn't
            AXIS.WebDAV.MKCOL({
                headers: {
                    'If-None-Match': '*'
                },
                async: AXIS.Bits.isAsync(options),
                url: AXIS.Bits.Utils.domainize(trashFolderUrl),
                callback: function(response){
                    if (response.httpHandle.status > 210 && response.httpHandle.status != 412) {
                        AXIS.Logger.log("bitapi", 'error', "Couldn't create trash folder",trashFolderUrl);
                    }
                    else {
                        var bitFolderName = thisScope.location.substring(thisScope.location.lastIndexOf("/") + 1);
                        var dest = trashFolderUrl + "/" + bitFolderName;
                        
                        /** 
                         * Do what the editor does too
                         * Need to add a property to the resource which stores its
                         * original location.
                         */
                        AXIS.WebDAV.setProperty({
                            url: thisScope.location,
                            async: AXIS.Bits.isAsync(options),
                            setProperties: {
                                name: 'Editor_restorepath',
                                value: thisScope.location
                            },
                            callback: function(){
                                /**
                                 * Finally, do the actual move...
                                 */
                                AXIS.WebDAV.MOVE({
                                    url: thisScope.location,
                                    async: AXIS.Bits.isAsync(options),
                                    destination: AXIS.Bits.Utils.domainize(dest),
                                    overwrite: true,
                                    onFailure: function(response){
                                        AXIS.Logger.log("bitapi", 'error', "Couldn't delete bit!", response);
                                    },
                                    onSuccess: function(response){
                                        // remove ourselves from Bit collection
                                        AXIS.Bits.collection.splice(thisScope.id, 1);
                                        AXIS.Util.processCallback(options, [thisScope]);
                                    }
                                });
                            }
                        });
                    }
                }
            });
        },
        
        /**
         * Save display name
         * @param {Object} value - New name
         * @param {Object} options
         *                                     .callback, .scope
         * @return this object                                     
         */             
        setDisplayName : function(value, options)
        {
            this.setProperty('displayname', value, {
                scope: this,
                callback: function(){
                    AXIS.Util.processCallback(options, [this]);
                }
            });
            return this;
        },
        getDisplayName : function()
        {
            return this.getProperty('displayname') || "";
        },
        // get name of this bit as if it were a folder in a filesystem...
        getBitFolderName: function()
        {
            return AXIS.Bits.Utils.getBitFolderName(this.location);
        },
        getLocation: function()
        {
            return this.location;
        },
        
        // Get name of owner
        getOwner : function()
        {
            return this.getProperty('owner');
        },
        
        /**
         * Save description
         * @param {Object} value
         * @param {Object} options - .callback, .scope
         * @return this object
         */
        setDescription : function(value, options)
        {
            var bitmark = this.getBitmark('description');
            if( bitmark.getValues().length > 0 )
            {
                bitmark.set(0, value).save(options);
            }
            else
            {
                bitmark.add(value).save(options);
            }
            return this;
        },
        getDescription : function()
        {
            var values = this.getBitmark('description').getValues(); 
            return values.length > 0 ? values[values.length-1] : '';
        },
        
        // Get value of any property
        getProperty : function(name)
        {
            return this._getMetadata(name, { type: AXIS.Bits.Metadata.Types.SimpleProperty }).getValue();
        },
        setProperty : function(name, value, options)
        {
            var options = options || {
                doNotSave: false
            };
            
            var propertyObj = this._getMetadata(name, {type: AXIS.Bits.Metadata.Types.SimpleProperty }).setValue(value);
            
            if (!options.doNotSave) {
                return propertyObj.save(options);
            } else {
                return propertyObj;
            }
        },
        
        // Get any bitmark object (creates a new one if not available)
        getBitmark : function(name)
        {
            return this._getMetadata(name, { type: AXIS.Bits.Metadata.Types.SimpleBitmark } );
        },

        // setter for bitmark is on the bitmark obj itself
        // @return {boolean} - Whether <tagName> is on this bit
        hasTag : function(tagName)
        {
            return AXIS.Util.lang.inArray( tagName, this.getBitmark('tag').getValues() );
        },
        
        /**
         * Get all tags 
         * @return Array of strings                                 
         */
        getTags: function()
        {
            return this.getBitmark('tag').getValues();
        },
        
         // All properties/bitmarks are stored as metadata internally...
         // (optional) options.type specifies which kind of metadata object to create if nothing's found (default is property) 
        _getMetadata : function(name, options)
        {
            if (!this.metadata[name]) {
                options = options ? options : {};
                options.type = options.type ? options.type : AXIS.Bits.Metadata.Types.SimpleProperty;
                this.metadata[name] = AXIS.Bits.Metadata.create(name, this.location, {
                    type: options.type
                });
            }
            return this.metadata[name];
         },
         
         /**
          * render a new instance of the bit
          * @options
          *  .container - DOM node to render into
          *  .datapath - server path for instance information
          * @return BitInstance object
          */
         newInstance: function(options)
         {
             options = options || {};
             options.bit = this;
             var thisScope = this;
             var instance = new AXIS.Bits.BitInstance(options);

             instance.events.onBitRendered.subscribe({
                 justOnce: true,
                 callback: function(d){
                     AXIS.Util.processCallback(options, instance);
                 }
             });

             return instance;
         },
         
         /**
          * Triggers onOptionsUpdate on all its instances
          * @param {Object} data
          * @param {Object} options
          *                                 .except    containerId of instance not to update
          *                                 .bSkipEvent Set to true if you dont want to trigger onOptionsUpdate event
          */ 
         updateInstances: function(data, options)
         {
             options = options || {};
             for( var key in AXIS.Bits.instances)
             {
                 if( AXIS.Bits.instances[key].bit.location == this.location && AXIS.Bits.instances[key].bitContainerId != options.except )
                 {
                     AXIS.Bits.instances[key].updateOptions({
                         data: data,
                         bSkipEvent: options.bSkipEvent 
                     });
                 }
             }
         },
         
         init: function(instance) {
             if (this.loaded) {
                 this.render(instance);
             }
             else {
                 var thisBit = this;
                 if (this.loading) {
                     this.events.onLoad.subscribe({
                         scope: this,
                         callback: function() {
                             this.init(instance);
                         }
                     });
                 }
                 else {
                     this.loading = true;
                     this.getDependencies({
                         callback: function() {
                             thisBit.loaded = true;
                             thisBit.init(instance);
                             thisBit.events.onLoad.fire();
                         }
                     });
                 }
             }
         },

         loadInstanceSpecifics: function() {
             var thisBit = this;
             thisBit.render = function(instance) {
                 var render_helper = function(r) {
                     if (!thisBit.manifest.edit || thisBit.manifest.edit.length == 0) { //default is text editor
                         thisBit.manifest.edit = [{
                             "key": "Text",
                             "type": "Default"
                         }, {
                             "key": "stylebtn",
                             "type": "ChoiceButton",
                             "label": "Customize Style",
                             "onClick": function(){
                                 AXIS.Bits.EditUI.editDialog.dialog('close');
                                 bitMix.functions.invokeCustomizer("text");
                             }
                         }];
                     }
                        thisBit.processManifestForStorage();
                        instance.editOptions = new AXIS.Util.dictionary(thisBit.manifest.edit, instance.options, thisBit.manifest.version);
                        instance.injectHTML();
                        instance.setHandlers();
                        instance.events.onBitRendered.fire(instance);
                 };

                 if (!instance.fetchData) {
                     return setTimeout(render_helper, 10);
                 };

                 var threads = 2;
                 function track() {
                     threads--;
                     if (threads == 0) {
                         render_helper();
                     }
                 };

                 // Check for any instance specific markup
                 AXIS.Bits.Utils.fileLoader(
                    instance.datapath + 'bit.html',
                    null,
                    function(r) {
                        instance.html =  r.responseText;
                    },
                    track
                 );
                 
                 AXIS.Bits.Utils.fileLoader(
                    instance.datapath + 'options.json',
                    null,
                    function(r) {
                        instance.options = AXIS.Util.json.safeEval(r.responseText);
                    },
                    track
                );
             };
         },
        
        // Add hooks for non-json storage types
        processManifestForStorage : function() {
            var thisBit = this;
            var propertiesToFetch = [];
            var bitmarksToFetch = [];
            for(var i=0; i<this.manifest.edit.length; i++)
            {
                if( AXIS.Util.lang.inArray(thisBit.manifest.edit[i].storage, ["property", "bitmark"] ) )
                {
                      if (thisBit.manifest.edit[i].storage == "property") {
                          propertiesToFetch.push([AXIS.WebDAV.ns.lb, thisBit.manifest.edit[i].key]);
                      }
                      else
                      {
                          bitmarksToFetch.push(thisBit.manifest.edit[i].key);
                      }
                      thisBit.manifest.edit[i].get = function() {
                             if( !thisBit.metadata[this.key] )
                             {
                                 thisBit.metadata[this.key] = AXIS.Bits.Metadata.create(this.key, thisBit.location, {
                                     type: this.storage == "property" ? AXIS.Bits.Metadata.Types.SimpleProperty : AXIS.Bits.Metadata.Types.SimpleBitmark
                                 });
                                 thisBit.metadata[this.key].setValue(this['default']);
                             }
                             return thisBit.metadata[this.key].getValue();
                      };  
                      thisBit.manifest.edit[i].set = function(value) {
                             if( !thisBit.metadata[this.key] )
                             {
                                 thisBit.metadata[this.key] = AXIS.Bits.Metadata.create(this.key, thisBit.location, {
                                     type: this.storage == "property" ? AXIS.Bits.Metadata.Types.SimpleProperty : AXIS.Bits.Metadata.Types.SimpleBitmark
                                 });
                             }
                             thisBit.metadata[this.key].setValue(value).save();
                      }
                }
            }
            if (propertiesToFetch.length) {
                AXIS.WebDAV.SEARCH({
                    "depth": "0",
                    "async": false,
                    "where": "",
                    "limit": AXIS.Bits.FETCH_LIMIT,
                    "offset": "0",
                    "props": propertiesToFetch,
                    "bitmarks": {
                        ns: AXIS.WebDAV.ns.bm,
                        names: bitmarksToFetch
                    },
                    "url": this.location,
                    scope: this,
                    callback: function(r){
                        var xmlObj = r.responseXMLObject();
                        if (xmlObj.multistatus) {
                            AXIS.Util.lang.augmentObject(thisBit.metadata, AXIS.Bits.Metadata.parse_bitmarks(xmlObj.multistatus.response));
                        }
                    }
                });
            }      
        },
        
         getDependencies: function(options) {
             var thisBit = this;
             var old_mechanism = function() {
                 AXIS.Bits.Utils.fileLoader(thisBit.location + 'manifest.js', null, function(r){
                     eval("thisBit.manifest = " + r.responseText);
                     thisBit.manifest.js = thisBit.manifest.js || [];
                     thisBit.manifest.css = thisBit.manifest.css || [];
                     thisBit.manifest.html = thisBit.manifest.html || [];
                     thisBit.manifest.edit = thisBit.manifest.edit || [];
                     for (var i = 0; i < thisBit.manifest.edit.length; i++) {
                         for (var name in thisBit.manifest.edit[i]) {
                             if (typeof(thisBit.manifest.edit[i][name]) == 'string' && thisBit.manifest.edit[i][name].indexOf('function') === 0) {
                                 eval("thisBit.manifest.edit[i][name] = " + thisBit.manifest.edit[i][name]);
                             }
                         }
                     }
                     thisBit.manifest.version = thisBit.manifest.version || 0;
                 }, function(r){
                     var z, w, aa, c, j;
                     var loc = thisBit.location;
                     /**
                      * fix relative URLs
                      */
                     var procGrps = function(a, ex){
                         var jsList = [];
                         for (z = 0; z < a.length; z++) {
                             if (a[z].indexOf('http://') === -1 && a[z].match(/^\//) == null) {
                                 a[z] = loc + a[z];
                             }
                             if (ex == 'js' && a[z].indexOf('/bit.js') == -1) {
                                 jsList.push(a[z]);
                                 thisBit.content[ex].push("");
                             }
                             else {
                                 AXIS.Bits.Utils.fileLoader(a[z], null, function(r){
                                     thisBit.content[ex].push(r.responseText);
                                 }, track);
                             }
                         }
                         if (jsList.length) {
                             AXIS.includeScriptChain(jsList.slice(), function(){
                                 for (var i = 0; i < jsList.length; i++) {
                                     track();
                                 }
                             });
                         }
                     };
                     
                     var threads = thisBit.manifest.css.length + thisBit.manifest.js.length + thisBit.manifest.html.length;
                     function track(){
                         threads--;
                         if (threads == 0) {
                             thisBit.loadInstanceSpecifics();
                             AXIS.Util.processCallback(options);
                         }
                     };
                     
                     procGrps(thisBit.manifest.css, 'css');
                     procGrps(thisBit.manifest.js, 'js');
                     procGrps(thisBit.manifest.html, 'html');
                 });          
             };
             
             if ( AXIS.Util.lang.inArray( thisBit.getBitFolderName(), ['richtext_ck', 'codemirror', 'imagepicker', 'imagepickermultiple']) ) {
                 old_mechanism();
             }
             else {
                 AXIS.Bits.Utils.fileLoader(thisBit.location + 'combined.js', null, function(r){
                     eval("thisBit.combined = " + r.responseText);
                     thisBit.manifest = thisBit.combined;
                     thisBit.manifest.js = thisBit.manifest.js || [];
                     thisBit.manifest.css = thisBit.manifest.css || [];
                     thisBit.manifest.html = thisBit.manifest.html || [];
                     thisBit.manifest.edit = thisBit.manifest.edit || [];
                     for (var i = 0; i < thisBit.manifest.edit.length; i++) {
                         for (var name in thisBit.manifest.edit[i]) {
                             if (typeof(thisBit.manifest.edit[i][name]) == 'string' && thisBit.manifest.edit[i][name].indexOf('function') === 0) {
                                 eval("thisBit.manifest.edit[i][name] = " + thisBit.manifest.edit[i][name]);
                             }
                         }
                     }
                     thisBit.manifest.version = thisBit.manifest.version || 0;
                     thisBit.content.js = thisBit.combined.content.js || [];
                     thisBit.content.css = thisBit.combined.content.css || [];
                     thisBit.content.html = thisBit.combined.content.html || [];
                 }, function(bExists){
                     if (bExists) {
                         var z, w, aa, c, j;
                         var loc = thisBit.location;
                         var procGrps = function(a, ex){
                             for (z = 0; z < a.length; z++) {
                                 if (a[z].indexOf('http://') === -1 && a[z].match(/^\//) == null) {
                                     a[z] = loc + a[z];
                                 }
                             }
                         };
                         procGrps(thisBit.manifest.css, 'css');
                         procGrps(thisBit.manifest.js, 'js');
                         procGrps(thisBit.manifest.html, 'html');
                         
                         thisBit.loadInstanceSpecifics();
                         AXIS.Util.processCallback(options);
                     }
                     else {
                         old_mechanism();
                     }
                 });
             }
         }
    };

    /**
     * BitInstance class
     */
    this.BitInstance = function(options) {
        var rand = Math.floor(Math.random()*100000);
        this.bit = options.bit;
        this.container = options.container || false;
        this.datapath = options.datapath || this.bit.location + 'data/';
        this.renderOptions = options.renderOptions || {};
        this.bitContainerId = 'bit_div'+AXIS.Util.random_num();
        this.events = {};
        this.events.onOptionsUpdate = AXIS.CustomEvent.create({ name: 'onOptionsUpdate_' + rand });
        this.events.onLoad = AXIS.CustomEvent.create({ name: 'onLoad_' + rand });
        this.events.onBitRendered  = AXIS.CustomEvent.create({ name: 'onBitRendered_' + rand });
        this.events.onBitResized = AXIS.CustomEvent.create({ name: 'onBitResized_' + rand });
        this.events.bitInstanceChannel = AXIS.CustomEvent.create({ name: 'bitInstanceChannel_' + rand }); 
        this.options = {};
        this.fetchData = (typeof(options.fetchData) != 'undefined') ? options.fetchData : true;

        // add ourselves to Bits.instances
        AXIS.Bits.instances[this.bitContainerId] = this;

        this.insertDOMContainer();
    };

    this.BitInstance.prototype = {
        getContainer: function() {
            return this.bitContainer;
        },

        insertDOMContainer: function() {
            var thisInstance = this;
            /**
             * Can only do this if we have a document... Call back when ready.
             */
            if(AXIS.onDOMReady.hasFired() === false)
              {
                AXIS.onDOMReady.subscribe({
                  callback: function() {
                    thisInstance.insertDOMContainer();
                  }
                });
                
                return;
              }

            this.bitContainer = AXIS.Element.create('div', {
              'id':                 thisInstance.bitContainerId,
              'class':              'bit',
              'data-bit-location':  thisInstance.bit.location,
              'data-bit-datapath':  thisInstance.datapath,
              appendTo:             thisInstance.container
            });

            this.renderDOMContainer();
        },

        renderDOMContainer: function() {
            var bId = this.bitContainerId;
            var bDRef = this.bitContainer;

            /**
             * If no #id, or #location, or already initialized, exit.
             */
            if( bId === false   || 
                this.bit.location === false  || 
                bDRef.getAttribute('data-bit-initialized'))
              {
                return;  
              }

            var bClass = bDRef.getAttribute('class') || '';

            this.events.onBitRendered.subscribe({
                justOnce: true,
                scope: this,
                callback: function(d){
                        /**
                         * Indicate that it has been initialized.  This is done so any
                         * future insertion of bit containers into a page (which will
                         * probably be followed by another Bits#initialization) does not
                         * trigger re-initialization.
                         */
                        bDRef.setAttribute('data-bit-initialized', 1);
                }
            });

            /**
             * Add a namespace for this bit by adding to the registered class names
             * for this instance (adding bit name).  See the cssLoader.html file,
             * which will preface all css rules with a classSelector with bName.
             */
            bDRef.setAttribute('class', bClass + ' ' + this.bit.name);

            /**
             * Initialize the bit
             */
            this.bit.init(this);
        },

        /**
         * Invoke edit interface for this bit 
         * @param {Object} options
         *    .onBegin
         *    .onEnd 
         * @return this object
         */
        edit: function(options) {
            options = options || {};
            var self = this;

            AXIS.Bits.EditUI.create();
            AXIS.Bits.EditUI.bitInstance = this;
            AXIS.Bits.EditUI.add(this.editOptions, {
                onClose: function(){
                    if (self.editOptions.commit(self.datapath + 'options.json')) {
                        self.events.onOptionsUpdate.fire(self.editOptions.all(true));
                    }
                    if( options.onEnd )
                    {
                        options.onEnd();
                    }
                },
                onOpen: function() {
                    if( options.onBegin )
                    {
                        options.onBegin();
                    }
                }
            });
       },
        
         saveContent: function(data, options)
         {
             this.html = data;
             AXIS.Util.saveData(this.datapath + "bit.html", data, options);    
             this.injectHTML({ bMarkupOnly: true });
         },
         
         /**
          * Updates editOptions of this instance
          * @param {Object} data - New editOptions name-value pairs
          * @param {Object} options
          *                                 .data    -  Use these editOptions hash instead of fetching from server again
          *                                 .bSkipEvent - Set to true if you dont want onOptionsUpdate to fire
          */
         updateOptions: function(options)
         {
             options = options || {};
             var thisScope = this;
             var oldCallback = options.callback;
             options.callback = function() {
                 if(!options.bSkipEvent)
                 {
                     thisScope.events.onOptionsUpdate.fire(thisScope.editOptions.all(true));
                 }
             }
             if (this.editOptions) // if there are any options to update
            {
                options.url = this.datapath + 'options.json';
                this.editOptions.update(options);
            }
         },
        
         injectHTML: function(options){
             options = options || {};
             var html = AXIS.Bits.Utils.translateRelativePaths(this.html || this.bit.content.html.join(''), this.bit.location);
             var styleRegex = new RegExp('(<style[^>]*>([\\S\\s]*?)<\/style>)', 'gim');
             var scriptRegex = new RegExp('(<script[^>]*>([\\S\\s]*?)<\/script>)', 'gim');
             var matches, markup = html.replace(styleRegex, '').replace(scriptRegex, '');
             var editOps = options.editOps || this.editOptions.all(true);
             var jContainer = jQuery(this.bitContainer);
             var bOnLoadDefined = false;
             
            // First attach styles
            if (!options.bMarkupOnly) {
                for(var i=0; i<this.bit.content.css.length; i++ )
                {
                    var words = this.bit.manifest.css[i].split('/');
                    words[words.length - 1] = '';
                    AXIS.CSS.attachNewSheet(AXIS.CSS.encapsulate(this.bit.content.css[i], this.bit, words.join('/')), document);
                }
                styleRegex.lastIndex = 0;
                while ((matches = styleRegex.exec(html)) != null) {
                    AXIS.CSS.attachNewSheet(AXIS.CSS.encapsulate(matches[2], this.bit), document);
                }
            }
            
            if (markup.indexOf('<%') > -1) {
                // set template vars width/height
                if (editOps.proportional) {
                    editOps.height = options.width ? options.width * editOps.height / editOps.width : editOps.height;
                }
                else {
                    editOps.height = options.height || editOps.height;
                }

                editOps.width = options.width || editOps.width;
                
                // Pass in system variables for templater
                editOps['_BitData'] = this;
                markup = AXIS.Util.tmpl(markup, editOps);
            }

            // Then markup
             if (!editOps.iframe) {
                 this.bitContainer.innerHTML = markup;
             }
             else
             {
                 var width = jContainer.parents('.containerContent').width();
                 editOps.width = editOps.width || width;
                 editOps.height = editOps.height || width;
                 var aframe = jQuery('<iframe width=' + editOps.width + ' height=' + editOps.height + '></iframe>');
                 jQuery(this.bitContainer).html(aframe);
                 var doc = aframe.get(0).contentWindow.document;
                 doc.open();
                 styleRegex.lastIndex = 0;
                 while ((matches = styleRegex.exec(html)) != null) {
                     doc.writeln(matches[1]);
                 }
                 doc.writeln(markup);
                 scriptRegex.lastIndex = 0;
                 while ((matches = scriptRegex.exec(html)) != null) {
                     doc.writeln(matches[1]);
                 }
                 doc.close();
                 return; // we've loaded everything into iframe... exit
             }

             // Then scripts
             if (!options.bMarkupOnly) {
                for(var i=0; i<this.bit.content.js.length; i++ )
                {
                    if( this.bit.manifest.js[i].indexOf('bit.js') > -1 )
                    {
                        bOnLoadDefined = this.bit.content.js[i].match(/onLoad.fire/) ? true : false;
                        AXIS.Util.attachNewScript('(function(BitData){ try { ' + this.bit.content.js[i] + ' } catch(e){ AXIS.Logger.log("bitjs", "error", e); } })(AXIS.Bits.instances["' + this.bitContainerId + '"]) ');
                    }
                    else
                    {
                        AXIS.Util.attachNewScript(this.bit.content.js[i]);
                    }
                }
                 
                scriptRegex.lastIndex = 0;
                while ((matches = scriptRegex.exec(html)) != null) {
                    if (matches[2]) {
                        var scriptToAttach = '(function(BitData){ try { ' + matches[2] + ' } catch(e){ AXIS.Logger.log("bitjs", "error", e); } })(AXIS.Bits.instances["' + this.bitContainerId + '"]) ';
                        AXIS.Util.attachNewScript(scriptToAttach);
                    }
                    else {
                        jQuery('head').append(matches[1]);
                    }
                }
                
                if (!bOnLoadDefined && !html.match(/onLoad.fire/)) {
                    AXIS.Util.attachNewScript('AXIS.Bits.instances["' + this.bitContainerId + '"].events.onLoad.fire();');
                }
            }
         },

        setHandlers: function() {
            var html = this.html || this.bit.content.html.join('');
            var js = this.bit.content.js.join('');
            
            // Set handlers if the bit doesnt do it on its own
            if (!js.match(/onOptionsUpdate.subscribe/) && !html.match(/onOptionsUpdate.subscribe/)) { 
                // we need to subscribe to options update so we can pick up any changes thru edit ui
                this.events.onOptionsUpdate.subscribe({
                    wait: true,
                    scope: this,
                    callback: function(){
                        this.injectHTML({ bMarkupOnly: true });
                    }
                });
            }

            // set resize handler for bits that need to specify their height/width
            if (html.match('<%=width%>')) {
                // we need to subscribe to resize events so that we can redraw ourselves
                this.events.onBitResized.subscribe({
                    wait: true,
                    scope: this,
                    callback: function(ev){
                        this.injectHTML({ 
                            bMarkupOnly: true,
                            width: ev.data.width,
                            height: ev.data.height
                        });
                    }
                });
            }
            
            var self = this;
            jQuery(this.bitContainer).resize(function() {
                var objRef = jQuery(this).closest('.container').attr('objRef');
                if (objRef) {
                    objRef.refreshPosition();
                }
            });
        }         
    };

    /**
     * Used to manage edit dialogs of bits
     */
    this.EditUI = {
        editDialog: null,
        bitInstance: null,
        
        // Simply set the container to use (jquery element)  
        create: function(name)
        {
            var self = this;
            jQuery('#bitEditDialog').remove();
            self.editDialog = jQuery("<div id='bitEditDialog'></div>").appendTo('body');
            self.editDialog.dialog({
                modal: true,
                draggable: true,
                autoOpen: false,
                closeOnEscape: true,
                width: 665,
                height: "auto",
                minHeight: 100,
                maxHeight: 500,
                resizable: false,
                position: [400, 200],
                dialogClass: 'limebits'
            });
            self.editDialog.bind('dialogclose', function(){
                self.editDialog.dialog('destroy');
                self.editDialog.unbind();
                self.editDialog.remove();
            });
            self.editDialog.bind('dialogopen', function(){
                if( !name && self.bitInstance )
                {
                    name = self.bitInstance.bit.getDisplayName();
                }
                self.editDialog.dialog('option', 'title', name);
                self.editDialog.dialog('option', 'position', [400,200]);

                if (name.indexOf("Picker") > -1) {
                    setTimeout(function(){ //sometimes the positioning still doesnt work
                        self.editDialog.dialog('option', 'position', [400, 200]);
                    }, 10);
                }
            });
        },
        
        save: function()
        {
            this.editDialog.find('.dialog-ok').click();
        },
        
        add: function(editOptionsStorage, options)
        {
            options = options || {};
            var rand = Math.floor(Math.random()*100000);
            var self = this;
            var editUI = jQuery('<div class="ui-edit-dialog-component" style="float:left; height: 100%; width: 100%"></div>').appendTo(self.editDialog).hide();
            var events = {
                onDialogOpened: AXIS.CustomEvent.create({ name: 'onDialogOpened_' + rand }),
                onDialogClosing: AXIS.CustomEvent.create({ name: 'onDialogClosing_' + rand })
            };
            
            // special case for user using cross button
            self.editDialog.bind('dialogbeforeclose', function(){
                if (self.editDialog.find('.ui-edit-dialog-component').length) { // we got here from user clicking on cross button
                    events.onDialogClosing.fire( 'cancel' );
                    editOptionsStorage.rollback();
                    for( var eventName in events )
                    {
                        events[eventName].unsubscribeAll();
                    }
                    if (options.onClose) {
                        setTimeout(function() { // i cannot tell my caller i've closed until i've really closed
                            options.onClose();
                        }, 10);
                    }
                }
            });
            
            this._createEditUI(editUI, editOptionsStorage, {
                events: events,
                onRender: function(){
                    if (self.editDialog.dialog('isOpen')) {
                        self.editDialog.find('.ui-edit-dialog-component').hide();
                        editUI.show();
                    }
                    else {
                        editUI.show();
                        self.editDialog.dialog('open');
                    }
                    events.onDialogOpened.fire();
                    if (options.onOpen) {
                        options.onOpen();
                    }
                },
                onClose: function() {
                    editUI.remove();
                    for( var eventName in events )
                    {
                        events[eventName].unsubscribeAll();
                    }
                    
                    if (options.onBeforeClose) {
                        options.onBeforeClose();
                    }
                    // close the dialog if we're done with the last one
                    if (self.editDialog.find('.ui-edit-dialog-component').length == 0) {
                        self.editDialog.dialog('close');
                    }
                    else // show the previous one...
                    {
                        jQuery(self.editDialog.find('.ui-edit-dialog-component').get(-1)).show();
                    }
                    if (options.onClose) {
                        options.onClose();
                    }                    
                }
            });
        },
        
        /**
         * 
         * @param {Object} elem - Where to render it (jQuery element)
         * @param {Object} editOps - dictionary object
         * @param {Object} options - 
         *                                 .onRender - triggered when components have been rendered
         */
        addEditComponents: function(elem, editOptionsStorage, options)
        {
            options = options || {};
            var self = this;            
            var editOps = editOptionsStorage.all();

            var renderCntr = editOps.length;
            var renderCntFunc = function() {
               renderCntr = renderCntr - 1; 
               if( renderCntr == 0 && options.onRender)
               {
                   options.onRender();
               }
            };
            
            var sectionContainer;
            for (var w = 0; w < editOps.length; w++) {
                editOps[w].type = editOps[w].type || 'String';     // default to 'String' type
                editOps[w].section = typeof(editOps[w].section) == 'undefined' ? 0 : editOps[w].section;
                
                // no section
                if(editOps[w].section === null)
                {
                    sectionContainer = elem;
                }
                else
                {
                    sectionContainer = jQuery('#dialog-section-'+editOps[w].section, elem); 
                    if( sectionContainer.length == 0 ) 
                    { // create new section
                        sectionContainer = jQuery('<div class="ui-dialog-section" id="dialog-section-'+editOps[w].section+'"></div>'); 
                        elem.append(jQuery('<div class="ui-dialog-section-wrapper"></div>').append(sectionContainer));    
                    }
                }
                
                var item = jQuery('<div class="ui-dialog-component" id="' + editOps[w].key + '"></div>');
                sectionContainer.append(item);
                
                if (typeof(this["_render" + editOps[w].type]) == 'undefined') {
                    this._renderEditBit(editOps[w].type, item, editOps[w], {
                        events: options.events,
                        callback: renderCntFunc
                    });
                }
                else {
                    this["_render" + editOps[w].type](item, editOps[w], {
                        events: options.events,
                        callback: renderCntFunc
                    });
                }
            }
        },
        
        changeValue: function(editOp, value)
        {
            jQuery('#'+editOp.key+' input', jQuery('#bitEditDialog')).val(value);    
        },
        
        _createEditUI: function(elem, editOptionsStorage, options) {
            var self = this;
            
            var dialogComponents = jQuery('<div class="dialog-components"></div>');
            elem.append(dialogComponents);
            
            /* add bottom bar buttons */
            var bottomBar = jQuery('<div class="bottom-content"></div>');
            var applyBtn = jQuery('<button class="dialog-ok ui-button ui-corner-all ui-state-default ui-priority-primary">OK</button>');
            var cancelLink = jQuery('<a class="dialog-cancel ui-state-default ui-priority-secondary">Cancel</a>');
            applyBtn.hover(function() { $(this).addClass("ui-state-hover") }, function() { $(this).removeClass("ui-state-hover") });
            cancelLink.hover(function() { $(this).addClass("ui-state-hover") }, function() { $(this).removeClass("ui-state-hover") });
            bottomBar.append(applyBtn).append(cancelLink);
            elem.append(bottomBar);

            var onApply = function() {
                options.events.onDialogClosing.fire('ok');
                options.onClose();
            }
            applyBtn.click(function() {
                onApply();
            });
            cancelLink.click(function() {
                editOptionsStorage.rollback();
                options.events.onDialogClosing.fire( 'cancel' );
                options.onClose();
            });
            // default action for enter button
            elem.bind('keypress', function(e) {
                if( e.keyCode == 13 )
                {
                    // Need to trigger onApply asynchronously or else onChange wont be triggered before we collect the changes
                    setTimeout(onApply, 1);
                 }
            });

            this.addEditComponents(dialogComponents, editOptionsStorage, options);
        },
        
         _renderGrid: function(elem, editOps, options) {
             options = options || {};
             options.renderOptions = editOps; 
             options.renderOptions.events = options.events;
             
             var grid = AXIS.Bits.get('/!lime/root/apps/bitmix/bits/grid');
             // fix relative paths
             
             if (editOps.dataPath.charAt(0) != '/') {
                 if (AXIS.Bits.EditUI.bitInstance.datapath.match(/\/$/)) {
                     editOps.dataPath = AXIS.Bits.EditUI.bitInstance.datapath + editOps.dataPath;
                 }
                 else {
                     editOps.dataPath = AXIS.Bits.EditUI.bitInstance.datapath + '/' + editOps.dataPath;
                 }
             }
            
             options.container = elem[0];
             grid.newInstance(options);
         },
         
         _renderEditBit: function(name, elem, editOps, options) {
             options = options || {};
             options.renderOptions = editOps; 
             options.renderOptions.events = options.events;

             var newBit = AXIS.Bits.get('/!lime/root/apps/bitmix/bits/'+name.toLowerCase());
             options.container = elem[0];
             newBit.newInstance(options);
         },
                     
         _renderInteger: function(elem, editOps, options) {
                var self = this;
                
                var inputTemplate = '<input class="text ui-widget-content ui-corner-all" id="input_<!--input_id-->" type="text" name="input_<!--input_id-->" value="<!--value-->"\ >';
                var labelTemplate = '<label class="dialog-label" for="input_<!--input_id-->"><!--label--></label>';
                                                                
                var inputHtml = inputTemplate.replace(/<!--input_id-->/g, editOps.key).replace(/<!--value-->/g, editOps.getAttributeAsString('value'));
                var labelHtml = labelTemplate.replace(/<!--input_id-->/g, editOps.key).replace(/<!--label-->/g, editOps.getAttributeAsString('label'));
                
                var inputElem = jQuery(inputHtml);
                
                elem.append(labelHtml);
                elem.append(jQuery('<div class="dialog-input"></div>').append(inputElem));
                
                elem.append('<div class="dialog-input-help left-padded">' + editOps.getAttributeAsString('help') + '</div>');
                inputElem.spinner({max:editOps.max,min:editOps.min});
                
                inputElem.bind('spinchange', function(evt, ui) {
                    editOps.change(jQuery(evt.currentTarget).attr("value"));
                });
                AXIS.Util.processCallback(options,self);
         },

         _renderString: function(elem, editOps, options) {
             var self = this;
             var userInput = jQuery('<input class="text ui-widget-content ui-corner-all" type="text" >'); 
            
            if( !editOps.isValueDefault() )
            {
                userInput.attr("value", editOps.getAttributeAsString('value'));
            }                                                                             
            else
            {
                userInput.attr("value", editOps.getAttributeAsString('default') ).addClass('ui-hint-text');
            }
             elem.append('<label class="dialog-label" for="input_' + editOps.key + '">' + editOps.getAttributeAsString('label') + '</label>');
             elem.append(jQuery('<div class="dialog-input"></div>').append(userInput));
             elem.append('<div class="dialog-input-help left-padded">' + editOps.getAttributeAsString('help') + '</div>');
             
             userInput.change( function() {
                 editOps.change(userInput.attr("value"));
                 if( editOps.onChange )
                 {
                     editOps.onChange(userInput.attr("value"));
                 }
             });
             userInput.focus(function(){
                 if( userInput.attr("value") == editOps.getAttributeAsString('default') )
                 {
                     userInput.attr("value", "").removeClass('ui-hint-text');
                 }
                 else
                 {
                     userInput.select();    
                 }
             });
             userInput.blur( function() {
                 if( userInput.attr("value") == "" )
                 {
                     userInput.attr("value", editOps.getAttributeAsString('default')).addClass('ui-hint-text');
                 }
             });

             if (typeof(editOps.onRender) == 'function') {
                editOps.onRender(userInput);
             }

             AXIS.Util.processCallback(options, self);
         },

         _renderImage: function(elem, editOps, options) {
             options = options || {};
             options.renderOptions = editOps;
             options.renderOptions.events = options.events;             
             
             if (!editOps.dataPath) {
                 editOps.dataPath = AXIS.Bits.EditUI.bitInstance.datapath;
             }

             elem.parent().addClass('widescreen'); // i need space!
             elem.append(editOps.getAttributeAsString('label'));
             options.container = elem[0];
             var instance = AXIS.Bits.get('/!lime/root/apps/bitmix/bits/imagepicker').newInstance(options);
        },
        
        _renderImagePickerMultiple: function(elem, editOps, options) {
             options = options || {};
             options.renderOptions = editOps;
             options.renderOptions.events = options.events;             
             
             if (!editOps.dataPath) {
                 editOps.dataPath = AXIS.Bits.EditUI.bitInstance.datapath;
             }

             elem.append(editOps.getAttributeAsString('label'));
             options.container = elem[0];
             var instance = AXIS.Bits.get('/!lime/root/apps/bitmix/bits/imagepickermultiple').newInstance(options);
        },

        _renderRichText: function(elem, editOps, options) {
             options = options || {};
             options.renderOptions = editOps;
             options.renderOptions.events = options.events;

             options.container = elem[0];

             AXIS.Bits.get('/!lime/root/apps/bitmix/bits/richtext_ck').newInstance(options);
        },
        
        // default is text editor that saves to bit.html
        _renderDefault: function(elem, editOps, options) {
             options = options || {};
             options.renderOptions = editOps;
             options.renderOptions.events = options.events;

             elem.parent().parent().addClass('widescreen'); // i need space!
             options.container = elem[0];

             editOps.get = function(){
                 return AXIS.Bits.EditUI.bitInstance.html || AXIS.Bits.EditUI.bitInstance.bit.content.html.join(" ");
             };
             
             editOps.set = function(value){
                 AXIS.Bits.EditUI.bitInstance.saveContent(value);
             };

             AXIS.Bits.get('/!lime/root/apps/bitmix/bits/richtext_ck').newInstance(options);
        },
        
        _renderCodeSnippet: function(elem, editOps, options) {
             options = options || {};
             options.renderOptions = editOps;
             options.renderOptions.events = options.events;
             options.renderOptions.height = '60px';
             
             var codeContainer = jQuery('<div class="dialog-code-snippet"></div>');
             elem.append('<label class="dialog-label" style="text-align:left" for="input_' + editOps.key + '">' + editOps.getAttributeAsString('label') + '</label>');
             elem.append(codeContainer);
             elem.append('<div class="dialog-input-help">' + editOps.getAttributeAsString('help') + '</div>');

             options.container = codeContainer[0];
             
             editOps.get = function(){
                 return AXIS.Bits.EditUI.bitInstance.html || AXIS.Bits.EditUI.bitInstance.bit.content.html.join(" ");
             };
             
             editOps.set = function(value){
                 AXIS.Bits.EditUI.bitInstance.saveContent(value);
             };
             
             AXIS.Bits.get('/!lime/root/apps/bitmix/bits/codemirror').newInstance(options);
        },
            
        _renderCode: function(elem, editOps, options) {
             options = options || {};
             options.renderOptions = editOps;
             options.renderOptions.events = options.events;
             options.renderOptions.height = '300px';

             elem.parent().addClass('widescreen'); // i need space!
             options.container = elem[0];
             
             editOps.get = function(){
                 return AXIS.Bits.EditUI.bitInstance.html || AXIS.Bits.EditUI.bitInstance.bit.content.html.join(" ");
             };
             
             editOps.set = function(value){
                 AXIS.Bits.EditUI.bitInstance.saveContent(value);
             };
             
             AXIS.Bits.get('/!lime/root/apps/bitmix/bits/codemirror').newInstance(options);
        },
        
        // a dummy interface for hidden options
        _renderHidden: function(elem, editOps, options) {
            elem.remove(); 
            AXIS.Util.processCallback(options, this);
        },
        
        _renderIntroduction: function(elem, editOps, options) {
            elem.append('<div class="ui-dialog-section-help">'+editOps.getAttributeAsString('help')+'</div>');
            AXIS.Util.processCallback(options, this);
        },
        
        _renderLinkPicker: function(elem, editOps, options) {
             options = options || {};
             options.renderOptions = editOps;
             options.renderOptions.events = options.events;

             options.container = elem[0];
             AXIS.Bits.get('/!lime/root/apps/bitmix/bits/linkpicker').newInstance(options);
        },
        
        _renderDate: function( elem, editOps, options) {
             var self = this;
             var value = editOps.value();
             if( value == '' )
             {
                 var d = new Date();
                 value = d.getMonth() + '/' + d.getDate() + '/' + d.getFullYear(); 
             }
             
             var userInput = jQuery('<input class="text ui-widget-content ui-corner-all" type="text" >').attr("value", value); 
            
             elem.append('<label class="dialog-label" for="input_' + editOps.key + '">' + editOps.getAttributeAsString('label') + '</label>');
             elem.append(jQuery('<div class="dialog-input"></div>').append(userInput));
             elem.append('<div class="dialog-input-help left-padded">' + editOps.getAttributeAsString('help') + '</div>');
             
             userInput.change( function() {
                 editOps.change(userInput.attr("value"));
             });
             AXIS.Util.processCallback(options, self);
        },
        
        _renderDropdown: function(elem, editOps, options) {
            var self = this;
            
            var choices = editOps.choices;
            var userInput = jQuery('<select class="dropdown ui-corner-all"></select>');

            elem.append('<label class="dialog-label" for="input_' + editOps.key + '">' + editOps.getAttributeAsString('label') + '</label>');
            elem.append(jQuery('<div class="dialog-input"></div>').append(userInput));
            elem.append('<div class="dialog-input-help left-padded">' + editOps.getAttributeAsString('help') + '</div>');

            if (choices.length) {
                for( var i=0; i<choices.length; i++)
                {
                    userInput.append(jQuery('<option></option>').val(choices[i]).html(choices[i]));
                    if (choices[i] == editOps.value()) {
                        userInput.find('option:last').attr('selected', 'selected');
                    }
                }
            }
            else {
                for (var name in choices) {
                    userInput.append(jQuery('<option></option>').val(choices[name]).html(name));
                    if (choices[name] == editOps.value()) {
                        userInput.find('option:last').attr('selected', 'selected');
                    }
                }
            }
             userInput.change( function() {
                 editOps.change(userInput.attr("value"));
                 if( editOps.onChange )
                 {
                     editOps.onChange(userInput.attr("value"));
                 }
             });
            AXIS.Util.processCallback(options, self);                        
        },
        
        _renderBoolean: function( elem, editOps, options) {
            var self = this;
            var userInput = jQuery('<input type="checkbox" class="checkbox ui-widget-content ui-corner-all" ></input>');

            elem.append('<label class="dialog-label checkbox" for="input_' + editOps.key + '">&nbsp;</label>');
            elem.append(jQuery('<div class="dialog-input checkbox"></div>').append(userInput).append(editOps.getAttributeAsString('label')));
            elem.append('<div class="dialog-input-help left-padded">' + editOps.getAttributeAsString('help') + '</div>');
            
            if( editOps.value() )
            {
                userInput.attr('checked', 'checked');
            }
            userInput.change( function() {
                 editOps.change(userInput.attr("checked"));
             });
            AXIS.Util.processCallback(options, self);
        },
        
        _renderChoiceButton: function( elem, editOps, options)
        {
            // the choice button goes in a different place
            elem = elem.parents('.dialog-components').siblings('.bottom-content');
            var choiceBtn = jQuery('<button class="dialog-choice ui-button ui-corner-all ui-state-default">'+editOps.label+'</button>');
            elem.append(choiceBtn);
            choiceBtn.hover(function() { $(this).addClass("ui-state-hover") }, function() { $(this).removeClass("ui-state-hover") });
            choiceBtn.click(function() {
                if(editOps.onClick)
                {
                    editOps.onClick();
                }
            });
            AXIS.Util.processCallback(options, self);
        }
    };
        
    // Misc utils that can be moved somewhere else if needed...
    this.Utils = {
        // Calls callback if specified, passing 
        processCallback: function(options, args){
            AXIS.Util.processCallback(options, args);
        },
        
        inArray: function(needle, haystack){
            if (!AXIS.isArray(haystack)) {
                return false;
            }
            for (var i = 0; i < haystack.length; i++) {
                if (haystack[i] == needle) {
                    return true;
                }
            }
            return false;
        },
        
        // An easy way to specify popular search queries    
        search_assists: function(filterOptions){
            filterOptions = filterOptions ? filterOptions : {};
            var whereQuery = (filterOptions.application && filterOptions.application == 'bitmix') ? '' : AXIS.WebDAV.SEARCH.is_bit();
            for (option in filterOptions) {
                if (option == 'tag') {
                    whereQuery = AXIS.WebDAV.SEARCH.and(whereQuery, AXIS.WebDAV.SEARCH.eq([AXIS.WebDAV.ns.bm, "tag"], filterOptions.tag, {
                        "ns": AXIS.WebDAV.ns.bm,
                        "name": "bitmark"
                    }));
                }
                else 
                    if (option == 'owner') {
                        whereQuery = AXIS.WebDAV.SEARCH.and(whereQuery, AXIS.WebDAV.SEARCH.eq([AXIS.WebDAV.ns.d, "owner"], filterOptions.owner, {
                            "ns": AXIS.WebDAV.ns.d,
                            "name": "prop"
                        }));
                    }
				else if (option == 'tags') {
					// we pass in an array of tags. Only an array of two is currently supported
					whereQuery = AXIS.WebDAV.SEARCH.and(whereQuery, AXIS.WebDAV.SEARCH.or(
																		AXIS.WebDAV.SEARCH.eq([AXIS.WebDAV.ns.bm, "tag"], filterOptions.tags[0], {
												                        	"ns": AXIS.WebDAV.ns.bm,
												                        	"name": "bitmark"
                    													}),
																		AXIS.WebDAV.SEARCH.eq([AXIS.WebDAV.ns.bm, "tag"], filterOptions.tags[1], {
												                        	"ns": AXIS.WebDAV.ns.bm,
												                        	"name": "bitmark"
                    													})
								));
				}
				else if (option == 'is_collection') {
					// looking only for folders, aka, dav collections
					whereQuery = AXIS.WebDAV.SEARCH.and(whereQuery, AXIS.WebDAV.SEARCH.is_collection());
				}
            }
            return whereQuery;
        },
        
        /**
         * Get proper url, domainized according to <level> 
         * @param {Object} location - the path
         * @param {Object} options
         *                                 .level - (top/user/site) the level the url should be in (default is current level) 
         *                                 .owner - set to a specific user's level if you want (default is the user in whose domain we are)
         *                                 .limeroot (boolean) - set to true if you want to force into a url from !lime/root (useful when you're trying to access top level urls from user level)
         */ 
        domainize: function(location, options)
        {
            options = options ? options : {};
            
            if( location.indexOf('http://') === 0 || location.match(/^\/\!lime\/root\//) ) 
            {
                return location; // we dont have ability to work on absolute locations yet
            }
            
            location = location.charAt(0) == '/' ? location : '/' + location;
            var root = AXIS._siteData.hosts.limebits.match(/www(\.[^\/]*)/)[1];
            
            if (window.location.href.indexOf(AXIS._siteData.hosts.limebits) === 0) { // we are at the top level, so we are in the logged in user's domain
                options.owner = options.owner || AXIS.User.username();
                options.level = options.level || 'top';
            }
            else { // get from the url
                var x = window.location.host.replace(root, '').split('.');
                options.owner = options.owner || x[1] || x[0];
                if (x[1]) {
                    options.level = options.level || 'site';
                }
                else 
                    if (x[0]) {
                        options.level = options.level || 'user';
                    }
            }
            
            var base;
            if( options.level == 'top' )
            {
                base = 'www' + root;
                location = location.replace('^/!lime/root', ''); // get rid of root reference in url
            }
            else if( options.level == 'user' )
            {
                base = options.owner + root;
                location = location.replace('^/!lime/root/home/' + options.owner, '')
                                                .replace('^/home/' + options.owner, ''); // get rid of owner reference in url
            }
            else if( options.level == 'site' )
            {
                // try to get name of site from location or url
                var matches, sitename;
                if( (matches = location.match(new RegExp('^/bits/([^/]*)')) ) != null )
                {
                    sitename = matches[1];
                }
                else if( (matches = location.match(new RegExp('/home/' + options.owner+'/bits/([^/]*)')) ) != null )
                {
                    sitename = matches[1];
                }
                else
                {
                    sitename = window.location.host.replace(root, '').split('.')[0];
                }
                base = sitename + '.' + options.owner + root;
                location = location.replace(new RegExp('^/!lime/root/home/' + options.owner+'/bits/[^/]*'), '')
                                              .replace(new RegExp('^/home/' + options.owner+'/bits/[^/]*'), '')
                                              .replace(new RegExp('^/bits/[^/]*'), ''); // get rid of site level references in url
            } 
            
            if( options.limeroot && location.indexOf("/!lime") == -1)
            {
                location = "/!lime/root" + location; // convert to limeroot
            }
            else if( options.limeroot === false && location.indexOf("/!lime") > -1 ) 
            {
                location = location.replace('/!lime/root', ''); // get rid of limeroot    
            }
                
            return window.location.protocol + '//'  + base + location;
        },
        
        // replace any src="..." with absolute paths
        translateRelativePaths : function(markup, bitLoc)
        {
            bitLoc = AXIS.Bits.Utils.domainize(bitLoc);
            bitLoc = bitLoc.charAt(bitLoc.length-1) != '/' ? bitLoc + '/' : bitLoc;
            markup = markup.replace(/src=([\"\'])(?!http:\/\/|www\.|\/\!lime|<%)(.*)\1/g, "src=$1" + bitLoc + "$2$1"); 
            return markup;
        },

        fileLoader: function(url, folderStructure, onsuccess, cb) {
            var bFileFolderPresent = false;
            if (!folderStructure) {
                bFileFolderPresent = true;
            }
            else {
                for (var i = 0; i < folderStructure.children.length; i++) {
                    var location = folderStructure.children[i].href;
                    if( url.indexOf(location) > -1 ) 
                        // we just have depth 1 info, so make do with that...
                        bFileFolderPresent = true;
                }
            }

            if (bFileFolderPresent) { 
                // only get files we think are available...
                AXIS.WebDAV.GET({
                    url: url,
                    headers: {
                        'X-No-Rewrite': '1'
                    },
                    async: true,
                    callback: function(r){
                        var st = r.getStatus();
                        if (st === 200 || st === 304) {
                            onsuccess(r);
                        }
                        else
                        {
                            bFileFolderPresent = false;
                        }
                        
                        if (cb) {
                            cb(bFileFolderPresent);
                        }
                    }
                });
            }
            else if (cb) 
            {
                    cb(false);
            }
        },

        /**
         * Copy bit located at <bitPath> to logged in user's home folder, and forward user there
         * @param {string} bitPath - Location of bit to copy
         * @param {boolean} location_replace - Whether to replace page in history, or add to it
         * @param {string} target_url - Where to forward user to, after copy
         */
        getBit: function(bitPath, location_replace, target_url)
        {
            target_url = target_url ? target_url : '/apps/bitfinder';
            if (AXIS._siteData.hosts.limebits != ("http://" + location.host + "/"))
              {
                return AXIS.Util.bit.getBitFromUserDomain(bitPath);
              }

            var bit = AXIS.Util.bit.bitInfo(AXIS.Util.uri.getBitUrl(bitPath));
            var bitObj = AXIS.Bits.get(bitPath);
            
            var user = AXIS.Cookies.read('user');

            if (user)
              {
                  var dst_folder = '/home/' + user + '/bits';
                  
                  bitObj.copy({
                      destFolder: dst_folder + '/' + bit.name,
                      async: true,
                      callback: function(newBit){
                          var orig_bitname = bitObj.getDisplayName();
                          var alternateName = AXIS.Util.uri.findAlternateName(orig_bitname, [orig_bitname]);
                          newBit.setDisplayName(alternateName);
                          if( target_url.indexOf('bitmix') > -1 )
                          {
                              target_url = target_url.replace('unauthenticated', user); // just in case we came here from a twisted unauthenticated loop...
                              target_url += '#bm_project=' + encodeURIComponent('/bits/' + newBit.getBitFolderName());
                          }
                          var location = newBit.getLocation().replace(/\/$/,''); // the reader of this cookie does not expect a trailing slash
                          AXIS.Cookies.create(location, "openConfigure", {
                              path: '/apps/'
                          });
                          if (location_replace) {
                              top.location.replace(target_url);
                          }
                          else {
                              top.location = target_url;
                          }
                      }
                  });
              }
            else
              {
                var rand = "" + Math.random();
                AXIS.Cookies.create(rand + '-url', AXIS.Util.uri.getUserUrl(bitPath), { path: '/apps/copier/' });
                AXIS.Cookies.create(rand + '-confirmed', 'true', { path: '/apps/copier/' });
                AXIS.Cookies.create(rand + '-target', target_url, { path: '/apps/copier/' });
                AXIS.Login.redirReturnTo = AXIS._siteData.hosts.limebits + 'apps/copier/#token=' + rand;
                AXIS.Login._redirectToAccess("if_new");
              }            
        },

        // get name of bit as if it were a folder in a filesystem...
        // @param location - Location of bit
        getBitFolderName: function(location)
        {
            var loc = AXIS.Util.uri.chompSlash(location);
            return loc.substring(loc.lastIndexOf("/") + 1);
        },
        
        // Get some existing instance of bit
        getLatestInstance: function(bitLoc, callback)
        {
            bitLoc = bitLoc.charAt(bitLoc.length - 1) == '/' ? bitLoc : bitLoc + '/';
            
            for( var key in AXIS.Bits.instances)
            {
                if( AXIS.Bits.instances[key].bit.location == bitLoc )
                {
                    callback(AXIS.Bits.instances[key]);
                    return AXIS.Bits.instances[key];
                }
            }
            return AXIS.Bits.get(bitLoc).newInstance({
                callback: callback
            });
        }
    };

    /**
     * An interface to access any kind of meta data
     */
    this.Metadata = 
    {
       BITMARK_BASEURL : '/bitmarks',
       LIVE_PROPERTIES: [ 'displayname' ],
       
       // The basic building blocks for any complex metadata
       Types : {
           // A property is simply a name value pair assigned to a bitResourceId
           // <bitResourceId> is optional i.e. will be created automatically when you call save()
           SimpleProperty : function(name, bitResourceId)
           {
               this.name = name;
               this.bitResourceId = bitResourceId;
               
               this.getName = function() { return this.name; };
               this.getValue = function() { return this.value; };
               this.setValue = function(value) { this.value = value; return this;};
               this.getResourceId = function() { return this.bitResourceId; };
               this.setResourceId = function(id) { this.bitResourceId = id; return this; };
               
               /**
                * Save this property to the backend
                * @return this object
                */
               this.save = function(options) 
               { 
                   options = options? options: {};
                   
                   var propToSave = [{
                       name: this.name,
                       value: this.value,
                       ns: AXIS.Bits.Utils.inArray(name, AXIS.Bits.Metadata.LIVE_PROPERTIES) ? AXIS.WebDAV.ns.d : AXIS.WebDAV.ns.lb
                   }];
                   
                   AXIS.Bits.Metadata.saveProperties(this.bitResourceId, propToSave, {
                           async : AXIS.Bits.isAsync(options),
                           callback : function() {
                               AXIS.Util.processCallback(options,this);
                           }
                   });
                   return this;
               };
           },
           
           // A bitmark is a collection of values with the same name, assigned to a resourceId
           // <bitResourceId> is optional i.e. will be created automatically when you call save()
           SimpleBitmark : function(name, bitResourceId)
           {
                this.name = name;
                this.bitResourceId = bitResourceId;
                
                /**
                 *  to store all the bitmark data
                 *  Organized as [ { resourceId : <resrc uuid>, value: <value>, modified: boolean, ownerid: <ownerid> }, ... ]
                 */
                this.data = [];  
                 
                this.getName = function() { return this.name; };
                this.getResourceId = function() { return this.bitResourceId; };
                this.setResourceId = function(id) { this.bitResourceId = id; };
                
                /**
                 * Return an array of bitmark values
                 * @param {Object} filterOptions
                 *                                     .owner 
                 *                                     .lastmodified
                 */
                this.getValues = function(filterOptions)
                {
                    filterOptions = filterOptions ? filterOptions : {};
                    var values = [];
                    for( var i=0; i<this.data.length; i++ )
                    {
                        if (!filterOptions.owner || filterOptions.owner == this.data[i].owner) {
                            values.push(this.data[i].value);
                        }
                    }
                    return values;
                };
                
                /**
                 * Return an array of bitmark with properties
                 * @param {Object} filterOptions
                 *                                     .owner 
                 *                                     .lastmodified
                 */
                this.get = function(filterOptions)
                {
                    filterOptions = filterOptions ? filterOptions : {};
                    var data = [];
                    for( var i=0; i<this.data.length; i++ )
                    {
                        if (!filterOptions.owner || filterOptions.owner == this.data[i].owner) {
                            data.push(this.data[i]);
                        }
                    }
                    return data;
                };
                
                // Utility method to find array position based on resourceId
                this.getIndexByResourceId = function(id)
                {
                    for( var i=0; i<this.data.length; i++ )
                    {
                        if( this.data[i].resourceId == id )
                        {
                            return i;
                        }
                    }
                    return -1; // couldnt find anything
                };
                
                /**
                 * Add new bitmark resource
                 * @param value [,value, ...]
                 */
                this.add = function()
                {
                    for (var i = 0; i < arguments.length; i++) {
                        this.data.push({
                            value: arguments[i],
                            owner: AXIS.User.username(),
                            modified: true
                        });
                    }
                    return this;
                };
                /**
                 * Remove bitmark resource 
                 * @param {Object} id - Either array position or resourceId
                 */
                this.remove = function(id)
                {
                    // get array position
                    if( typeof(id) == 'string' )
                    {
                        id = this.getIndexByResourceId(id);
                    }    
                    this.data[id].value = null;
                    this.data[id].modified = true;
                    
                    return this;
                };
    
                /**
                 * Edit existing bitmark resource
                 * @param id - Either index of array, or resourceId
                 * @param value - New value
                 */
                this.set = function( id, value )
                {
                    // get array position
                    if( typeof(id) == 'string' )
                    {
                        id = this.getIndexByResourceId(id);
                    }    
                    
                    if (!this.data[id]) {
                        this.data[id] = {
                            owner: AXIS.User.username()
                        };
                    }
                    this.data[id].value = value;
                    this.data[id].modified = true;
                    
                    return this;
                };
    
               /**
                * Save this bitmark to the backend
                * @return this object
                */
               this.save = function(options) 
               { 
                   options = options? options: {};
                   AXIS.Bits.Metadata.saveBitmark(this.bitResourceId, this, {
                           async : AXIS.Bits.isAsync(options),
                           callback : function() {
                               AXIS.Util.processCallback(options,this);
                           }
                   });
                   return this;
               };
            }
          },
       
         /*
      	  * Save a property : proppatch
      	  * returns the response object
      	  */
      	saveProperties : function(resourceBaseUrl, propertiesToSet, options) {
            options = options ? options : {};
            
      	    return AXIS.WebDAV.PROPPATCH({'url': resourceBaseUrl,
      		                                   'setProperties': propertiesToSet,
      		                                   scope: this,
                                               async: AXIS.Bits.isAsync(options),
      		                                   onSuccess: function( response ) {
                                                          AXIS.Util.processCallback(options, response);
                                              },
                                              onFailure: function( r) {
                                                AXIS.Logger.log('bitapi','error', 'Could not save property', r);
                                              }
                           });
    	},
    
         /**
          * Save bitmark on backend
          * @param {Object} resourceBaseUrl
          * @param {Object} bmark
          * @param {Object} options - .callback, .scope
          * @return response object
          */
         saveBitmark : function(resourceBaseUrl, bmark, options ) {
             // cycle through all bitmark resources looking for things to save
             for (var i = 0; i < bmark.data.length; i++) {
                 if (bmark.data[i].modified) {
                     if (bmark.data[i].value === null) {
                         return this.removeBitmark(i, bmark, {
                             scope: [this, i],
                             async: AXIS.Bits.isAsync(options),
                             callback: function(index, response){
                                 // update internal records
                                 bmark.data.splice(index, 1);
                                 AXIS.Util.processCallback(options, [response]);
                             }
                         });
                     }
                     else {
                         if (!bmark.data[i].resourceId) {
                             return this.createBitmarkHolder(resourceBaseUrl, {
                                 scope: [this, i],
                                 async: AXIS.Bits.isAsync(options),
                                 callback: function(index, response){
                                     bmark.data[index].resourceId = response.result;
                                     this.addEditBitmark(index, bmark, options);
                                 }
                             });
                         }
                         else {
                             return this.addEditBitmark(i, bmark, options);
                         }
                     }
                 }
             }
       },
    
        /**
         * Remove bitmark from backend
         * @param {Object} index - Which bitmark resource within <bitmark>
         * @param {Object} bitmark - The Bitmark object
         * @param {Object} options - .callback, .scope
         */   
       removeBitmark : function( index, bitmark, options ) {
           
           return AXIS.WebDAV.DELETE({
               async: AXIS.Bits.isAsync(options),
               url: AXIS.Bits.Utils.domainize(bitmark.data[index].resourceId, {limeroot:true}),
               onSuccess: function(response){
                   AXIS.Logger.log('bitapi', 'info', 'Bitmark removed');
                   AXIS.Util.processCallback(options, [response]);
               }
           });
       },
                    
        /**
         * Create collection to hold bitmark
         * @param {Object} resourceBaseUrl - The bit location
         * @param {Object} options - .callback, .scope
         * @return response object
         */                
       createBitmarkHolder : function( resourceBaseUrl, options )
       {
           var folderUrl = AXIS.Bits.Utils.domainize(AXIS.Bits.Metadata.BITMARK_BASEURL, {limeroot:true}); // bitmarks only at root
           return this.getUUID(resourceBaseUrl, { scope: this,
               async: AXIS.Bits.isAsync(options),
               callback: function(response){
                   folderUrl += "/" + response.result;
                   AXIS.Logger.log('bitapi','debug','Creating folder', folderUrl);
                   var random = Math.floor(Math.random() * Math.pow(10, 17));
                   var bitmarkUrl = folderUrl + "/" + random;
                   
                   // Make folder
                   AXIS.WebDAV.MKCOL({ 
                       headers: {
                           'If-None-Match': '*'
                       },
                       async: AXIS.Bits.isAsync(options),
                       url: folderUrl, 
                       callback: function(r){
                           AXIS.Logger.log('bitapi','debug','Creating file', bitmarkUrl);
                           // Make file
                           r.result = AXIS.WebDAV.MKCOL({
                               headers: {
                                   'If-None-Match': '*'
                               },
                               async: AXIS.Bits.isAsync(options),
                               url: bitmarkUrl,
                               scope: this,
                               callback: function(response){
                                   response.result = bitmarkUrl;
                                   AXIS.Util.processCallback(options, [response]);
                               }
                           });
                       }
                   });
                }
           });
        },
    
        /**
         *  Actual saving of bitmark on backend
         * @param {Object} index - Which bitmark resource
         * @param {Object} bitmarkObj - The bitmark object
         * @param {Object} options - .callback, scope
         * @return response object
         */       
       addEditBitmark : function( index, bitmarkObj, options ) {
    
           // bitmarkValue = AXIS.Util.escapeEntities(bitmarkValue);
            var bitmarkUrl = bitmarkObj.data[index].resourceId;
            var bitmarkValue = bitmarkObj.data[index].value;
           if (typeof(bitmarkValue) == 'number' || typeof(bitmarkValue) == 'boolean') {
               bitmarkValue = "" + bitmarkValue;
           }
           else 
               if (typeof(bitmarkValue) == 'object') {
                   bitmarkValue = JSON.stringify(bitmarkValue);
               }
    
           var propertiesToSet = [{
               name: bitmarkObj.name,
               value: bitmarkValue, 
               ns: AXIS.WebDAV.ns.bm
           }];
           AXIS.Logger.log('bitapi','info', 'Saving bitmark ', propertiesToSet, bitmarkUrl);
    
           return AXIS.WebDAV.PROPPATCH({'url': bitmarkUrl,
                                                  'setProperties': propertiesToSet,
                                                  async : AXIS.Bits.isAsync(options),
                                                  scope: this,
                                                  onSuccess: function( response ) {
    			                                        AXIS.Logger.log('bitapi','info', 'Bitmark saved');
                                                        bitmarkObj.data[index].modified = false;
    			                                        AXIS.Util.processCallback(options, [response]);
                                                  },
                                                  onFailure: function( r) {
                                                    AXIS.Logger.log('bitapi', 'error', 'Cannot save bitmark', r);
                                                    AXIS.Util.processCallback(options, [response]);
                                                  }
                                                });
      	 },
         
         // Parses an xml <resp> and returns a hash of bitmarks (with bitmark name as the key)
         parse_bitmarks : function(resp)
        {
           	    var marks = { };
    
      			var pstat = resp.propstat;
      			var bstat = resp.bitmarkstat;
      
      			pstat = AXIS.isArray(pstat) ? pstat : (typeof(pstat) == 'undefined' ? [] : [pstat]);
      			bstat = AXIS.isArray(bstat) ? bstat : (typeof(bstat) == 'undefined' ? [] : [bstat]);
                
                    for (var j = 0; j < pstat.length; j++) {
                        if (pstat[j].status.match(/200 OK/)) {
                            for (name in pstat[j].prop) {
                                var val = AXIS.Bits.Metadata.helpers.parse(name, pstat[j].prop[name]);
                                marks[name] = AXIS.Bits.Metadata.create(name, resp.href);
                                marks[name].setValue(val);
                            }
                        }
                    }
                
                    for (var k = 0; k < bstat.length; k++) {
                        if (bstat[k].status.match(/200 OK/)) {
                            var bitmark = bstat[k].bitmark;
                            bitmark = AXIS.isArray(bitmark) ? bitmark : [bitmark];
                            
                            for (var index = 0; index < bitmark.length; index++) {
                                // a weird hacky sort of way to extract the name/value out of bitmark xml node
                                for (var key in bitmark[index]) {
                                    if (key == '#' || key == 'href') {
                                        continue;		 
                                    }         
                                    else {
                                        var value = bitmark[index][key];
                                        if (value.indexOf('{') > -1) // dont know how else to test if value is json
                                        {
                                            value = JSON.parse(value);
                                        }
                                        if (!marks[key]) {
                                            marks[key] = AXIS.Bits.Metadata.create(key, resp.href, {
                                                type: AXIS.Bits.Metadata.Types.SimpleBitmark
                                            });
                                        }
                                        marks[key].data.push({
                                            value: value,
                                            resourceId: bitmark[index].href
                                        });
                                        
                                        /* Disabling owner data for now, since it triggers a lot of requests
                                        AXIS.WebDAV.getProperty({
                                            url: AXIS.Bits.Utils.domainize(bitmark[index].href, {limeroot:true}), // bitmarks can only be accessed at root
                                            properties: ['owner'],
                                            async: false,
                                            passThru: {
                                                key: key,
                                                value: value,
                                                href: bitmark[index].href
                                            },
                                            onSuccess: function(r){
                                                var owner = r.responseXMLObject().multistatus.response.propstat.prop["owner"];
                                                var value = r.passThru.value;
                                                var rsrcid = r.passThru.href;
                                                marks[r.passThru.key].data.push({
                                                    value: value,
                                                    resourceId: rsrcid,
                                                    owner: AXIS.Bits.Metadata.helpers.parse('owner', owner)
                                                });
                                            }
                                        });
                                        */
                                    }
                                }
                            }
                        }
                    }
      			 return marks;
        },
    	  
         /**
          * Get resource id of bit located at <url>
          * @param {Object} url
          * @param {Object} options - callback, scope
          * @return response object
          */ 
        getUUID : function(url, options)
          {
              return AXIS.WebDAV.getProperty({
                  url: url,
                  properties: ['resource-id'],
                  async: AXIS.Bits.isAsync(options),
                  onSuccess: function(r){
                      var resource_id;
                      try {
                          resource_id = r.responseXMLObject().multistatus.response.propstat.prop['resource-id'].href.replace(/urn:uuid:/, '').replace(/-/g, '');
                      } 
                      catch (e) {
                          AXIS.Logger.log('bitapi','error','Cannot parse uuid', e);
                      }
                      r.result = resource_id;                  
                      AXIS.Util.processCallback(options, [r]);
                  }
              });
          },
          
          helpers : {
              parse : function(property, obj)
              {
                  switch (property) {
                      case 'owner':
                          // parse out the name from url
                          var words = obj.href.split('/');
                          return words[words.length - 1];
                       
                       default:
                           return obj;
                  }
              }    
          },
          
        /**
         * Create new instance of bitmark
         * @param name - Name of metadata
         * @param bitResourceId - Resource Id of bit this metadata is part of
         * @param {Object} options
         *                                     .type (optional) - The type of metadata to create
         */
        create: function(name, bitResourceId, options){
            options = options ? options : {};
            options.type = options.type? options.type : AXIS.Bits.Metadata.Types.SimpleProperty;
            
            var obj = new options.type(name,bitResourceId);
            return obj;
        }
    };
    
}

/**
 * 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
 * 
 * General CSS manipulation/reading functionality.  Allows the dynamic modification of 
 * style sheets, reading of values, etc.
 *
 * @see http://www.javascriptkit.com/dhtmltutors/externalcss3.shtml
 */
function CSS()
  {
    /*
     * @constructor
     */
    this.__construct = function()
      {
      };

    /**
     * Returns the current stylesheet collection
     */
    this.sheets = function(d)
      {
          d = d || document;
          return d.styleSheets; 
      };
    
    /**
     * Find a CSS rule based on selector.  
     * 
     * @param    {String}    r     A valid CSS selector
     * @returns                    The rule, or false if none.
     * @example:   var r = CSS.findRule('.myClass');
     *              r.style.width = '100px';
     */
    this.findRule = function(r)
      {
        var ss    = this.sheets();
        var rn    = (r) ? r.toLowerCase() : ''; 
        var i     = ss.length;
        
        while(i--) 
          { 
            var styleSheet  = ss[i];
            var ii          = 0;                              
            var cssRule     = false;                      
            do
              {                                   
                if(styleSheet.cssRules) 
                  {          
                    cssRule = styleSheet.cssRules[ii];
                  } 
                else 
                  {                             
                    cssRule = styleSheet.rules[ii];    
                  }                                    
                if(cssRule && (cssRule.selectorText.toLowerCase()==rn))  
                  {                      
                    return({sheet:styleSheet,rule:cssRule,style:cssRule.style,index:ii});                                 
                  }                                     
                ii++;                                 
              } while(cssRule)                        
          } 
        return false;   
      };   
             
    this.fetchAllRules = function(d)
      {
        var ss    = this.sheets(d);
        var i     = ss.length;
        var ret   = [];
        while(i--) 
          { 
            var styleSheet  = ss[i];
            ret = ret.concat(this.fetchAllRulesForSheet(styleSheet));
          } 
        return ret;   
      };  
    
    this.fetchAllRulesForSheet = function(styleSheet)
    {
            var ii          = 0;                              
            var cssRule     = false;
            var ret   = [];
            try {
                do {
                    if (styleSheet.cssRules) {
                        cssRule = styleSheet.cssRules[ii];
                    }
                    else {
                        cssRule = styleSheet.rules[ii];
                    }
                    
                    //if (cssRule && cssRule.type == 1) {
                    if (cssRule) {
                        ret.push({
                            sheet: styleSheet,
                            rule: cssRule,
                            style: cssRule.style,
                            index: ii
                        });
                    }
                    ii++;
                }
                while (cssRule);
            } catch(e) {
                /* to avoid cross domain access errors */
            }                   
            return ret;     
    };
                                                 
    /**
     * Remove a CSS rule based on selector.  
     *
     * @param    {String}    r     A valid CSS selector
     * @returns                    Boolean, whether a rule was removed
     * @example:   var r = CSS.removeRule('.myClass');
     */
    this.removeRule = function(r) 
      { 
        var rr = this.findRule(r);    
        if(rr)
          { 
            var ss    = rr.sheet;
            var rule  = rr.rule;
            var ind   = rr.index;
            
            if(ss.cssRules) 
              {  
                ss.deleteRule(ind);
              } 
            else 
              {                     
                ss.removeRule(ind);
              }                            
            return true;                 
          } 
        return false;
      };  
      
    /*
     * Allows the addition of a style rule.
     *                         
     * @param    {String}    r     A valid CSS selector
     * @returns                    The new rule
     */
    this.addRule = function(r) 
      {       
        var ss = this.sheets();
        if(!this.findRule(r)) 
          {    
            if(ss[0].addRule) 
              {       
                ss[0].addRule(r, null,0);
              } 
            else 
              {                   
                ss[0].insertRule(r+' { }', 0);
              }        
          }                        
        return this.findRule(r);   
      }; 

    /**
     * Return computed style of oElm
     * @param {Object} oElm
     * @param {string} strCssRule
     */      
    this.getComputedStyle = function(oElm, strCssRule, bNumerical){
        var strValue = "";
        if (document.defaultView && document.defaultView.getComputedStyle) {
            strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule);
        }
        else 
            if (oElm.currentStyle) {
                strCssRule = strCssRule.replace(/\-(\w)/g, function(strMatch, p1){
                    return p1.toUpperCase();
                });
                strValue = oElm.currentStyle[strCssRule];
            }
        if (bNumerical) {
            var value = parseInt(strValue);
            return isNaN(value) ? 0 : value;
        }
        else {
            return strValue;
        }
    };
      
      /**
       * Get style value from rule
       * @param {Object} rule
       * @param {string} styleName
       */
      this.getStyleFromRule = function(rule, styleName)
      {
          var styleValue = null;
          if( rule.style.getPropertyValue )
          {
              styleValue = rule.style.getPropertyValue(styleName);
          }
          else if( rule.style.getAttribute )
          {
              styleValue = rule.style.getAttribute(styleName);
          }
          return styleValue;
      };
      
      /**
       * Change the style's value in rule
       * @param {Object} rule
       * @param {string} styleName
       * @param {string/integer} styleValue
       */
      this.setStyleToRule = function(rule, styleName, styleValue)
      {
          if( rule.style.setProperty )
          {
              rule.style.setProperty(styleName, styleValue, "");
          }
          else if( rule.style.setAttribute )
          {
              rule.style.setAttribute( styleName, styleValue, "" );
          }
      };

    /**
     * Attach stylesheet to <targ>
     * @param {String} css - styles
     * @param {Object} targ(optional) - Target element (document by default)
     * @param {Object} attributes(optional) - Attributes for the style element
     */
   this.attachNewSheet = function(css, targ, attributes)
    {
      targ = targ || document;
      
      var sht   = targ.createElement("style");
      sht.type  = "text/css";
	  for(var x in attributes) {
	  	sht[x] = attributes[x];
	  }
      /**
       * IE does it this way.
       */
      if( typeof(sht.styleSheet) != 'undefined' ) 
        {
          sht.styleSheet.cssText = css;
        } 
      else 
        {
          sht.appendChild(targ.createTextNode(css));
        }

      /**
       * Create the new stylesheet in the parent document.
       */
      targ.getElementsByTagName("head")[0].appendChild(sht);
    };
    
    this.replaceSheet = function(sheet, css, targ)
    {
        targ = targ || document;
        var sht = sheet.ownerNode;
      /**
       * IE does it this way.
       */
      if(sht.styleSheet) 
        {
          sht.styleSheet.cssText = css;
        } 
      else 
        {
          sht.appendChild(targ.createTextNode(css));
        }
    };
    
    /**
     * Convert the rules into simple string of styles
     * @param {Object} rules
     */
    this.getRulesAsString = function(rules)
    {
        var x, rule, cr, pElement;
        var newSheet = '';
        
        for (x = 0; x < rules.length; x++) {
            // only process style rules
            if (rules[x].rule.type != 1) {
                continue;
            }
            
            cr = rules[x].rule;
            rule = cr.selectorText + '{' + cr.style.cssText + '}';
            
            /**
             * Skip any AXIS style.
             */
            if (rule.indexOf('AXIS_') !== -1) {
                continue;
            }

            newSheet += rule;
        }
        return newSheet;
    };   
    
    /**
     * Get a particular stylesheet
     * @param obj - what to search for
     *                     .id - the id of stylesheet
     *                     .href - the source of stylesheet (regexes accepted)
     */
    this.getStyleSheet = function(obj)
    {
        var sheets = this.sheets();
        for( var i=0; i<sheets.length; i++)
        {
            if( obj.id && sheets[i].id == obj.id )
            {
                return sheets[i];
            }
            else if( obj.href)
            {
                if( obj.href instanceof RegExp && sheets[i].href.match(obj.href) )
                {
                    return sheets[i];
                }
                else if( typeof(obj.href) == 'string' && obj.href == sheets[i].href )
                {
                    return sheets[i];
                }
            }
        }
        return false;
    };
    
    this.encapsulate = function(css, bit, location, prefix)
    {
        var bName = prefix || bit.getBitFolderName();
        location = location || bit.getLocation();
        if (location.charAt(location.length - 1) !== '/') {
            location += '/';
        }
        
        // first, strip out the comments
        css = css.replace(new RegExp("/\\*[\\S\\s]*?\\*/","gim"), '');
        
        var selectorRegex = new RegExp('([\\S\\s]*?)({[\\S\\s]*?})', 'gim');
        var newCss = css.replace(selectorRegex, function(a, b, c, d){
            
            // Prefix the selectors
            var selectors = b.replace(/[\r\n]/gim, '').split(',');
            for (var i = 0; i < selectors.length; i++) {
                if (selectors[i].indexOf('.Bit') > -1) {
                    selectors[i] = '.' + bName; // bit level styles
                }
                else 
                    if (selectors[i].indexOf('.canvas') == 0) { // do nothing for canvas selector
                        continue;
                    }
                    else 
                        if (selectors[i].indexOf('.') === 0) // this is a class
                        {
                            selectors[i] = '.' + bName + ' ' + selectors[i] + ',' + '.' + bName + AXIS.Util.lang.trim(selectors[i]); // add both "children" and "self" kinds of rules
                        }
                        else if( selectors[i].indexOf('@') === 0)
                        {
                            continue;
                        }
                        else // this is not a class, dont add "self" rule
                        {
                            selectors[i] = '.' + bName + ' ' + selectors[i];
                        }
            }
            
            // Prefix the image urls
            var properties = c;
            var urlRegex = new RegExp('(url\\([\'\"]?)([\\S\\s]*?)([\'\"]?\\))', 'gim');
            properties = properties.replace(urlRegex, function(a, b, c, d){
                if (!c.match(/http:\/\/|www\.|\/!lime\/root/)) {
                    c = location + c;
                }
                return b + c + d;
            });
            return '\n' + selectors.join(',') + properties;
        });
        return newCss;
    };   
}
/**
 * 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 Observer implements something like a command pattern, while offering the ability
 * to properly manage browser history (allowing movement through commands using a 
 * browser's BACK|FORWARD buttons.
 *  
 * @requires AXIS
 * @throws   Error OBSERVER_BAD_ARGS
 */

function History()
  {
    /*
     * @constructor
     * @see    #start
     */
    this.__construct = function()
      {
        AXIS.Errors.createExceptionType('HistoryException');
        AXIS.Errors.registerCode('COMMAND_BAD_ARGS','Missing or malformed arguments passed to History.set()');
        
        /**
         * Set up subscribable events
         */
        this.onChange = AXIS.CustomEvent.create({
          name:   'HistoryChange',
          scope:  this
        });

        /**
         * The history tracking is done via a Queue method, which watches
         * for changes in the location hash. 
         */
        AXIS.onDOMReady.subscribe({
          scope:    this,
          callback: function() {

            /**
             * We store this Queue Object as #instance, so we have a reference
             * when needed to stop, etc.
             */
            this.watcher = AXIS.onHashChangeWatcher;
            this.previousParams = AXIS.parseUrl().getAllHashParams();
            
            /**
             * We use the hash watcher to track when history has been changed
             */
            AXIS.onHashChange.subscribe({
                scope: this,
                callback: function(ev){
                    if (AXIS.Util.lang.equals(this.previousParams, ev.data.params) == false) {
                        this.onChange.fire({
                            params: ev.data.params,
                            changes: {
                                added: AXIS.Util.lang.diff(ev.data.params, this.previousParams),
                                removed: AXIS.Util.lang.diff(this.previousParams, ev.data.params),
                                updated: AXIS.Util.lang.diffByValue(this.previousParams, ev.data.params)
                            }
                        });
                        this.previousParams = ev.data.params;
                    }
                }
            });
            
            /**
             * Unfortunately, for IE need this.  IE will not update the history
             * record via location.replace with a #.  That actually makes sense,
             * in a way, but not good for our purposes.  So we create a history
             * frame, whose manipulation creates a history record.
             *  
             * However, a nice and as it turns out desired consequence of waiting for
             * the content is that the hash is read and executed on init -- which means
             * that if you bookmark a page with the hash, that hash instruction will
             * execute on page load.
             *
             * @see History#set
             */
  
            if(AXIS.isIE)
              {
                this.HFrame = AXIS.Element.create('iframe',{
                  id:     '__history',
                  name:   '__history',
                  src:    'about:blank',
                  style:  {
                    display:  'none'  
                  }
                });
              }
          }
        });
      };
      
    this.getArguments = function()
      {
        var x, s;
        var fArgs = {};
        var frag  = AXIS.parseUrl().fragment;
        var args  = frag.split('&');
        
        /**
         * Lose command
         */
        args.shift();
        
        for(x=0; x < args.length; x++)
          {
            s = args[x].split('=');
            fArgs[s[0]] = s[1];
          }
        
        return fArgs;
      };
      
    /**
     * This will change the # of the current page location in browser.  Pass it any
     * number of arguments, which will simply be added to the # of the current location,
     * separated by `^` character.  NOTE: It is up to you to worry about escaping characters, 
     * if that is an issue.
     *
     * @param      {Object}        d       Info object:
     *    {
     *      [args]  {Object}       A collection of key = value pairs.
     *    }
     *  @param   {object} options  
     *                      {Boolean}    replace - Whether to replace history (instead of adding to it)
     */
		this.set = function(d, options)
		  {			
            options = options || {};
		    if(d && (d.command || this.watcher.command))
		      {
		        var h, hd, k, z;
		        var hArgs   = d.args || {}; 
		        var cmd     = d.command || this.watcher.command;
		        var twL     = AXIS.parseUrl();
		        var cArgs   = this.getArguments();

		        /**
		         * This is going to be the newly constructed hash.
		         */
    		    var hsh     = '#' + cmd;   

            /**
             * Update any change requests in argument list.
             */
            for(z in hArgs)
              {
                cArgs[z] = hArgs[z];  
              }

            /**
             * Build new hash, &k=v
             */
    		  	for(k in cArgs)
    		  	  {
                    var regex = new RegExp(k+'=.*?($|&)');
                    if (hsh.match(regex)) { // replace if parameter exists
                        hsh = hsh.replace(regex, k+'='+encodeURIComponent(cArgs[k])+'$1');
                    }
                    else { // append
                        hsh += '&' + k + '=' + encodeURIComponent(cArgs[k]);
                    }
    		  	  }

            /*
             * Update history object.  Note that we don't do anything if the current
             * hash matches generated hash.
             */          
                
                if (twL.fragment != hsh) {
                    /**
                     * Form the fully formed url + hash
                     */
                    h = twL.url.split('#')[0] + hsh;
                    
                    /**
                     * IE needs to do this in order to create a history entry.
                     *
                     * @see #__construct
                     */
                    if (AXIS.isIE) {
                        hd = this.HFrame.contentWindow.document;
                        hd.open();
                        hd.write('<script>parent.window.location.replace("' + h + '");</script>');
                        hd.close();
                    }
                    else 
                        if (options.replace) {
                            window.location.replace(h);
                        }
                        else {
                            window.location.href = h;
                        }
                }
          }
        else
          {
            new AXIS.Errors.HistoryException('COMMAND_BAD_ARGS').report(); 
          }
		  };
		
        /**
         * Same as above, except this doesnt do all the fancy command handling... just a pure and simple hash updater
         * To remove a parameter, set its value to null
         * @param {Object} - Name/Values of parameters to be updated in url 
         * @param {Object} - options
         *                                     .replace (boolean) - Whether history should be replaced (defaults to false)
         *                                     .cancelNext (boolean) - Whether to stop History.onChange from being fired
         */ 
        
        this.update = function(params, options)
        {
            options = options || {};
            var parsedUrl = AXIS.parseUrl(); 
            var hsh = parsedUrl.fragment;
            
            for(key in params)
            {
                var regex = new RegExp(key + '=.*?($|&)');
                if (hsh.match(regex)) { // replace if parameter exists
                    if (params[key] === null) { // remove if null
                        hsh = hsh.replace(new RegExp('&?' + key + '=[^$&]*'), '');
                    }
                    else {
                        hsh = hsh.replace(regex, key + '=' + encodeURIComponent(params[key]) + '$1');
                    }
                }
                else if(params[key] !== null) { // append
                    var separator = hsh.length === 0 ? '' : '&';
                    hsh += separator + key + '=' + encodeURIComponent(params[key]);
                }
            }
            
            if( parsedUrl.fragment != hsh )
            {
                var url = parsedUrl.url.split('#')[0] + '#' + hsh;
                /**
                 * IE needs to do this in order to create a history entry.
                 *
                 * @see #__construct
                 */
                if(AXIS.isIE)
                  {
                    var hd = this.HFrame.contentWindow.document;
                		hd.open();
                		hd.write('<script>parent.window.location.replace("'+ url +'");</script>');
                		hd.close();
                  }
                else if( options.replace)
                {
                    window.location.replace(url);
                }
                else
                {
                    window.location.href = url;
                }
                if( options.cancelNext )
                {
                    this.previousParams = AXIS.parseUrl().getAllHashParams();                    
                }
            }
        };
        
		/**
		 * Unsets arguments. Will take the list of arguments sent and remove those values.
		 * ie. consider fragment: #command&old=argment&foo=bar
		 * After: #unset(['old']);
		 * fragment = #command&foo=bar.
		 *
		 * If you simply want to clear a value, use #set ''.
		 *
		 * @param   {Array}   u   An array of string values corresponding to arguments.   
		 */
		this.unset = function(u)
		  {
		    var args  = u || [];
		    var url   = AXIS.parseUrl();
		    var frag  = url.fragment;
		    var f     = frag.split('&');
		    var ns    = [];
		    
		    for(var a=0; a < args.length; a++)
		      {
		        for(var y=0; y < f.length; y++)
		          {
		            if(f[y].indexOf(args[a] + '=') === -1)
		              {
		                ns.push(f[y]);
		              }
		          }
		      }
		      
		    url = url.url.replace(frag,'') + ns.join('&');
		    window.location.replace(url);
		  };
		  
		this.clear = function()
		  {
		    var u = AXIS.parseUrl();
		    window.location.replace(u.url.replace('#' + u.fragment,'#'));
		  };
  };/**
 * 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_ERR_REG_FAIL
 * @throws  Error   AXIS_INCLUDE_SCRIPT_NO_SRC
 * @throws  Error   AXIS_INCLUDE_CSS_NO_HREF
 */ 

var $AXIS = function()
  {      
    /**
     * Check if this is a combined/minified version of the AXIS library.  The check
     * is for existence of XHR extension.  Loose, but unlikely that the combined
     * AXIS will not have this extension, as it is necessary for most everything.
     */
    this.minFName = (typeof XHR == '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);
    
    this.isIPhone =           /iphone/.test(this.uA);
    this.isIPod =             /ipod/.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 '';
    };

    /**
     * Returns a reference to the SCRIPT element whose source contains
     * the string argument.
     *
     * @param    {String}    s     The string to search for in the .src attributes
     *                              of the SCRIPTS collection.  Normally, this would
     *                              be a js filename, like AXIS.js
     */
    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 '';
    };
          
    /**
     * General null function, used variously.
     */
    this.F  = function()
      {
        return false;
      };
    
    /**
     * @see #attachEvent, #detachEvent
     */
    this.IE_EVENTS = [];
    
    /**
     *
     * @private
     * @see #settings
     * @see #initialize
     */
    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
     */
    this._notificationsEnabled  = true;
    
    /**
     * Site data file, used variously, mainly for Login
     *
     * @private
     * @see #initialize
     * @see __build__.js
     */  
    this._siteData  =           {};
       
    /**
     * These are the objects which together w/ AXIS represent the base framework.  
     * You may add other scripts.
     * NOTE: order is important.  Be sure to leave Login.js as the very last.  
     *
     * 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_COMBINED_FRAMEWORK_LIST
     *
     * @private
     */
     
    this._framework =  [
      this.PATH() + 'Util.js',
      this.PATH() + 'Cookies.js',
      this.PATH() + 'User.js',
      this.PATH() + 'Logger.js',
      this.PATH() + 'Analytics.js',
      this.PATH() + 'Loader.js',
      this.PATH() + 'XHR.js',
      this.PATH() + 'WebDAV.js',
      this.PATH() + 'Login.js',
      this.PATH() + 'Bits.js',
      this.PATH() + 'CSS.js',
      this.PATH() + 'History.js'
     ];
    /* END LIMEBITS_SITE_COMBINED_FRAMEWORK_LIST */
                      
    /**
     * Error handling/debugging for the AXIS is enabled by loading the
     * `Errors` extension.  Errors are reported in this system by adding
     * commands like: new AXIS.Errors.AXISException('exception info') to
     * your code.  It wouldn't be reasonable to expect these commands to
     * be removed from the code when not in debugging mode.  However, they
     * make no sense unless the Errors extension is loaded: if it is not
     * loaded, AXIS.Errors does not exist, resulting in an non-existent
     * method javascript error.  The below exists, therefore, to "catch"
     * those debug commands when the Errors extension is not enabled. 
     * Note that the methods of this dummy class do nothing.
     */
    this.Errors = 
      {
        createExceptionType:      function(ex) 
          {
            AXIS.Errors[ex] = function()
              { 
                this.report = function(){};
              };
          },
        registerCode:             this.F,
        setReportingLevel:        this.F
      };
          
    /**
     * A reference to every script include object is registered here, indexed by its .id.
     *
     * @private
     * @see #includeScript
     */                      
    this._registeredScripts =    [];
    
    /**
     * A reference to every css include object is registered here, indexed by its .id.
     *
     * @private
     * @see #includeCSS
     */                      
    this._registeredStyleSheets =    [];
        
    /**
     * The # of ms that an XHR request will poll for readystate 4 before dying.
     *
     * @private
     * @see Queue#add
     * @see XHR#build
     */
    this._maxXHRLifespan  =       15000;
    
    /**
     * The # of ms that a notification stays visible prior to fading.
     *
     * @private
     * @see #showNotification
     */
    this._notificationFadeDelay = 20000;

    /**
     * ID and CLASS attributes of the DOM containers for messages (both
     * notifications and loading messages).  These should be defined in base.css. 
     *
     * @see #showNotification
     */
    this.notificationContainerId  =         'AXIS_notification_container';
    this.notificationItemClass  =           'AXIS_notification';
    this.notificationCloseButtonClass =     'AXIS_notification_close_button';
    
    /**
     * Ms fade runs in #fadeTo
     *
     * @see #fadeTo
     */
    this.defaultFadeSpeed  =         500; 
    
    /**
     * 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;
    
    this.createCoreErrorTypes = function()
      {  
        /**
         * @see #Errors
         */
        AXIS.Errors.createExceptionType('AXISException');
        AXIS.Errors.createExceptionType('XHRException');
        AXIS.Errors.createExceptionType('LoginException');
        AXIS.Errors.createExceptionType('LoaderException');
        AXIS.Errors.createExceptionType('DAVException');
      };
      
    /**
     * Escape CSS selector
     */
    this.escapeCSS = function(s) 
      {
        return s.replace(/([.:/%* >+~])/g, "\\$1");
      };
            
    /**
      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.checkIfScriptLoaded = function(prop, val)
      {
        var rs = this._registeredScripts;
        for(var w in rs)
          {
            if(rs[w][prop] && rs[w][prop] === val)
              {
                return rs[w];
              }  
          }  
        return false;
      };

    /**
     * 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 ret = [];
        var i, j, src, s, args;
        
        var scripts = document.getElementsByTagName("*");    
        for(i=0; i < scripts.length; i++) 
          {
            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(j=0; j < args.length; j++) 
                  {
                    s = args[j].split('=');
                    ret[s[0]] = s[1] || true;
                  }
                
                this._settings = ret; 
                break;
              }
          }
      };
      
    /**
     * 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
     */
    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 this[scr];  
          }

        if(window[scr]) 
          {
            /**
             * 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;
    
            return this[scr];
          }
        
        return null;
      };

    /**
     * Namespace storage. 
     *
     * @param  {String}    ns      The namespace, form of `chain.like.this`, which 
     *                              creates $AXIS.chain.like.this namespace.
     * @type   {Mixed}             NS ref if successful; false if not.  
     */
    this.createNamespace  =  function(ns) 
      {              
        var a = arguments;
        var x, y, f, i, z;
        
        if(a.length === 0)
          {
            return false;  
          }
                
        /**
         * Allowing for multiple namespace strings to be sent
         */
        for(i=0; i < a.length; i++) 
          {
            x = a[i].split(".");
            y = $AXIS;
        
            for(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]] || {};
              }
          }
        
        /**
         * Note that in case of multiple NS strings, only the first
         * resolved NS will be returned.
         */
        return eval('$AXIS.' + ns);
      };
        
    /**
     * 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));
      };

    /** 
     * onDOMReady
     * Copyright (c) 2009 Ryan Morr (ryanmorr.com)
     * Licensed under the MIT license.
     *
     * Cosmetic changes to cut bytes.  Also: Original code had legacy
     * browsers hijacking window.onload. This has been removed.
     */
    this._onContentReady = function()
      {
      	var ready, timer;
      	var D = document;
      	var hasFired = false;
      	
      	var onStateChange = function(e)
        	{
        	  // moz && opera
        		if(e && e.type == "DOMContentLoaded")
          		{
          			dR();
          		}
        		else if(D.readyState)
        		  {
        		    // safari & ie
          			if((/loaded|complete/).test(D.readyState))
            			{            			    
            				dR();
            			//IE, courtesy of Diego Perini (http://javascript.nwbox.com/IEContentLoaded/)
            			}
          			else if(!!D.documentElement.doScroll)
          			  {
            				try
              				{
              					ready || D.documentElement.doScroll('left');
              				}
            				catch(e)
              				{
              					return;
              				}

            				dR();
          			  }
        		  }
        	};
      	
      	var dR = function()
        	{
            if(!D.body)
              {
                return;  
              }	
        	  
        		if(!ready)
          		{
          			ready = true;
          			
          			/**
                 * Create the notification element.  
                 *
                 * @see #showNotification
                 */
                var n   = D.body.appendChild(D.createElement('div'));
                n.id    = AXIS.notificationContainerId;
                                  
                AXIS.onDOMReady.fire();
          			
          			/**
          			 * DOM cleanup
          			 */
          			if(D.removeEventListener)
          			  {
          				  D.removeEventListener("DOMContentLoaded", onStateChange, false);
          				}
          			D.onreadystatechange = null;
          			clearInterval(timer);
          			timer = null;
          		}
        	};
      	
      	// Mozilla & Opera
      	if(D.addEventListener)
      	  {
      		  D.addEventListener("DOMContentLoaded", onStateChange, false);
      		}
      	// IE
      	D.onreadystatechange = onStateChange;
      	// Safari & IE
      	//timer = setInterval(onStateChange, 5);
      };
    
    /**
     * Sets up a system whereby the hash fragment is watched, and any changes are
     * broadcast to subscribers of AXIS#onHashChange.  NOTE: To disable the watcher,
     * simply pass `disableHashWatcher` argument to AXIS.
     *
     * @see #initialize
     */
    this.createHashWatcher = function()
      {
        AXIS.onHashChange = AXIS.CustomEvent.create();
        
        AXIS.onHashChangeWatcher = AXIS.Queue.add({    
          currentHash:  null,  
          lastHash:     null,    
          command:      null,
          main:         function() {  
            var splt, cmd, z, x, s, params;
            
            var args          = {};
            var u             = AXIS.parseUrl();
            var hsh           = u.fragment;
            this.currentHash  = hsh;
            
            if(AXIS.settings('disableHashWatcher')) 
              {
                return false;  
              }
            
            if(!!hsh && this.lastHash !== hsh)
              {
                this.lastHash = this.currentHash;
                
              	/**
              	 * We now have a command, and any arguments.  Pass
              	 * this to the hash handler.
              	 */
              	splt  = hsh.split('&');
              	cmd   = splt.shift();
              	
              	/**
              	 * Store, so that apps can use #onHashChangeWatcher reference
              	 * to check for latest command.
                 */
              	this.command = cmd;
                
                /**
                 * Create args object.
                 */
                for(x=0; x < splt.length; x++)
                  {
                    s = splt[x].split('=');
                    args[s[0]] = decodeURIComponent(s[1]);
                  }
              	
                // "params" are the parameters in hash i.e. command + arguments
                params = args;
                s = cmd.split('=');
                params[s[0]] = decodeURIComponent(s[1]);
                  
                AXIS.onHashChange.fire({
                    command: decodeURIComponent(cmd),
                    args: args,
                    params: params
                });
              }
            return true;
          }
        });
      };
      
    /**
     * Determines paths to files needed to satisfy AXIS directes `extensions` and
     * `libraries`. Expects an array reference to push these results onto.
     *
     * NOTE: The filenames given in attributes `libraries` and `extensions` are
     * by default expected to exist within the folder containing the AXIS.  It is likely
     * that you may want to create custom extensions and libraries which will not
     * exist in the AXIS folder.  To do that, you simply prepend a tilde(~) to the 
     * library/extension name, followed by the path (either relative, or absolute).
     * Such as:
     *          libraries="array+string+~mylibrary/foo/bar/file.js+~http://www.foo.com/bar.js
     *
     * @param   {Array}     ret   An array to push resolved paths onto  
     * @param   {String}    qN    A directive string ("foo+bar...");
     * @param   {String}    aP    Added subpath (ie 'libraries', which means axis/libraries/)
     * @see     #initialize
     */
    this.buildPathForDirective = function(ret,qN,aP) 
      {
        var f, i;
        var a       = qN ? qN.split('+') : [];
        var aPath   = aP ? aP + '/' : '';

        for(i=0; i < a.length; i++) 
          {
            if(a[i].charAt(0) == '~')
              {
                f = a[i].substring(1,a[i].length);
              }
            else
              {
                f = AXIS.PATH() + aPath + a[i] + '.js';  
              }
        
            ret.push(f); 
          }
      };

    /**
     * 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() 
      {
        AXIS.createCoreErrorTypes();
        
        /**
         * Fired when the AXIS is loaded, initialized... ready
         *
         * @see AXIS#start
         */
        AXIS.onReady = AXIS.CustomEvent.create({
          name: 'onReady'
        });
          
        /**
         * Fires when the DOM is ready for manipulation
         *
         * @see AXIS#_onContentReady
         */
        AXIS.onDOMReady = AXIS.CustomEvent.create({
          name: 'onDOMReady'
        });
          
        /**
         * Fires when the window has loaded (window.onload)
         */
        AXIS.onWindowReady = AXIS.CustomEvent.create({
          name: 'onWindowReady'
        });
        
        /**
         * Fires just prior to the window being unloaded (user leaves or refreshes).
         * NOTE: Opera does not support this, and Chrome only seems to fire on refresh.
         */
        AXIS.onBeforeUnload = AXIS.CustomEvent.create({
          name: 'onBeforeUnload'
        });
        
        AXIS.attachEvent('beforeunload',function() {
          AXIS.onBeforeUnload.fire();
        }, window);
        
        /**
         * Fires when the window is resized NOTE how we are
         * attaching an event, below.
         */
        AXIS.onResize = AXIS.CustomEvent.create({
          name: 'onResize'
        });
        AXIS.attachEvent('resize',function() {
          AXIS.onResize.fire();
        },window);
        
        /**
         * Fires when the window is scrolled. NOTE how we are
         * attaching an event, below.
         */
        AXIS.onScroll = AXIS.CustomEvent.create({
          name: 'onScroll'
        });
        AXIS.attachEvent('scroll',function() {
          AXIS.onScroll.fire();
        },window);
          
        /**
         * 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 XHR object; it could be any essential file.
         */
        if(typeof XHR == 'function') 
          {
            AXIS._framework = [];      
          }
          
        var AF = AXIS._framework;
                  
        /**
         * Get any query args first
         */
        AXIS.fetchBuildArguments();
        
        /**
         * Load AXIS stylesheet.
         */
        this.includeCSS({
          id:   'css_axis_stylesheet',
          href: this.ROOT() + 'css/AXIS.css'
        });
          
        /**
         * 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')) 
          {
            // ABQIAAAABH14nUM9IOGSATH59A8PIxTtVJmlcGkc8uAjvGT8FSkFC9SscxRd4KeXJgXC39BF8yapmiOggBEOdg

            AF.push('http://www.google.com/jsapi?key=ABQIAAAAL888oCL6bdlp-RuWkSBsthQXxHUepxJBTAuGj9Pcf4C4H-lDUxRnOXsQgDZxtSvcHhv84_sjei_pWQ');
          }
        
        /**
         * Uses the YUI reset css
         */
        if(AXIS.settings('CSSReset'))
          {
            this.includeCSS({
              id:   'CSS_RESET',
              href: this.ROOT() + 'css/reset.css'
            });
          }  
          
        /**
         * Ensure there is JSON support.  Look for native; if not, use library.
         */
        if(AXIS.isUndefined(window.JSON))
          {
            this._settings.libraries += this._settings.libraries === "" ? 'json' : '+json';
          }
          
        /**
         * Check if user wants notifications turned off
         */
        if(AXIS.settings('disableNotifications')) 
          {
            AXIS._notificationsEnabled = false;
          }
          
        var AP  = AXIS.PATH();
        var lib = AXIS.settings('library');
            
        /**
         * Augment framework list with extensions, libraries.
         */        
        AXIS.buildPathForDirective(AF, AXIS.settings('extensions'));
        AXIS.buildPathForDirective(AF, AXIS.settings('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.  
 *
 * @see AXIS#initialize
*/

(function(){

var fi, r, n, p;
var fs            = AXIS._framework;
var AP            = AXIS.PATH();

/**
 * These are 
 */
var rObs          = [
  'Util',
  'Cookies',
  'User',
  'Logger',
  'Analytics',
  'Loader',
  'XHR',
  'WebDAV',
  'Login',
  'Bits',
  'CSS',
  'History'
];
/**
 * Go through the framework list, strip out paths and such, 
 * and get an array of object names.  At this point, check for directives.
 * 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(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 this file exists in the AXIS folder, then this is an extension.
     */
    if(fs[r].indexOf(AXIS.PATH() + fi)!=-1)
      {
        rObs.push(fi);
      }
    else
      {
        /**
         * Two cases here:
         * 1. This is a remote extension -- an extension that does not exist in
         *    the core axis/ folder.  To determine if this is an extensible function
         *    constructor, first check if it is a function, and if it is, check if
         *    it contains a #__construct method.  
         * 2. This is a library.  If so, nothing more needs to be done. Ignore.
         *
         * So really, all we are doing is checking if this is an extension.  Get
         * the file name (extension files are function constructors with a name
         * identical to the root file name, minus file extension), check if exists, do
         * the other checks mentioned above.
         */
         
        if( AXIS.isFunction(window[fi]) &&
            window[fi].toString().indexOf('this.__construct') )
          {            
            rObs.push(fi);
          }
      }
  }

/**
 * Register object and construct.
 */
for(n=0; n < rObs.length; n++)
  {
    try
      {
        AXIS.register(rObs[n]).__construct();
      } catch(e){}
  }
  

           
/**
 * Replace any vars that have been replaced
 *
 * @see #initialize
 */
for(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();
          }
          
        AXIS.createHashWatcher();
      };
      
    /**
     * Load any system css, various initializations. This starts the system.
     *
     * @see #initialize
     * @see #Queue
     */
    this.start  = function() 
      {  
        this._onContentReady();
          
        /**
         * Set window.onload handling
         */
        this.attachEvent('load',function() {
          AXIS.onWindowReady.fire();
        },window);
            
        this.Queue.start();
        
        /**
         * Why do we wait for DOM to be ready to say AXIS is ready?
         * Because a lot of AXIS components wait for DOM to be ready to complete their initialization.
         * So, if we wait in line after them, we can say AXIS is completely ready
         */
        AXIS.onDOMReady.subscribe({
            scope: this,
            callback: function() {
                this.onReady.fire();        
            }
        });
      };
      
    /**
     * 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}   group   An array of scripts.
     * @see #_includeScript
     */
    this.includeScriptGroup = function(group, fc) 
      {
        var finalCall = fc || AXIS.F;
        if(AXIS.isArray(group) && (group.length > 0)) 
          {
            var grpCount = group.length;
            var grp = function() 
              {
                --grpCount;
                if(grpCount < 1) 
                  {
                    finalCall();
                  }   
              };
                  
            for(var i=0; i < group.length; i++) 
              {
                if(AXIS.isString(group[i])) 
                  {
                    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, fc) 
      {
        var finalCall = fc || AXIS.F;

        if (chain.length == 0) {
            finalCall();
        }

        if(chain && AXIS.isArray(chain) && 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 scriptObj = d;
            if (AXIS.isString(d)) {
              scriptObj = {
                src: d
              };
            }
              
            var z     = scriptObj.onload || AXIS.F;
            scriptObj.onload = function() 
              {
                z();
                AXIS.includeScriptChain(chain, finalCall);      
              }  
            
            AXIS._includeScript(scriptObj); 
          }

      };
      
    /**
     * 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(AXIS.isArray(ob) && (ob.length > 0)) 
              {
                for(var x=0; x < ob.length; x++) 
                  {
                    AXIS._includeScript(ob[x]);  
                  }
                return true;
              }
            
            else if(AXIS.isObject(ob)) 
              {
                AXIS._includeScript(ob);
                return true;
              }
            
            else if(AXIS.isString(ob)) 
              {
                AXIS._includeScript({
                  src: ob
                });
                return true;
              }
          }
        
        return false;
      };
    
    /**
     * 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'
     *                            [charset] -- character set, default utf-8
     *                            [onload] -- To fire when script loaded
     *                            [method] -- 'write' || 'insert'
     *                          }
     * @private
     * @see #contentReady
     * @throws  Error   AXIS_INCLUDE_SCRIPT_NO_SRC
     */
    this._includeScript = function(ob) 
      {   
        var onloadStr   = '';
        var rscr        = AXIS._registeredScripts;
        
        ob.onload       = ob.onload || AXIS.F;

        if(AXIS.isUndefined(ob.src) || ob.src.toString() == "") 
          {
            new AXIS.Errors.AXISException('AXIS_INCLUDE_SCRIPT_NO_SRC').report();  
            return;
          }
          
        /**
         * Check if we have already loaded this script, and if we have, exit.
         * NOTE that we still fire the onload handler for the script, if any.
         */
        for(var q in rscr)
          {
            if(rscr[q].src === ob.src)
              {
                ob.onload();
                
                return true;  
              }
          }
          
        ob.id         = ob.id || AXIS.getUniqueId('script_'); 
        ob.charset    = ob.charset || 'utf-8';
        ob.type       = ob.type || 'text/javascript';
          
        /**
         * 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).
         */
        if(ob.method === 'write' || (AXIS.onDOMReady.hasFired() === false)) 
          {
            
            if(ob.register) 
              {
                onloadStr += "AXIS.register('" + ob.baseName + "');";
              }
          
            onloadStr +=   'AXIS._registeredScripts["' + ob.id + '"].onload();';
            
            /**
             * We store a copy of the call object mainly to store the onload
             * handler reference (if any) to be called when written script loads.
             * Stored on DOM insert as well, below.
             */
            AXIS._registeredScripts[ob.id] = ob;
        
            document.write('<' + 'script' + ' id="' + ob.id + '" type="' + ob.type + '" src="' + ob.src + '" charset="' + ob.charset + '"> </' + 'script' + '>' + '<' + 'script' + ' type="text/javascript">' + onloadStr + '</' + 'script' + '>');
        
            return true;
          }
          
        /**
         * If the DOM is loaded, append to HEAD script collection.
         */
        var hT    = document.getElementsByTagName('head')[0]; 
        var s     = document.createElement('script'); 
        s.id      = ob.id; 
        s.type    = ob.type;
        s.src     = ob.src; 
        s.charset = ob.charset;
        
        if(ob.onload) 
          {
        		s.onload = s.onreadystatechange = function() 
          		{
          			if( !this.__loaded__  
          			    && (  !this.readyState 
          			          || this.readyState == "loaded" 
          			          || this.readyState == "complete"
          			        )
          			  ) 
          			  {
          			    /**
          			     * A flag to indicate that this script has loaded... see
          			     * above for info on why.
          			     */ 
            			  this.__loaded__ = true;
            					      
            				/**
            				 * 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;
      };
    
    /**
     * 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
     */
    this.includeCSS = function(ob) 
      {
        var ss = AXIS._registeredStyleSheets;

        try 
          {
            if(ob && ob.href.toString() == "") 
              {
                throw new AXIS.Errors.AXISException('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';
            
            /**
             * Don't reload the style sheet if already exists.
             */
            for(var q in ss)
              {
                if(ss[q].href === ob.href)
                  {
                    return true;  
                  }  
              }
    
            AXIS._registeredStyleSheets[css.id] = ob;
            hT.appendChild(css);
            return true;
          }
        catch(e) 
          {
            e.report();
            return false;
          }
      };


    /**
     * Regular Expressions library.  Used variously.     
     *
     */
    this.Regexes  = 
      {
        /**
         * #parseUrl Regex from the reverseHTTP javascript layer.
         * http://github.com/tonyg/reversehttp/blob/master/priv/www/httpd.js
         *
         * @sandro :  modified capturing group for .parseUrl fragment(#), changing:
         *              (#(\w*)) (matching on word characters)
         *      to :    (#(.*)) (any single character)
         *
         * @see #parseUrl
         */
        parseUrl:            /^((\w+):)?(\/\/((\w+)?(:(\w+))?@)?([^\/\?:]+)(:(\d+))?)?(\/?([^\/\?#][^\?#]*)?)?(\?([^#]+))?(#(.*))?/,
        text:           /^[\S\ ]{1,}$/, // a non null string, spaces included
        oneChar:        /^[^\s]{1,}$/, // a non null string, no spaces
        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}$/,
        DBFieldName:    /^[_$a-zA-Z]\S{0,99}$/,
        
        allDigits:      /^\d+$/,
        allAlpha:       /^[a-zA-Z]+$/,
        username:       /^[A-Za-z-_!:~\w]{5,63}$/,
        password:       /^[A-Za-z-_!:~\w]{5,255}$/,
        JSONP:          /^[\s\u200B\uFEFF]*([\w$\[\]\.]+)[\s\u200B\uFEFF]*\([\s\u200B\uFEFF]*([\[{][\s\S]*[\]}])[\s\u200B\uFEFF]*\);?[\s\u200B\uFEFF]*$/
      };

    /**
     * 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        || 10000000000;
            ob.maxIterations      = ob.maxIterations    || 10000000000;
            ob.onBeforeDie        = ob.onBeforeDie      || AXIS.F;
            ob.main               = ob.main             || AXIS.F;
                
            /**
             * This is what to call should you want to terminate a process.
             */
            ob.die                = function(idx) 
              {
                this.onBeforeDie(this);
                AXIS.Queue.remove(idx || this);
              };
    
            /**
             * Execute the main method immediately, and if it is to remain
             * on the queue, add it.
             */
            ob.main() && 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;
     
            while(c--) 
              {
                qc = q[c];
                qc.__ITERATIONS__++;
                qc.__TIMELAST__     = qc.__TIMECURRENT__;
                qc.__TIMECURRENT__  = d.getTime();     
                qc.__TIMETOTAL__    = qc.__TIMECURRENT__ - qc.__TIMESTART__;
                qc.__LASTXINDEX__   = c;
      
                /**
                 * Check for death conditions
                 */
                if( (qc.lifespan < qc.__TIMETOTAL__) || 
                    (qc.maxIterations < qc.__ITERATIONS__) || 
                    !!qc.main() === false) 
                  {
                    qc.die(c);  
                  }            
              }
          },
            
        /**
         * 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.
         * @type  Number
         */
        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;
          },
           
        /**
         * Removes sent object instance, if any
         *
         * @param      {Mixed}     r     A Queue object, or a Queue index.
         * @type       {Boolean}
         */
        remove: function(r) 
          {
            /**
             * Removal involves marking for cleanup, not simply deleting. The aim
             * is to kill only via using the natural destructor, #die.
             */
            
            /**
             * If passed an index, use that. Note that we check if
             * the object at given index 
             */
            if(r && AXIS.isNumber(r)) 
              {
                try 
                  { 
                    this._queue[r].main = AXIS.F;
                    return true;  
                  } 
                catch(e) 
                  {
                    return false;
                  }
              }
            
            /**
             * Find and remove object
             */
            var i = this._queue.length;
            while(i--) 
              {
                if(this._queue[i] === r) 
                  {
                    this._queue[i].main = AXIS.F;     
                    return true;
                  }  
              }
            
            return false;
          },
          
        /**
         * Destroys all objects in the Queue
         */
        clear: function() 
          {
            var i = this._queue.length;
            for(var x = i; x >= 0; x--) 
              {
                this.remove(x);  
              }
            return i;
          },
            
        start: function() 
          {
            AXIS.Queue._timer = setInterval(AXIS.Queue.walk,1);
          },
            
        stop: function() 
          {
            clearInterval(AXIS.Queue._timer);
          }
      };

    /**
     * Object facilitating document.createElement() and similar
     * types of document element creation and manipulation needs.
     *
     * @param    {Mixed}     t     1. A DOM element;
     *                              2. A DOM element Id;
     * @param    {Object}    p     properties, all optional, in form:
     *                        {
     *                          html:   html to insert,
     *                          class:  class attribute,
     *                          style:  {
     *                                    width:  '20px',
     *                                    height: '5px'
     *                                  },
     *                          events: {
     *                                    'click':  function() {
     *                                                // do something
     *                                              }
     *                                  }
     *
     *                          // ...and any other DOM attributes.
     *                          id:     'idstring',
     *                          title:  'some title'
     *                        },                         
     */
    this.Element = 
      {
        /**
         * Stores information when Element is extended (via #extended).
         *
         * @see AXIS#attachEvent
         */
        extensions: [],
        
        clone: function(ob)
          {
            if(AXIS.isElement(ob))
              {
                return ob.cloneNode(true);  
              }  
            return null;
          },
          
        setProperties: function(el, ops)
          {
            if(AXIS.isElement(el) === false)
              {
                return false;  
              }
            
            ops = ops || {};
            
            var p, pp, r, ob;
            
            for(p in ops) 
              {
                /**
                 * Get the option key/value
                 */
                pp = ops[p];
                
                /**
                 * Is the value an object?
                 */
                ob = AXIS.isObject(pp);
                
                switch(p) 
                  {
                    
                    case 'html':
                      el.innerHTML = pp;
                    break;
                    
                    case 'text':
                      el.appendChild(document.createTextNode(pp));
                    break;
                            
                    case 'class':
                    case 'className':
                      el.className = pp;
                    break;
                    
                    case 'style':
                      if(ob) 
                        {
                          for(r in pp) 
                            {
                              el.style[r] = pp[r];  
                            }
                        }
                    break;
                    
                    case 'events':
                      if(ob) 
                        {
                          for(r in pp) 
                            {
                              AXIS.attachEvent(r, pp[r], el);
                            }
                        }
                    break;
                    
                    case 'append':
                      if(AXIS.isArray(pp)) 
                        {
                          for(r in pp) 
                            {
                              if(AXIS.isElement(pp[r]))
                                {
                                  el.appendChild(pp[r]);
                                }
                            }
                        }
                    break;
            
                    default:
                      try
                        {
                          el.setAttribute(p, '' + pp);
                        }
                      catch(e)
                        {                      
                          el[p] = pp;
                        }
                    break;  
                  }
              } 
            
            return el; 
          },
        
        /**
         * Creates a new element. If you pass an #appendTo argument to the `ops` parameter,
         * the newly created element will be appended to the specified target.
         *
         * @param     {Mixed}     targ    Either a string or a DOM element. If a string, it is
         *                                assumed to be a tag name ('div', 'a', ...) and a new
         *                                element of that type will be created.  If a DOMelement,
         *                                the assumption is that you want to create a clone, which
         *                                is what will be created.  In both cases, the property
         *                                options will augment the newly created object.
         * @param     {Object}    ops     Atributes and other properties to set on the new object.
         */  
        create: function(targ,ops) 
          {
            ops = ops || {};
            
            var ii  = ops.appendTo;
            ii      = !!ii
                      ? AXIS.isString(ii)
                        ? AXIS.find(ii) 
                        : ii 
                      : document.body;
                      
            delete ops.appendTo;
                        
            if(AXIS.isUndefined(targ) || AXIS.onDOMReady.hasFired() === false) 
              {
                return false;
              }

          	var el  = AXIS.isString(targ) ? document.createElement(targ) : this.clone(targ);
            el      = this.setProperties(el, ops);

            ii.appendChild(el);

            return el;
          },
          
        /**
         * Updates the properties of a DOM Element.
         *
         * @param     {Mixed}     targ    Either a string or a DOM element. If a string, it is
         *                                assumed to be an element id, and an attempt will be made
         *                                to find and use the element with that id. If a DOMelement,
         *                                new properties will be applied to that object.
         * @param     {Object}    ops     Atributes and other properties to set on the object.
         */
        set: function(targ,ops) 
          {
            ops = ops || {};
                        
            if(AXIS.isUndefined(targ) || AXIS.onDOMReady.hasFired() === false) 
              {
                return false;
              }
                
          	/**
          	 * Get the reference and change its properties.
          	 */
          	var el  = AXIS.isString(targ) ? AXIS.find(targ) : targ;
            el      = this.setProperties(el, ops);
            
            return el;
          },
        
        /**
         * Extends the methods available to DOM Elements.  Ideally this will 
         * extend the HTMLElement.prototype (global element method augmentation).
         * For browsers that do not support that (IE it seems), we use a little
         * trick to achieve the same thing.
         */
        extend: function(o) 
          {
            var ob    = o || {};
            var name  = !AXIS.isUndefined(o.name) && AXIS.isString(o.name) ? o.name : false;
            var fn    = !AXIS.isUndefined(o.func) && AXIS.isFunction(o.func) ? o.func : false;
            
            if(!name || !fn) 
              {
                return;  
              }

            /**
             * Store 
             *
             * @see #attachEvent
             */
            this.extensions[name] = fn;  
            
            /**
             * Anything other than IE can use the HTMLElement prototype
             */
            if(AXIS.isIE === false) 
              {
                HTMLElement.prototype[name] = fn;
                return;
              }
    
            /**
             * Alter element selection methods so that what is returned
             * is automatically updated with extended element methods.
             */
            var _createElement = document.createElement;
            document.createElement = function(tag) 
              {
                var e = _createElement(tag);
                if(e) { e[name] = fn; }
              	
              	return e;
              }
            
            var _getElementById = document.getElementById;
            document.getElementById = function(id) 
              {
                var e = _getElementById(id);
              	if(e) { e[name] = fn; }
                return e;
              }
            
            var _getElementsByTagName = document.getElementsByTagName;
            document.getElementsByTagName = function(tag) 
              {
              	var a = _getElementsByTagName(tag);
              	var z = a.length;
              	while(z--) 
                	{
                		a[z][name] = fn;
                	}
              	
                return a;
              }
          }
      };
      
    /**
     * Creates a subscribable event.
     */
    this.CustomEvent = 
      {    
        /**
         * Going to store references to created events to allow the firing
         * of custom event objects without access to the original reference.  Note
         * that this is only done for created custom events that are passed a #name
         */
        events: [],
        
        /**
         * This is a special method that allows the firing of a custom event
         * identified by the #name attribute passed when the event was created.
         *
         * @see #create
         */
        fire: function(nm,aob) 
          {
            if(nm && this.events[nm]) 
              {
                this.events[nm].fire(aob || false);
              }
          },
        
        /**
         * This is a special method that allows the subscribing to a custom event
         * identified by the #name attribute passed when the event was created.
         *
         * @see #create
         */
        subscribe: function(nm,sob) 
          {
            if(nm && this.events[nm] && sob) 
              {
                this.events[nm].subscribe(sob);
              }
          },
        
        hasFired: function(nm, wlf) 
          {
            return  (nm && this.events[nm]) 
                      ? this.events[nm].hasFired(wlf || false) 
                      : null;
          },
          
        /**
         * Creates a custom event.  It is expected that the object returned by this
         * method will be stored somewhere permanent, and fired when necessary, ie:
         *
         * var myCustomEvent = AXIS.CustomEvent.create({...});
         * myCustomEvent.fire();
         *
         * By using the optional #name attribute, you can also identify this event
         * for use without having a permanent reference, ie:
         *
         * var myCustomEvent = AXIS.CustomEvent.create({name: 'myName'});
         * AXIS.CustomEvent.fire('myName');
         *
         * @param    {Object}      ob    Object of form:
         *              {
         *                {String}  [name]        Name of event.
         *                {Object}  [scope]       Scope to fire subscrber handler in. Default window.
         *                {Boolean} [wait]        See notes for #subscribe, below.
         *              }
         */
        create: function(ob) 
          {
            var d         = ob || {};
    
            var evOb = function() 
              {
                this._subscribers   = {};
                this._hasFired      = false;
                this._lastFiredArgs = false;
                this._scope         = d.scope || window;
                this._name          = d.name || '';
                this._wait          = !!d.wait;
              };
                  
            evOb.prototype = 
              {
                /**
                 * Subscribes to an event.
                 *
                 * @param      {Object}      ob    Object of form:
                 *                {
                 *                  {Function}  callback    Function to call when event fires.
                 *                  {Object}    [object]    An object which is available to #fire
                 *                  {Boolean}   [wait]      Wait for next firing (see docs below)
                 *                  {Boolean} [justOnce] Set to true if you want to be unsubscribed after first time
                 *                  {Mixed}     [scope]     Scope to fire subscrber handler in. Default window.
                 *                  {Mixed} [position] If set to 'first', subscriber will be triggered first, if 'last', triggered last, else triggered in order of subscription
                 *                }
                 * @return identifier               
                 */
                subscribe: function(ob) 
                  {                   
                    var b         = ob          || {};
                    b.callback    = b.callback  || AXIS.F;
                    b.object      = b.object    || {};
                    b.wait        = !!b.wait;
                    b.scope       = b.scope     || this._scope;
                    
                    if (ob.position == 'first') {
                        b.identifier = 1000; // first thousand should be guaranteed to be first
                        while (this._subscribers[b.identifier]) // get a unique one
                        {
                            b.identifier = b.identifier - 1;
                        }
                    }
                    else 
                        if (ob.position == 'last') {
                            b.identifier = (new Date().getTime()) + (24 * 60 * 60 * 1000); // 24 hours later should be guaranteed to be last
                            while (this._subscribers[b.identifier]) // get a unique one
                            {
                                b.identifier += 1;
                            }
                        }
                        else {
                            b.identifier = (new Date().getTime());
                            while (this._subscribers[b.identifier]) // get a unique one
                            {
                                b.identifier += 1;
                            }
                        }
                    
                    /**
                     * Prepare the scoped callback. See below and #fire.
                     */
                    b.callback    = AXIS.curry(b.callback, b.scope);  
                       
                    this._subscribers[b.identifier] = b;  
        
                    /**
                     * At the point of subscription an event may have already fired.
                     * The default behaviour is, if already fired, to fire the 
                     * callback for this subscription immediately.  This is useful in 
                     * situations like DOMReady or WindowLoaded, which will fire
                     * once only, and if subscribed to multiple times it is expected
                     * the developer wants the subscription to immediately fire.  However,
                     * it is also the case that some subscriptions want to wait for 
                     * the next event, and are indifferent to, or misled by, immediate
                     * firing.  In the latter case, the subscriber can set the .wait
                     * property of the subscription argument to true, avoiding immediate
                     * firing.  NOTE: The #create method also allows forcing a wait on
                     * on subscriptions, which forces second behaviour of second case.
                     *
                     * @see #create
                     */
                    if(this._hasFired && (b.wait === false && this._wait === false)) 
                      {
                        b.callback({
                          name:   this._name,
                          data:   this._lastFiredArgs,
                          object: b.object
                        });
                        if( b.justOnce === true )
                        {
                          this.unsubscribe(b.identifier);
                        }
                      }
                    return b.identifier;
                  },
                
                /**
                 * Delete subscriber with given identifier
                 *
                 * @param    {String}      identifier - What subscribe() returned
                 * @return {Boolean} - If there was something to unsubscribe
                 */
                unsubscribe: function(identifier) 
                  {
                      if (this._subscribers[identifier]) {
                          delete this._subscribers[identifier];
                          return true;
                      }
                      return false;    
                  },
                
                /**
                 * Destroys all subscribers for this event
                 */
                unsubscribeAll: function() 
                  {
                    this._subscribers = {};
                    return true;
                  },
                  
                fire:           function(a) 
                {
                    if (this._name != '') { 
                        AXIS.Logger.log('events', 'debug', this._name); 
                    }
                      var ss;
                    /**
                     * @see  #subscribe
                     * @see  #hasFired
                     */
                    this._hasFired        = true;
                    this._lastFiredArgs   = a;
                    var orderedSubscribers = [];
                    for(var identifier in this._subscribers) 
                      {
                          orderedSubscribers.push(identifier);
                       }
                    orderedSubscribers.sort();
                    
                    for (var i = 0; i < orderedSubscribers.length; i++) {
                        ss = this._subscribers[orderedSubscribers[i]];
                        ss.callback({
                            name: this._name,
                            data: this._lastFiredArgs,
                            object: ss.object
                        });
                        if( ss.justOnce === true )
                        {
                          this.unsubscribe(ss.identifier);
                        }
                    }
                  },
                
                /**
                 * Whether or not this event has fired at least once.  By
                 * passing wLastF argument, will still return false if has
                 * not fired, but will pass the arguments passed by the last
                 * #fire execution, if any.  As this is still truthy, it's 
                 * a simple way to get more info in one call, but be careful.
                 */
                hasFired: function(wlf) 
                  {
                    return wlf ? this._lastFiredArgs : this._hasFired;  
                  }
              };
            
            var ce = new evOb;
            
            /**
             * If we're passed a #name, store the custom object for later reference
             */
            if(AXIS.Regexes.oneChar.test(ce._name)) 
              {
                if(this.events[ce._name]) 
                  {
                    new AXIS.Errors.AXISException('CUST_EVENT_NAME_DUPLICATE');
                    return;
                  }
                
                AXIS.CustomEvent.events[ce._name] = ce;
              }
            
            return ce;
          }
      };

    /**
     * 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 && Object.prototype.toString.apply(a) === '[object Array]';
      };
      
    /**
     * Whether a given value is an Object.
     *
     * @param     {Mixed}     a     The value to check
     * @type      {Boolean}
     */
    this.isObject = function(a) 
      {
        return !!a && Object.prototype.toString.call(a) === '[object Object]';  
      };
      
    /**
     * Whether a given value is a Function.
     *
     * @param     {Mixed}     a     The value to check
     * @type      {Boolean}
     */
    this.isFunction = function(a) 
      {
        return !!a && a.constructor === Function;  
      };
      
    /**
     * Whether a given value is a String.
     *
     * @param     {Mixed}     a     The value to check
     * @type      {Boolean}
     */
    this.isString = function(a) 
      {
        return  typeof a !== 'undefined' && 
                a !== null && 
                a.constructor === String;  
      };
      
    /**
     * Whether a given value is a Number.
     *
     * @param     {Mixed}     a     The value to check
     * @type      {Boolean}
     */
    this.isNumber = function(a) 
      {
        return  typeof a !== 'undefined' && 
                a !== null && 
                a.constructor === Number;  
      };
    
    /**
     * Whether a given value is a Boolean.
     *
     * @param     {Mixed}     a     The value to check
     * @type      {Boolean}
     */
    this.isBoolean = function(a) 
      {
        return  typeof a !== 'undefined' && 
                a !== null && 
                a.constructor === Boolean;  
      };
      
    /**
     * Whether a given value is a Regular Expression.
     *
     * @param     {Mixed}     a     The value to check
     * @type      {Boolean}
     */
    this.isRegExp = function(a) 
      {
        return !!a && a.constructor === RegExp;  
      };
      
    /**
     * Whether a given value is an DOM element.
     *
     * @param     {Mixed}     a     The value to check
     * @type      {Boolean}
     */
    this.isElement = function(a) 
      {
        return  typeof HTMLElement === 'object' 
                ? !!a && a instanceof HTMLElement 
                : !!a && typeof a === 'object' && 
                  a.nodeType === 1 && 
                  typeof a.nodeName === 'string';
      };
      
    /**
     * Whether a given value is undefined.
     *
     * @param     {Mixed}     a     The value to check
     * @type      {Boolean}
     */
    this.isUndefined = function(a) 
      {
        return typeof a === 'undefined';
      };
    
    /**
     * Breaks a url into its component parts.
     *
     * @param   {String}    [url]   The url to parse. Default is document location.
     */
    this.parseUrl = function(url)
      {
        url = url || window.location.href;
        var m = url.match(AXIS.Regexes.parseUrl);

        var resp = 
          {
            'url'         : m[0],
            'protocol'    : m[2],
            'username'    : m[5],
            'password'    : m[7],
            'host'        : m[8]  || "",
            'port'        : m[10],
            'pathname'    : m[11] || "",
            'querystring' : m[14] || "",
            'fragment'    : m[16] || ""
          };
         
        /**
         * Try and find a querystring argument matching sent parameter. 
         *
         * @param   {String}    p     A querystring key. Given ?foo=bar: sending
         *                            'foo' would return 'bar'.
         * @type    {Mixed}           A string if found with value. (foo=bar);
         *                            Null if no param (no `foo=` key present);
         *                            undefined if no value (foo=&bar=2)
         *
         */
        resp.param = function(p)
          {
            var ex = new RegExp("[?&]" + p + "=([^&]*)?","i").exec('?' + this.querystring);
            return !ex ? null : ex[1];
          }
         
         // Similar to resp.param(), but for hash parameters
         resp.hashParam = function(p)
         {
            var ex = new RegExp("[#&]" + p + "=([^&]*)?","i").exec('#' + this.fragment);
            return !ex ? null : decodeURIComponent(ex[1]);
         }
         resp.getAllHashParams = function()
         {
             var args  = this.fragment.split('&');
             var hashParams = {};
             for (var x = 0; x < args.length; x++) {
                 var s = args[x].split('=');
                 hashParams[s[0]] = decodeURIComponent(s[1]);
             }
             return hashParams;
         }
         return resp;
      };

    
    this.clone = function(obj, deep)
      {
        var c;
        
        /**
         * Non objects aren't passed by reference, so just send it back.
         */
        if(AXIS.isObject(obj) === false)
          {
            return obj;
          }
          
        c = new obj.constructor(); 
    
        for(var p in obj)
          {
            c[p] = deep ? this.clone(obj[p]) : obj[p];
          }
        
        return c;
      };
    
    /** 
     * document.getElementById() shortcut. 
     * @param       {String}    id    An id attribute of a document element
     * @returns                       element or null
     */
    this.find     = function(id) 
      {
        return id ?  document.getElementById(id) : null;  
      };
    
    /** 
     * Returns the text inside of an element, if any. 
     * @param       {Mixed}    el     An element id string, or an element reference.
     * @type        {String}
     */
    this.getText  = function(el) 
      {
        var t =   AXIS.isString(el) 
                  ? AXIS.find(el) 
                  : AXIS.isElement(el) 
                    ? el 
                    : false;
        if(t) 
          {
            return el.innerText || el.textContent;
          }
        
        return '';
      };
            
    /**
     * 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 obj = ob || document;
        if(obj.addEventListener) 
          {
            obj.addEventListener(ev, f, false);
          }
        else
          {  
            /**
             * IE needs some special treatment.  There are three things.
             * First, we need to attach any DOM Element extensions which
             * have been registered: @see #Element#extend. Other browsers 
             * allow modification of HTMLElement.prototype, so this is not 
             * necessary for them.  Second, IE doesn't properly provide the 
             * `this` context to an event handler, so we create a `wrapper` 
             * function that will properly contextualize the handler.
             * Related to second point, we also set the `target` property of the
             * event object returned to handler (ie. handler(e){}) to the 
             * element to maintain consistency with the common .target property
             * provided by other browser implementations. And finally, we need
             * to store this new handler wrapping function so that when a
             * #detachEvent request is made, we can use this lookup to find
             * the relevant wrapper function which was used.
             *
             * @see #detachEvent
             * @see #Element#extend
             */
            var nf = function(el) 
              {
                el.target = window.event.srcElement || document;
                for(var w in AXIS.Element.extensions)
                  {
                    el.target[w] = AXIS.Element.extensions[w];
                  }  

                f.call(ob,el);
              }
            
            AXIS.IE_EVENTS['IE' + ev + f] = nf;
            obj.attachEvent('on'+ev, nf);
          } 
      };  

    /**
     * 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 obj = ob || document;

        if(obj.removeEventListener)
          {                                       
            obj.removeEventListener(ev, f, false);
          }
        else
          { 
            /**
             * @see #attachEvent
             */
            var id = 'IE' + ev + f;
            f = AXIS.IE_EVENTS[id] || f;   
            obj.detachEvent('on'+ev, f);  
            delete AXIS.IE_EVENTS[id];                  
          } 
      };
      
    /**
     * Fires a bound event on a given element.  
     *
     * @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 scope of the element. Default to `document`
     */
    this.fireEvent = function(ev,el,ob)
      {
        var obj = ob || document;
        if(document.createEvent)
          {
            var evt = obj.createEvent("HTMLEvents");
            evt.initEvent(ev, true, true );
            return !el.dispatchEvent(evt);
          }
        else
          {
            var evt = obj.createEventObject();
            el.fireEvent('on' + ev, evt);
          }
      };
      
    /**
     * Stops event from triggering any event handlers set on surrounding elements.
     * NOTE: This does *not* stop the default action for the event, if any.
     * 
     *  @param {Event} e   The event object
     */
    this.stopPropagation = function(e)
      {
        e.cancelBubble = true;
        if(e.stopPropagation) 
          {
            e.stopPropagation();
          }
      };
      
    /**
     * Stops the default behaviour of the event from happening.  For example,
     * if you have a checkbox with an onclick handler, you could handle the
     * click, then .preventDefault(), and the checkbox would not be checked
     * (which is the default behaviour of clicking on a checkbox).
     * NOTE: This does *not* stop propogation of the event.
     * 
     *  @param {Event} e   The event object
     */
    this.preventDefault = function(e)
      {
        e.returnValue = false;
        if(e.preventDefault) 
          {
            e.preventDefault();
          }
        return 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;  
          }
      };
      
    /**
     * 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.
     *      button    {Boolean}   Whether to show a close button. Default true.
     *      onDismiss {Function}  A function to execute when notification dismissed.
     *      type      {String}    The type of notification (TODO)
     *    }
     */
    this.showNotification = function(v) 
      {
        v = v || {};
        if(v.content && this._notificationsEnabled) 
          {
            AXIS.onDOMReady.subscribe({
              callback: function() 
                {
                  var b           = v.button || true;
                  var t           = v.type || 'default';
                  b               = b ? '<input class="' + AXIS.notificationCloseButtonClass + '" type="button" value="OK" onclick="this.parentNode.nClose()" />' : '';
                  var n           = AXIS.find(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,
                    startDelay:   AXIS._notificationFadeDelay,
                    deleteOnEnd:  true
                  });
                      
                  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,n);
                }
            });
          }
      };
      
    /**
     * 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
     *                          [time]  Ms fade runs for
     *                          [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 === undefined) ? 100 : ob.startOpacity;
        var endOpacity        = (ob.endOpacity === undefined) ? 0 : ob.endOpacity;
        var time              = (ob.time === undefined) ? -AXIS.defaultFadeSpeed : -ob.time;
        var delta             = Math.abs(startOpacity - endOpacity);
        var startDelay        = (ob.startDelay === undefined) ? 0 : ob.startDelay;
        var deleteOnEnd       = (ob.deleteOnEnd === undefined) ? false : !!ob.deleteOnEnd;
        var onComplete        = ob.onComplete || AXIS.F;
          
        var curOp             = startOpacity;
        
        /**
         * Lose any previous fades on this element
         */
        AXIS.Queue.killByPropertyValue('_fade_el',el);
          
        var doOpacity = function(newop) 
          {
            curOp              = Math.abs(newop);
            el.style.opacity   = curOp/100;
            el.style.filter    = 'alpha(opacity=' + curOp + ')';
          };
          
        doOpacity(startOpacity);
          
        return AXIS.Queue.add({
          _forceFade:   false,
          _fade_el:     el,
          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   = ((elapsed - startDelay) / time) * delta;
      
                  doOpacity(startOpacity + dec);
      
                  if(Math.abs(dec) >= Math.abs(startOpacity - endOpacity)) 
                    {
                      doOpacity(endOpacity);
                              
                      if(deleteOnEnd === true && el.parentNode) 
                        {
                          el.parentNode.removeChild(el);
                        }
                      onComplete();  
                      return false;  
                    }
                }            
              return true;
            }
        });
      };
  };
  
/**
 * Build custom library creation API into AXIS prototype.  
 */
$AXIS.prototype = new function()
  { 
    /**
     * @see #store
     */      
    this.$$     = [];
      
    /**
     * The iff/endiff truth state
     *
     * @see #iff
     * @see #endiff
     */
    this.$$if   = true;
    
    /**
     * An if/iff construct. Can be passed one or two arguments, each of which may 
     * be of any type, including functions.  Two cases, 1 or 2 arguments, A or B:
     * 
     * A: A Function argument is evaluated in the scope of AXIS.$, or used as sent.
     *    The processed argument  is then "cast" to a boolean,  creating the truth 
     *    condition for an `if` block.
     * B: Function arguments are evaluated in the scope of AXIS.$, or used as sent.
     *    No casting is done; the truth condition of an `iff` block is
     *    the result of comparison (arg1 === arg2).
     *
     * @see #$elsif
     * @see #$endif
     */
    this.$if = function()
      {
        var a = arguments;
        var f = function(arg)
          {
            return  AXIS.isFunction(arg)
                    ? arg.apply(AXIS.$)
                    : arg;
          };
          
        var t1 = f(a[0]);
        var t2 = f(a[1]);
        
        AXIS.$$if = (a.length === 1) 
                    ? !!t1
                    : (a.length === 2)
                      ? t1 === t2
                      : false;
                      
        return this;
      }; 
    
    /**
     * @see #$if
     */
    this.$elsif = function()
      {
        return this.$$if === false ? this.$if(arguments[0],arguments[1]) : this;  
      };
    
    /**
     * @see #$if
     */
    this.$endif = function()
      {
        AXIS.$$if = true;
        
        return this;
      };

    this.scope  = function(sc, build)
      {
        AXIS.$ = build ? new window[sc] : sc;
        
        return this;
      };
    
    /**
     * Stores a value at a given namespace.  If no key value is given,
     * will store value at ns.$.  If nothing is sent, stored this.$$.
     * 
     * @example   f('my.namespace.here','that') === $AXIS.my.namespace.here.that;
     *            f('other.namespace')          === $AXIS.other.namepspace.$;
     *            f()                           === AXIS.$$
     * @param     {String}    [nm]  A namespace.
     * @param     {String}    [k]   An specific key in the namespace.
     */
    this.store = function(nm,k)
      { 
        if(arguments.length === 0)
          {
            AXIS.$$ = AXIS.clone(AXIS.$);
          }
        else if(nm)
          {
            var ns = AXIS.createNamespace(nm);
            ns[k || '$'] = AXIS.clone(AXIS.$);
          }
        
        return this;
      };
      
    this.restore = function()
      {
        AXIS.scope(AXIS.$$);
        
        return this;
      };
  
    this.run = function(f)
      {
        var e;
        f = AXIS.isArray(f) ? f : [f];
        
        for(var w=0; w < f.length; w++)
          {
            e = f.apply(AXIS.$, Array.prototype.slice.call(arguments, 0));
            if(e !== undefined)
              {
                AXIS.$ = e;  
              }
          }
          
        return this;
      };

    this.extend = function(a)
      {
        if( AXIS.isObject(a) === false ||  
            AXIS.isString(a.name) === false || 
            AXIS.isFunction(a.func) === false)
          {
            alert("AXIS.extend() Arguments malformed. Probably no #name, no #func, or both missing.");
            return false;
          }

        var methName    = a.name;
        var chainType   = a.expects || 'Array';
        var func        = a.func;
        var ns          = a.namespace || false;
        
        var cts         = '[object ' + chainType + ']';
        var cins        = new window[chainType];
        var op          = Object.prototype.toString;
        var ap          = Array.prototype.slice;
        
        var onBefore    = AXIS.isFunction(a.onBefore) ? a.onBefore : false;
        var onAfter     = AXIS.isFunction(a.onAfter) ? a.onAfter : false;

        var args, callDesc, R, lastR;
        
        if((ns && ns.charAt(0) === '$') || methName.charAt(0) === '$')
          {
            alert("AXIS.extend() Method or Namspace names cannot begin with dollar sign($)");
            return false;  
          }
        
        /**
         * This is the business function, attached to a namespace if requested.
         */
        var aFunc = function()
          { 
            args = ap.call(arguments,0);
            
            /**
             * If we're in a failed `iff` condition, return this.
             *
             * @see #$if
             * @see #$endif
             */
            if(AXIS.$$if === false)
              {
                return this;  
              }

            /**
             * Unscoped? Create one based on chainType
             */
            !AXIS.$ && AXIS.scope(chainType,1);
            
            callDesc = 
              {
                'name':     methName,
                'func':     func,
                'scope':    AXIS.$,
                'args':     args
              };
            
            onBefore && onBefore.call(null, callDesc);
            
            /**
             * Ensure that the function executes in the scope it expects.
             * Only update scope value if something is returned.
             */
            var R = func.apply(op.apply(AXIS.$) === cts ? AXIS.$ : cins, args); 
            
            if(AXIS.isUndefined(R) === false)
              {
                /**
                 * If the last operation was destructive, we store the
                 * current (pre-change) $, so it can be recovered.
                 */
                if(lastR !== R)
                  {
                    lastR = AXIS.$$ = AXIS.clone(AXIS.$);
                  }
                
                AXIS.$ = R;
              }
            
            onAfter && onAfter.call(null, callDesc);
            
            return this;
          };
        
        /**
         * Check if a namespace was sent, create it if it doesn't exist,
         * and add named(methName) method to the namespace collection.  Notice
         * how if no namespace is sent, `this` (AXIS) is the namespace.
         */
        if(ns)
          {
            if(AXIS.isUndefined(this[ns]))
              {
                var f           = function(){}
                f.prototype     = this;
                this[ns]        = new f;
                this[ns].$if    = this.$if; 
                this[ns].$elsif = this.$elsif; 
                this[ns].$endif = this.$endif; 
              } 
              
            if(this[ns].hasOwnProperty(methName))
              {
                alert('AXIS.' + ns + '.' + methName + ' < Method exists.  No changes made.');
                return false;
              }
              
            this[ns][methName] = aFunc;
          }
        else
          {
            if(this[methName])
              {
                alert('AXIS.' + methName + ' < Method exists.  No changes made. Try creating a namespace.');
                return false;
              }
            this[methName] = aFunc;  
          }
        return true;
      };
  };


/**
 * Create AXIS object, and initialize.
 *
 * @see AXIS#createNamespace
 */
var AXIS  = new $AXIS;
/**
 * Now we re-use $AXIS var, as a namespace container.
 * @see AXIS#createNamespace
 */
$AXIS = {};

AXIS.initialize();

