// JavaScript application framework
// Created by Adam Bones (adam@oxdi.eu)

var App, merge, xhr, JSON;

// App:
new function() {
  
  var ATTR, ONLY, NOT, CLOSING, WRAP, EXCLUDES = map(null, false, undefined);
  
  var transformers = {

    html: function(o) {
      return {
        '1': '<!DOCTYPE HTML>', html: {
          lang: 'en', head: {
            meta: {
              'http-equiv': 'Content-Type', 'content': 'text/html; charset=utf-8'
            }, '1': o.head
          }, body: o.body
        }
      }
    },

    meta: function(o) {
      return {
        $loop: {
          id: 'meta', vs: ['Description', 'Keywords'], f: function(name) {
            return {
              name: name.toLowerCase(), content: o[name] || ''
            }
          }
        }
      }
    },

    // js: function(v) {
    //   return {
    //     tag: 'script', src: '/js/' + (v.name || v) + '.js' + (v.decache ? '?' + Math.round(Math.random() * 10000) : ''), type: 'text/javascript', charset: 'utf-8'
    //   }
    // },
    // 
    // css: function(v) {
    //   return {
    //     tag: 'link', rel: 'stylesheet', type: 'text/css', href: '/css/' + v + '.css'
    //   }
    // },
    
    js: function(v) {
      var s = v.name || v;
      if (s.indexOf('/') == -1) s = '/js/' + s;
      return {
        tag: 'script', src: s + '.js' + (v.decache ? '?' + Math.round(Math.random() * 10000) : ''), type: 'text/javascript', charset: 'utf-8'
      }
    },

    css: function(s) {
      if (s.indexOf('/') == -1) s = '/css/' + s;
      return {
        tag: 'link', rel: 'stylesheet', type: 'text/css', href: s + '.css'
      }
    },
    
    a: function(o) {
      return {
        tag: 'a', data: page.transformers.name(o), href: url(o)
      }
    },

    name: function(o) {
      if (o) {
        if (o.toDocument) o = o.toDocument();
        return o.Name || (o._type + (o.id ? ' ' + o.id : ''))
      }
    },

    cap: function(s) {
      return s.charAt(0).toUpperCase() + s.slice(1);
    },

    loop: function(o) {
      var h, v = {};
      if (h = o.vs || o.h) {
        var a, b, n = 0, keys = !o.vs;

        function add(key, value, first, last) {
          var id, name, m;
          if (id = o.id || '') {
            if (first) id += '.first';
            if (last) id += '.last';
          }
          if (keys) {
            name = value; m = key;
          } else {
            name = key; m = value;
          }
          v[id + ':' + n] = o.f ? o.f(m, n, name) : m;
          n++;
        }
        
        for (var id in h) {
          if ((!keys || h[id]) && !EXCLUDES[h[id]]) {
            if (b)
              add(b, h[b], !a);
            a = b; b = id;            
          }
        }
        if (b)
          add(b, h[b], !a, true);        
      }
      return v;
    }
  }
  
  App = klass({

    initialize: function(w) {
      this.klassed = {};
      this.loaded = {};
      this.transformers = cp({}, transformers);
      this.q = [];
      if (w) this.setWindow(w);
    },
    
    setWindow: function(w) {
      if (this.window = w) {
        if (this.div = w.document && w.document.createElement('div'))
          (this.top = new Com(w.document)).app = this;
        if (w.navigator) {
          var app = this, applyQ = function() {
            for (var i = 0; i < app.q.length; i++)
              app.q[i].apply(app);
          }
          if (/webkit/i.test(w.navigator.userAgent)) {
            setTimeout(function() {
              if (w.document.readyState == 'loaded' || w.document.readyState == 'complete' )
                applyQ();
              else
                setTimeout(arguments.callee, 10);
            }, 10); 
          } else if ((/mozilla/i.test(w.navigator.userAgent) && !/(compatible)/i.test(w.navigator.userAgent)) || (/opera/i.test(w.navigator.userAgent)))
            w.document.addEventListener('DOMContentLoaded', applyQ, false);
          else if (w.document.uniqueID && w.document.expando) { // http://www.hedgerwow.com/360/dhtml/ie-dom-ondocumentready.html
            (function () { 
              if (w.document.readyState != 'complete')
                return setTimeout(arguments.callee, 0);        
              try {
                w.document.documentElement.doScroll('left');
              } catch (e) {
                return setTimeout(arguments.callee, 100);
              }
              applyQ();
            })();
          }
        }
      }
      
    },
    
    start: function(f) {
      this.q.push(f);
    },
    
    cmd: function(h) {
      if (this.top)
        return this.top.cmd(h);
    },
    
    handle: function() {
      var app = this, f, id, o = this, node = this.window.document;
      for (var i = 0, v; i < arguments.length; i++)
        switch (typeof (v = arguments[i])) {
          case 'string':
            id = v; break;
          case 'function':
            f = v; break;
          case 'object':
            if (v)
              if (v.nodeType)
                node = v;
              else
                o = v;
        }
      var _f = node[id = 'on' + id];
      
      node[id] = function(e) {
        var v, n;
        e = e || app.window && app.window.event;
        n = e.target || e.srcElement;
        if (_f)
          if (_f.call(o, e, n) === false)
            v = false;
        if (f.call(o, e, n) === false)
          v = false;
        if (v === false)
          if (e)
            if (e.stopPropagation)
              e.stopPropagation();
            else
              e.cancelBubble = true;
        return v;
      }
    },
    
    $: function(a, b) {
      b ? this.transformers[a] = b : cp(this.transformers, a);
    },

    html: function(a, b) {
      return html(a, b, this.transformers);
    },

    build: function(a, b) {
      var s = this.html(a, b), n = 1, node = this.div;

      var name = s.match(/<(\w+)/)[1];
      while (WRAP[name]) {
        s = this.html(WRAP[name], { inner: s });
        name = WRAP[name];
        n++;
      }
      node.innerHTML = s;
      while (n-- > 0)
        node = node.firstChild;
      return node;
    },

    bind: function(v) {
      if (v)
        switch (typeof v) {
          case 'object':
            for (var id in v) this.bind(id, v[id]);
            break;
          case 'string': {
            var m = this.klassed[v] = this.klassed[v] || klass(Com.prototype);
            for (var o, i = 1; i < arguments.length; i++)
              if (o = arguments[i]) {
                o = this.klassed[o] || o;
                cp(m.prototype, o.prototype || o);
              }              
          }
        }
      return this.klassed;
    },

    load: function(v, parent, prev, next) {
      if (!v.nodeType && !v.node) 
        return this.load(this.build.apply(this, arguments))

      var app = this, last = prev || parent, inits = [], top = (function(o, parent) {
        var com, node, named;

        if (o.node) {
          com = o; node = o.node;
        } else if ((named = names(node = o)) || o == v) {
          for (var name in named)
            if (app.klassed[name]) {
              com = new app.klassed[name](node, named);
              (app.loaded[name] = app.loaded[name] || {})[com.id] = com;
              com.name = name;
              break;
            }
          if (o == v)
            com = com || new Com(node, named);
          if (com) {
            com.parent = parent;
            com.app = app;
            if (com.init)
              inits.push(com);              
          }
        }
        if (com)
          link(last, last = com);

        for (var i = 0, sub; sub = node.childNodes[i]; i++)
          arguments.callee(sub, com || parent);

        return com;
      })(v, parent);

      link(last, next);

      for (var i = 0; i < inits.length; i++)
        inits[i].init();

      return top;
    },

    com: function(node, up) {
      if (node)
        do
          for (var name in names(node))
            if (this.loaded[name])
              for (var id in this.loaded[name])
                if (this.loaded[name][id].node == node)
                  return this.loaded[name][id];
        while (up && (node = node.parentNode));
    }
  });
  
  function html(a, b, transformers) {
    var id, o, tag, classes = {}, attr = {}, inner = '', tokens, unpack;

    if (typeof a == 'string') {
      id = a; o = b;
    } else
      o = a;

    tokens = {
      '.': function(name) {
        tag = tag || 'div';
        classes[name] = true;
      },
      '#': function(v) {
        tag = tag || 'div';
        attr.id = v;
      },
      '$': function(id) {
        o = transformers[id](o);
      },
      'tag': function(name) {
        tag = name;
      }
    }

    unpack = function(o) {
      if (!EXCLUDES[o])
        switch (typeof o) {
          case 'number':
          case 'string': {
            inner += encode('' + o);
            break;
          }
          case 'object': {
            var v;
            if (o)
              for (var id in o) 
                if (!EXCLUDES[v = o[id]]) {
                  id = /^\d/.test(id) ? '' : id.replace(/:.*$/g, '');

                  if (typeof v == 'function') // code
                    v = v.toString().replace(/^function\s*\(\)\s*\{\s*/, '').replace(/\s*}$/, '');

                  switch (id) {
                    case 'data':
                      inner += encode('' + v); break;
                    case '':
                    case 'inner':
                      switch (typeof v) {
                        case 'number':
                        case 'string': {
                          inner += '' + v;
                          break;
                        }
                        case 'object':
                          unpack(v);
                      }
                      break;
                    case 'tag':
                      tag = v; break;
                    case 'flags':
                      cp(classes, v); break;
                    case 'classes':
                      for (var i = 0; i < v.length; i++)
                        classes[v[i]] = true;
                      break;
                    default:
                      switch (typeof v) {
                        case 'boolean':
                          classes[id] = v; break;
                        case 'number':
                        case 'string':
                          if (
                            (ATTR[id] && (!NOT[id] || !NOT[id][tag]) && (!ONLY[id] || ONLY[id][tag]))
                            || id.indexOf('data-') == 0) {
                            attr[id] = encode(v);
                            break;
                          }
                        default:
                          inner += html(id, v, transformers);
                      }
                  }
                }
          } // object
        } // switch
    } // unpack

    if (id) {
      var s = '', token = 'tag';
      for (var c, j = 0; j < id.length; j++)
        if (tokens[c = id.charAt(j)]) {
          if (s)
            tokens[token](s);
          s = ''; token = c;
        } else
          s += c;
      if (s && token)
        tokens[token](s);
    }
    unpack(o);

    if (tag || function() { // is there an explicit or implied tag?
      for (var x in classes) return true;
      for (var x in attr)    return true;
    }()) {
      tag = tag || 'div';

      var vs = [], s = [];
      if (!NOT['class'][tag])
        for (var name in classes)
          if (name && classes[name])
            vs.push(name);
      if (vs.length > 0)
        attr['class'] = vs.join(' ');

      s += '<' + tag;
      for (var id in attr)
        s += ' ' + id.toLowerCase() + '="' + attr[id] + '"';
      if (CLOSING[tag])
        s += ' />';
      else
        s += '>' + inner + '</' + tag + '>';
      return s;      
    } else
      return inner;
  }

  function encode(s) {
  	if (/["\\\x00-\x1f&<>"']/.test(s))
  	  s = s.replace(/([\x00-\x1f\\"&<>"'])/g, function(a, b) {
  	    switch(b){
  	      case '&':  return '&amp;';
  	      case '<':  return '&lt;';
  	      case '>':  return '&gt;';
  	      case '"':  return '&quot;';
  	      case '\'': return '&#39;';
  	    }
  	    c = b.charCodeAt();
  	    return '&#x00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16) + ";";
  	  });
  	return s;
  };

  // maps:
  new function() {
    CLOSING = map('area', 'base', 'basefont', 'br', 'hr', 'input', 'img', 'link', 'meta', 'embed');

    ATTR = map(
      'cmd', 'tip',
      // flash:
      'flashVars', 'pluginspage', 'wmode', 'scale',
      // http://www.w3.org/TR/REC-html40/index/attributes.html:
      'abbr', 'accept-charset', 'accept', 'accesskey', 'action', 'align', 'alink', 'alt', 'archive', 'axis', 'background', 'bgcolor', 'border', 'cellpadding', 'cellspacing', 'char', 'charoff', 'charset', 'checked', 'cite', 'class', 'classid', 'clear', 'code', 'codebase', 'codetype', 'color', 'cols', 'colspan', 'compact', 'content', 'coords', 'data', 'datetime', 'declare', 'defer', 'dir', 'disabled', 'enctype', 'face', 'for', 'frame', 'frameborder', 'headers', 'height', 'href', 'hreflang', 'hspace', 'http-equiv', 'id', 'ismap', 'label', 'lang', 'language', 'link', 'longdesc', 'marginheight', 'marginwidth', 'maxlength', 'media', 'method', 'multiple', 'name', 'nohref', 'noresize', 'noshade', 'nowrap', 'object', 'onblur', 'onchange', 'onclick', 'ondblclick', 'onfocus', 'onkeydown', 'onkeypress', 'onkeyup', 'onload', 'onmousedown', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onreset', 'onselect', 'onsubmit', 'onunload', 'onunload', 'profile', 'prompt', 'readonly', 'rel', 'rev', 'rows', 'rowspan', 'rules', 'scheme', 'scope', 'scrolling', 'selected', 'shape', 'size', 'span', 'src', 'standby', 'start', 'style', 'summary', 'tabindex', 'target', 'text', 'title', 'type', 'usemap', 'valign', 'value', 'valuetype', 'version', 'vlink', 'vspace', 'width');
    
    NOT = {
      style: map('base', 'basefont', 'head', 'html', 'meta', 'param', 'script', 'style', 'title'), title: map('base', 'basefont', 'head', 'html', 'meta', 'param', 'script', 'title'), 'class': map('meta', 'script', 'link')
    };
    
    ONLY = {
      code: map('applet'), data: map('object'), label: map('option', 'optgroup'), object: map('applet'), span: map('col', 'colgroup')
    };

    WRAP = {
      'td': 'tr', 'tr': 'tbody', 'tbody': 'table',
      'li': 'ul',
      'option': 'select'
    };
  }

  
  var n = 0, Com = klass({

    initialize: function(node, hh) {
      this.node = node;
      this.names = hh || names(node) || {};
      this.id = ++n;
    },
        
    ext: function(o) {
      return cp(this, o);
    },
    
    reset: function(o) {
      this.node.innerHTML = this.app.html(o);
      return this.app.load(this, this.parent, this.prev, (this.last() || this).next);
    },
    
    cmd: function(h) {
      if (this.cmds)
        cp(this.cmds, h);
      else {
        this.cmds = h;
        this.handleLinks(function(v) {
          if (v.length > 2) {
            var i;
            if ((i = v.indexOf('#')) > -1) {
              v = v.slice(i + 1).split(':');
              if (this.cmds[v[0]]) {
                this.cmds[v[0]].apply(this, v.slice(1));
                return false;
              }
            }
          }
        })
      }
      return this;
    },
    
    handleLinks: function(f, id) {
      this.handle(id || 'click', function(e, node) {
        if (!(e.ctrlKey || e.metaKey)) {
          var v;
          while (node && node.tagName != 'A')
            node = node.parentNode;
          if (node && node.href && (v = node.getAttribute('href', 2)))
            return f.call(this, v, node);
        }
      });
    },
        
    handle: function(id, a, b) {
      this.app.handle(this, id, a, b || this.node);
      return this;
    },
    
    toggle: function() {
      var vs = arguments, i = 0, name = typeof vs[0] == 'string' ?
        vs[i++] : 'on';
      this.names[name] = i < vs.length ?
        !!vs[i] : !this.names[name];
      return this.apply();
    },
    
    select: function(a, b) {
      var name = 'selected', com = a;
      if (typeof a == 'string') {
        name = a; com = b;
      }
      if (this[name])
        this[name].clear(name);
      if (this[name] = com)
        com.apply(name);
      return com;
    },
    
    clear: function(name) {
      this.names[name] = false;
      return this.apply();
    },
    
    apply: function() {
      var replace, names = {}, vs = [];
      for (var v, i = 0; i < arguments.length; i++)
        switch (typeof (v = arguments[i])) {
          case 'boolean':
            replace = v; break
          case 'string':
            names[v] = true; break;
          case 'object':
            if (v)
              if (v.constructor == Array)
                for (var j = 0; j < v.length; j++) names[v[j]] = true;
              else
                cp(names, v);
        }
      for (var id in (this.names = replace ? names : cp(this.names, names)))
        if (id && (this.names[id] = !!this.names[id]))
          if (this.node.id != id)
            vs.push(id);
      this.node.className = vs.join(' ');
      return this;
    },
    
    set: function(name, v) {
      v ? this.node.setAttribute(name, v) : this.node.removeAttribute(name);
      return this;
    },

    read: function(name) {
      return name == 'class' ?
        this.node.className : this.node.getAttribute(name, 2) || '';
    },
    
    data: function() {
      return this.node.textContent || this.node.innerText || '';
    },
    
    first: function(v) {
      return this.visit(v, function() { return this }, true);
    },

    last: function(v) {
      var com;
      this.visit(v, function() { com = this }, true);
      return com;
    },
    
    each: function(v, f) {
      this.visit(v, f, true);
      return this;
    },
    
    collect: function(m, f) {
      var vs = [];
      this.visit(m, function() { vs.push(f ? f.apply(this) : this) }, true);
      return vs;
    },

    count: function(m) {
      var n = 0;
      this.visit(m, function() { n++ }, true);
      return n;
    },
    
    // Apply f to each matching component of this tree, or until f returns something:
    visit: function(a, b, sub) {
      var v, f = b || a, name = b && a, h = {}, com = this, i = 0;
      do {
        h[com.id] = true;
        if (!sub || com != this)
          if (!name || com.names[name] || com[name] === true)
            if (typeof (v = f.call(com, i++)) != 'undefined')
              return v;
      } while ((com = com.next) && com.parent && h[com.parent.id]);
    },
    
    seek: function(a, b) {
      var com = this, id = a === true || b === true ? 'prev' : 'next', name = (typeof a == 'string' && a) || (typeof b == 'string' && b);
      if (this.parent)
        while ((com = com[id]) && this.parent.contains(com))
          if (com.parent == this.parent)
            if (!name || com.names[name] || com[name] === true)
              return com;
    },
    
    contains: function(o) {
      if (o.node) {
        while (o = o.parent)
          if (o == this) return true;
      } else {
        do
          if (o == this.node)
            return true;
        while (o = o.parentNode);
      }
      return false;
    },
    
    insert: function(com, next) {
      return com.move(this, next);
    },
    
    remove: function() {
      this.node.parentNode.removeChild(this.node);
      return this.move();
    },
    
    move: function(parent, ref) {
      var end = this.last() || this;
      link(this.prev, end.next); // detach from list
      if (this.parent = parent) {
        var prev = (ref && ref.prev) || parent.last() || parent;
        link(end, prev.next);
        link(prev, this);
        ref ?
          ref.node.parentNode.insertBefore(this.node, ref.node) :
          parent.node.appendChild(this.node);
      }
      return this;
    },
    
    start: function(f, period) {
      var com = this, id = setInterval(function() {
        if (f.apply(com) === false)
          clearInterval(id);
        
      }, period || 20);
      return this;
    },
        
    toString: function() {
      return (this.name || '') + '#' + this.id;
    }
  });
    
  function link(a, b) {
    if (a) a.next = b;
    if (b) b.prev = a;
  }
  
  function names(node) {
    if (node.className || node.id) {
      var h = {};
      if (node.id)
        h[node.id] = true;
      if (node.className)
        for (var i = 0, s, ss = node.className.split(' '); s = ss[i]; i++)
          if (s)
            h[s] = true;
      return h;
    }
  }
};

// merge
new function() {
  merge = function(base) {
    for (var o, i = 1; o = arguments[i]; i++)
      for (var id in o) {
        switch (typeof o[id]) {
          case 'object':
            if (h(o[id])) {
              if (!h(base[id]))
                base[id] = {};
              merge(base[id], o[id]);
              break;
            }
          case 'function':
          case 'number':
          case 'boolean':
          case 'string':
            base[id] = o[id];
        }
      }
    return base;
  }

  function h(v) {
    return typeof v == 'object' && v && !((typeof v.length == 'number') || v.constructor == Array || v.node || v.nodeType);
  }
}

// xhr:
new function() {
  var n = 0, h = {
    'custom-header':'true', 'Accept': 'application/json', 'Content-type': 'application/json',
    'X-Requested-With': 'XMLHttpRequest', 'If-Modified-Since': 'Thu, 1 Jan 1970 00:00:00 GMT' // Stop IE7 caching
  };
  // object means post
  // no object means get
  // url is optional in posts, default is /api/
  xhr = function() {
    var o, m, path, body = '', f;

    for (var v, i = 0; i < arguments.length; i++)
      switch (typeof (v = arguments[i])) {
        case 'string':
          m = m || 'GET'; path = v; break;
        case 'object':
          m = 'POST'; path = path || '/api/'; body = JSON.stringify(v); break;
        case 'function':
          f = v; break;
      }

    path = url(path, { n: ++n }); // beat local caches

    try {
      o = new ActiveXObject('Msxml2.XMLHTTP')
    } catch(e) {
      try {
        o = new ActiveXObject('Microsoft.XMLHTTP')
      } catch(e) {
        o = new XMLHttpRequest()
      }
    }
    o.open(m, path, true);
    for (var id in h)
      o.setRequestHeader(id, h[id]);

    o.onreadystatechange = function() {
      switch (o.readyState) {
        case 4: {
          var s;
          if (f)
            try {
              f(!(s = o.getResponseHeader('Content-Type') || '') || /json/.test(s) || /api|schemas/.test(url) ?
                JSON.parse(o.responseText) : o.responseText, o);              
            } catch(e) {
              try {
                console.log(e)
              } catch(e) {}
            }
          o.onreadystatechange = function() {};
        }
      }
    }
    o.send(body);
    return o;
  }  
}

function klass(o) {
  function f() {
    if (this.initialize)
      this.initialize.apply(this, arguments);
  }
  cp(f.prototype, o);
  return f;
}

function cp(base) {
  for (var o, i = 1; i < arguments.length; i++)
    if (o = arguments[i])
      if (typeof o == 'object')
        for (var id in o)
          base[id] = o[id];
  return base;
};

function map() {
  var h = {};
  for (var i = 0; i < arguments.length; i++) h[arguments[i]] = true;
  return h;
};

function url(o, h) {
  var s;

  // From document
  if (o.toDocument)
    o = o.toDocument();
  if (s = o._id) {
    if (o.URL)
      s = s.replace(/\/[^\/]+$/, '/' + o.URL.replace(/\s/g,'+'));
  } else
    s = o.toString();

  if (!/^(\/|#|http|mailto)/.test(s))
    s = '/' + s;

  // Add query string
  if (typeof h == 'object') {
    var vs = [];
    for (var id in h)
      if (h[id] != undefined)
        vs.push(escape(id) + '=' + escape(h[id]));
    if (vs.length > 0) {
      switch (s.indexOf('?')) {
        case -1:
          s += '?'; break;
        case (s.length - 1):
          break;
        default:
          s += '&'
      }
      s += vs.join('&');
    }
  }
  return s;
};

// http://www.JSON.org/json2.js
new function() {
if(!this.JSON){JSON={}}(function(){function f(n){return n<10?'0'+n:n}if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(key){return this.getUTCFullYear()+'-'+f(this.getUTCMonth()+1)+'-'+f(this.getUTCDate())+'T'+f(this.getUTCHours())+':'+f(this.getUTCMinutes())+':'+f(this.getUTCSeconds())+'Z'};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf()}}var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,subs={'\\b':'\\\\b','\\t':'\\\\t','\\n':'\\\\n','\\f':'\\\\f','\\r':'\\\\r','"':'\\"','\\\\':'\\\\\\\\'},rep;function quote(string){if(/["\\\x00-\x1f]/.test(string)){string=string.replace(/([\x00-\x1f\\"])/g,function(a,b){var c=subs[b];if(c)return c;c=b.charCodeAt();return'\\u00'+Math.floor(c/16).toString(16)+(c%16).toString(16)})}return'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==='object'&&typeof value.toJSON==='function'){value=value.toJSON(key)}if(typeof rep==='function'){value=rep.call(holder,key,value)}switch(typeof value){case'string':return quote(value);case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null'}gap+=indent;partial=[];if(typeof value.length==='number'&&!value.propertyIsEnumerable('length')){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||'null'}v=partial.length===0?'[]':gap?'[\n'+gap+partial.join(',\n'+gap)+'\n'+mind+']':'['+partial.join(',')+']';gap=mind;return v}if(rep&&typeof rep==='object'){length=rep.length;for(i=0;i<length;i+=1){k=rep[i];if(typeof k==='string'){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v)}}}}else{for(k in value){if(Object.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v)}}}}v=partial.length===0?'{}':gap?'{\n'+gap+partial.join(',\n'+gap)+'\n'+mind+'}':'{'+partial.join(',')+'}';gap=mind;return v}}if(typeof JSON.stringify!=='function'){JSON.stringify=function(value,replacer,space){var i;gap='';indent='';if(typeof space==='number'){for(i=0;i<space;i+=1){indent+=' '}}else if(typeof space==='string'){indent=space}rep=replacer;if(replacer&&typeof replacer!=='function'&&(typeof replacer!=='object'||typeof replacer.length!=='number')){throw new Error('JSON.stringify');}return str('',{'':value})}}if(typeof JSON.parse!=='function'){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==='object'){for(k in value){if(Object.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v}else{delete value[k]}}}}return reviver.call(holder,key,value)}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 typeof reviver==='function'?walk({'':j},''):j}throw new SyntaxError('JSON.parse');}}})();
}