// o3 dependent tools: (api, urls ...)
// requires: sweep

var url, xhr, html;

// dep:
var O3, editor, dialog, pointer;

url = function(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();

	// Add leading slash
  // if (s.indexOf('#') != 0 && s.indexOf('http://') != 0 && s.indexOf('mailto:') != 0)
  //   if (s.charAt(0) != '/')
  if (!/^(\/|#|http:|mailto:)/.test(s))
      s = '/' + s;

  // Add query string
  if (typeof h == 'object') {
    var vs = [];
    for (var id in h)
      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;
};

// 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 = '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)
            f(!(s = o.getResponseHeader('Content-Type') || '') || /json/.test(s) || /api|schemas/.test(url) ?
              JSON.parse(o.responseText) : o.responseText, o);
          o.onreadystatechange = function() {};
        }
      }
    }
    o.send(body);
    return o;
  }  
}

html = function(o) {
  o = o || {};

  return '<!DOCTYPE HTML>'
    + tag('html', {
      'lang': 'en',
      'head': {
        ':meta': o.data && {
          ':1': o.data.Title && tag('title', o.data.Title), // hack around title property being attribute
          ':2': tags('meta', { 'Description': !!o.data.Description, 'Keywords': !!o.data.Keywords }, function(name) {
            return { name: name.toLowerCase(), content: o.data[name] }
          })
        },
        'meta': {
          'http-equiv': 'Content-Type',
          'content': 'text/html; charset=utf-8'
        },
        ':js': tags('script', cp(map('components'), o.js || {}), function(name) {
          return {
            src: '/js/' + name + '.js', type: 'text/javascript', charset: 'utf-8'
          }
        }).inner,
        ':css': tags('link', cp(map('base'), o.css || {}), function(name) {
          return { rel: 'stylesheet', type: 'text/css', href: '/css/' + name + '.css' }
        }).inner,
        style: o.style && {
          type: 'text/css', media: 'screen', inner: o.style
        }
      },
      'body': {
        ':1': o.data && o.data._id && {
          classes: [o.data._id.replace('/', '-')]
        },
        ':2': o.body || '',
        ':3': o.data && o.data['Footer HTML']
      }
    });  
}

// ... dep

O3 = {
      
  url: url,
  
  load: xhr, sends: xhr, send2: xhr,
  
  cookie: function(x) {
    for (var id in x || {})
      document.cookie = id + '=' + x[id] +'; path=/';
    
    var h = {}, parts = document.cookie.split(';');
    
    for (var pair, i = 0; i < parts.length; i++)
      if (pair = parts[i].split('='))
        if (pair.length == 2) {
          var key = pair[0].replace(' ', ''), v = pair[1];
          try {
            v = JSON.parse(v);
          } catch (e) { }
          if (v != null)
            h[key] = v;
        }
    return h;
  }
};

