// Interface to the remote JSON cloud that is O3
O3 = {
  
  User: {
    all: {
      'Name': '',
      'Username': '',
      'Password': '',
      'Email': '',
      'Active': true,
      'Admin': true
    }
  },

  load: function(id, f) {
    this.send2('GET', url(id), {}, f);
  },
  
  loads: function(id, f) {
    this.send2('GET', '/collections' + url(id), {}, f);
  },

  create: function(o, f) {
    this.send2('POST', '/', o, f);
  },
  
  'delete': function(o, f) {
    this.send2('POST', '/' + (o._id || o) + '.json?_method=DELETE', o, f);
  },
  
  updates: function(list, f) {
    this.send2('PUT', '/?_method=PUT', list, f);
  },
  
  sendEnquiry: function(o, f) {
    this.send2('POST', '/sendEnquiry', o, f);
  },

  send2: function(m, url, o, f) {    
    xhr(m, url, JSON.stringify(o), {
      'Accept': 'application/json',
      'Content-type': 'application/json',
      'If-Modified-Since': 'Thu, 1 Jan 1970 00:00:00 GMT' // Stop IE7 caching
    }, function(text) {
      var o;
      
      try {
        o = JSON.parse(text);
      } catch (e) {
        o = {
          error: {
            code:   'unknown',
            reason: (e.message || e).toString()
          }
        };
      }
      if (f)
        f(o);
    })
  },
  
  href: function(o) {
    if (o['Attachment file'])
      return O3.asset(o['Attachment file']);
    if (o['Email address'])
      return 'mailto:' + o['Email address'];
    if (o.URL)
      return url(o.URL, true);
  },
  
  file: function(path) {
    var m;
    if (m = path.match(/^http:\/\/assets\.oxdi\.eu\/([\w\d]+)\/.*\.(\w+)$/))
      return [m[1], m[2]];
  },
  
  asset: function(o) {
    if (!o)
      return;

    var id, name = '/original'; 
    
    if (o.length == 2)
      id = o;
    if (o['Image file']) {
      id = o['Image file'];
      if (o['Resizing'] !== false && o['Width'] && o['Height'])
        name = '/1.0/' + (o['Scale'] ? 'scale/' : 'crop/') + o['Width'] + 'x' + o['Height'];
    }
    if (id)
      return 'http://assets.oxdi.eu/' + id[0] + name + '.' + id[1];
  },
    
  url: function(o, html) {
    var s;
    
    if (o.URL)
      s = o.URL.replace(/(\s|\-)+/g,'+').replace(/^\s+|[^\+\-\_\s\w]|\s+$/g,'');
    else
      s = (o._id || o).toString();
    
    //var s = (o.URL || o._id || o).toString().replace(/^\s+|[^\+\-\_\s\w]|\s+$/g,'');

  	if (s.match(/^\w+-\d+$/))
  		s = s.replace(/-\d+/,'');
    if (s != '#' && s.indexOf('http://') != 0 && s.indexOf('mailto:') != 0) {
      if (s.charAt(0) != '/')
        s = '/' + s;
      //if (!/\.(html|json)$/.test(s))
      if (!/\.(html|json)/.test(s))
        s += html ? '.html' : '.json';
    }
    if (typeof html == 'object') {
      var pairs = [];
      for (var id in html)
        pairs.push(id + (html[id] ? '=' + html[id] : ''));
      s += '?' + pairs.join('&');
    }
    return s;
  }
};


JSON.xmlify = function(o) {
  return this.stringify(o).replace(/"/g, '&quot;').replace(/&\s/g, '&amp;')
}

base({

  onExec: function(ev) {
    return this.exec.apply(this, arguments);
  },

  bind: function(o) {
    var com = this, id = o.id || (o._id + '-' + o._rev);
    this.apply(this.id = id).parts(function(id, title) {
      if (id == com.id)
        this.update(o[title] || title);
    });
    return this;
  },
  
  parts: function(callback) {
    var com = this, stack = [], ctx;
    do {
      if (ctx && !ctx.contains(com)) {
        stack.pop();
        ctx = stack[stack.length - 1];
      }
      if (com.id)
        stack.push(ctx = com);
      
      if (ctx && com.node.title && com.update && com.inspect) {
        callback.call(com, ctx.id, com.node.title);
      }
    } while ((com = com._next) && this.contains(com));
  },

  ext: function(o) {
    copy(this, o);
    return this;
  },
  
  build: function(callback, ord) {
    var com;
    callback.apply(com = (this.find('contents') || this));
    if (ord)
      com.ord();
    return this;
  },
    
  readLink: function() {
    var id, v = this.read('href'), o = {};
    if (v) {
      if (id = O3.file(v))
        o['Attachment file'] = id;
      else if (v.indexOf('mailto:') == 0)
        o['Email address'] = v.slice(7);
      else
        o['URL'] = v;
    }
    return o;
  },
  
  setLink: function(o) {
    return this.set('href', O3.href(o));
  },
  
  linkType: function() {
    var o = this.readLink();
    if (o['Attachment file'])
      return 'file attachment';
    if (o['Email address'])
      return 'email message';
    if (o['URL'])
      return 'URL';
    return 'nothing';
  },
  
  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) {
    return this.controls(function(name) {
      this.update(o[name] || '');
    });
  },
  
  controls: function(callback) {
    var map = {}, v;
    this.each('control', function() {
      var field, name = this.node.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;
    com.node.style.display = 'none';
    return com;
  },
  
  show: function(v) {
    var com = v ? this.find(v) : this;
    com.node.style.display = '';
    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() {
      if (finalize)
        finalize.call(this);

      s.display = s.opacity = s.filter = '';
    });
  },

  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.node.value;
  }
});

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


editor = function(callback) {
  if (typeof window != 'undefined') {
    var o = window.parent || window;
    if (o.shell) {
      if (callback)
        callback.apply(o);
      return true;
    }
  }
  return false;
}

dialog = function() {
  var com, vs = ['Show dialog'].concat($A(arguments));
  editor(function() {
    this.shell.exec.apply(this.shell, vs);
    com = this.shell.open;
  });
  return com;
  // if (editor())
  //   return shell.dialog.apply(shell, arguments);
}

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

// A component-like wrapper for a plain data object served up from O3/couch.
O = Class({
  
	initialize: function(o) {
	  this.o = o;
	},
	
	remove: function() {
	  var o = this.o;
	  //for (var i = 0; i < arguments.length)
	  return this;
	},
	
  // set: function(name, v);
  // unset: function();
  // update: function(mods);
  
	save: function(f) {
	  O3.updates([this.o], f);
	  return this;
	}
});




function UUID(len) {
  var uuid = [], me = arguments.callee, c = me.chars;
  if (!c) c = me.chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); 

  if (len) {
    // Compact uuid form
    for (var i = 0; i < len; i++) uuid[i] = c[0 | Math.random()*62];
  } else {
    var ri=0, r;

    uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
    uuid[14] = '4';

    for (var i = 0; i < 36; i++) {
      if (uuid[i]) continue;
      r = 0 | Math.random()*16;
      // i==19: set the high bits of clock sequence as per rfc4122, sec. 4.1.5
      uuid[i] = c[(i == 19) ? (r & 0x3) | 0x8 : r & 0xf];
    }
  }
  return uuid.join('');
}






// [dep]

cp (O3, {
  
  send: function(m, o, f) {
    m = m.toUpperCase();
    
    var url = '/';
    
    switch (m) {
      case 'POST': break;
      case 'GET': {
        url = this.url(o) ;
        break;
      }
      case 'DELETE': {
        url = this.url(o);
      }
      case 'PUT':
      default: {
        url += '?_method=' + m;
        m    = 'POST';
      }
    }
    
    xhr(m, url, JSON.stringify(o), {
      'Accept':       'application/json',
      'Content-type': 'application/json'
    }, function(text) {
      var o;
      
      try {
        o = JSON.parse(text);
      } catch (e) {
        o = {
          error: {
            code:   'unknown',
            reason: (e.message || e).toString()
          }
        };
      }
      if (f)
        f(o);
    })
  },
  
  create: function(m, callback) {
    if (typeof inner != 'undefined')
      if (inner.o.path)
        m.path = m.path || inner.o.path.concat(inner.o._id);

    req('POST', '/', m, callback || function(r) {
      if (r.ok)
        inner.location = url(r.ok, true);
    });
  },
  
  update: function(o, f) {
    req('POST', '/?_method=PUT', o, f);
  },

  destroy: function(o, callback) {
    req('POST', (o._id || o) + '?_method=DELETE', {}, callback);
  }
})

fetch = function(id, callback) {
  req('GET', url(id), {}, callback);
}

update = function(data, callback) {
  req('POST', '/?_method=PUT', data, callback);
}

list = function(id, callback) {
  fetch('collections/' + id, function(response) {
    var list;
    //if (list = response.ok)
    if (list = (!response.ok && !response.error ? response : response.ok))
      for (var i in list)
        callback(list[i]);
  });
}

url = function(o, html) {
  return O3.url(o, html)
};
// [/dep]

// ...................................................

req = function(method, url, data, callback) {
  var transport, data;
  
  try {
    transport = new ActiveXObject('Msxml2.XMLHTTP')
  } catch(error) {
    try {
      transport = new ActiveXObject('Microsoft.XMLHTTP')
    } catch(error) {
      transport = new XMLHttpRequest()
    }
  }
  
  transport.open(method, url, true);
  
  for (var name in req.Headers)
    transport.setRequestHeader(name, req.Headers[name]);
  
  transport.onreadystatechange = function() {
    if (transport.readyState == 4) {
      if (callback)
        callback(JSON.parse(transport.responseText));

      transport.onreadystatechange = function() {};
    }
  }

  transport.send(typeof data == 'string' ? data : JSON.stringify(data));
};

req.Headers = {
  'Content-type':      'application/json',
  'Accept':            'application/json'
  //'If-Modified-Since': 'Thu, 1 Jan 1970 00:00:00 GMT' // Stop IE7 caching
};