base({

  onExec: function(ev) {
    return this.exec.apply(this, arguments);
  },
  
  build: function(callback, ord) {
    var com;
    callback.apply(com = (this.find('contents') || this));
    if (ord)
      com.ord();
    return this;
  },
      
  ord: function() {
    var first, last, com, parent = this, ons = !!this.find(function() { return this.parent == parent && this.on });

    this.each(function() {
      if (this.parent == parent) {
        this.clear('last');
        if (first)
          this.clear('first');
        else if (!ons || this.on)
          first = this.apply('first');
        if (!ons || this.on)
          last = this;
      }
    });
    if (last)
      last.apply('last');
    return this;
  },
  
  setup: function(o) {
    this.controls(function(name) {
      this.update(o[name]);
    });
    return this;
  },
  
  controls: function(callback) {
    var map = {}, v;
    this.each('control', function() {
      var field, name = this.read('name') || ((field = this.up('field')) && field.first('control') == this ? field.reads()[0].replace(':', '') : false);
      if (name) {
        var value = this.value();
        if (callback)
          if (typeof (v = callback.call(this, name, value)) != 'undefined')
            return v;
        map[name] = value;
      }
    });
    return typeof v == 'undefined' ? map : v;
  },
  
  hide: function(v) {
    var com = v ? this.find(v) : this;
    try { com.node.style.display = 'none' } catch (e) {}
    return com;
  },
  
  show: function(v) {
    var com = v ? this.find(v) : this;
    try { com.node.style.display = '' } catch (e) {}
    return com;
  },
  
  fade: function(rate, finalize) {
    return this.morphO(1, 0, rate, function() {
      if (finalize === true)
        this.remove();
      else if (finalize)
        finalize.call(this);
    });
  },
  
  appear: function(rate, finalize) {
    return this.morphO(0, 1, rate, finalize);
  },
  
  morphO: function(i, j, rate, finalize) {
    var s = this.node.style;
    
    // Trigger hasLayout in IE (fixes text rendering bug)
    if (window.ActiveXObject)
      s.width = this.node.offsetWidth + 'px';

    return this.morph(i, j, rate, function(k) {
      s.display = k == 0 ? 'none' : '';
        
      if (window.ActiveXObject)
        s.filter = 'alpha(opacity=' + (k * 100) + ')';
      else
        s.opacity = k;
    }, function() {
      s.display = s.opacity = s.filter = '';
      if (finalize)
        finalize.call(this);
    });
  },

  morph: function(i, j, rate, iterator, finalize) {
    rate = rate || 0.05;
    
    var k = i;
    
    iterator.call(this, i);
    
    return this.start(function() {
      k += (i < j ? 1 : -1) * rate;
      iterator.call(this, Math.round(-100 * (Math.cos(Math.PI * k) - 1) / 2) / 100);
      
      if ((j > i && k >= j) || (j < i && k <= j)) {
        if (finalize)
          finalize.call(this);
        return false;
      }
    }, 20);
  }
});

bind('drop', {

  onClick: function() {
    return true;
  },
  
  onExec: function(id, o) {
    if ((o.button || o.ic || {}).parent == this) // the first button outside of the list - open/closes the drop
      return !this.toggle();
    if (!this.sticky)
      this.toggle();
    o.drop = this;   
  },
  
  ons: function(match) {
    return this.build(function() {
      this.each('button', function() {
        this.toggle(this.match(match));
      });
      this.ord();
    });
  },
  
  toggle: function(v) {
    var o = Com.drop;
    if (o.open)
      if (o.open != this)
        o.open.clear('open');
    if (arguments.length == 0)
      v = !this.open;
    o.open = v ? this.apply('open') : !this.clear('open');
    return this;
  }
});
 
bind('ic', {
  
  onClick: function() {
    var id;
    if (id = this.id()) {
      var o = {};
      o[this.name] = this;
      this.fire('exec', id, o);
    }
    if (this.read('href').indexOf('#') > -1)
      return false;
  },
  
  id: function() {
    var v, i;
    if (v = this.read('href'))
      if ((i = v.indexOf('#')) > -1)
        v = v.slice(i + 1);
    return v || this.read('title') || this.read();
  }  
})

bind('button', 'ic');

bind('inner');

bind('list');

bind('control', {

  onFocus: function() {
    //this.klass.focus = this;
    if (this.def)
      this.clear('def').node.value = '';
  },
  
  // onBlur: function() {
  //   // Bitofahack: if this was caused by sending an editor update command (which will move focus to inner windowm, then back to this control), ignore it
  //   if (!this.editing)
  //     this.klass.focus = false;
  // },

  onMouseDown: function() {
    return !this.up('selector');
  },
  
  onKeyDown: function(event) {
    if (!this.multi)
      if (event.keyCode == 13)
        this.fire('enter', this);

    this._value = this.node.value;
  },
  
  onKeyUp: function() {
    if (this.node.value != this._value) {
      //this.editing = true;
      this.fire('edit', this);
      this.node.focus();
      //this.editing = false;
    }
  },

  update: function(v) {
    this.node.value = v || '';
    return this;
  },
  
  value: function() {
    return this.number ? parseInt(this.node.value) : this.node.value
  }
});

bind('switch', 'control', {
  
  update: function(v) {
    return this.toggle(this.node.checked = v);
  },
  
  value: function() {
    return this.on || this.node.checked;
  }
});

pointer = function(event) {
  return [ event.pageY || (event.clientY + (document.documentElement.scrollTop  || document.body.scrollTop)),
           event.pageX || (event.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft)) ];
};

// 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');}}})();
}