
// Array.prototype.insert = function(i, v) {
//   this.splice(i, 0, v);
// };

// Array.prototype.remove = function(i) {
//   return this.splice(i, 1)[0];
// };


// Use this namespace to avoid polluting the global namespace.
app = {};

//
// Wrap these implementations in case go with other libraries.
//
if (window.jQuery) {
  JQ = jQuery;
  app.get_json = function(url, data, callback) {
    JQ.post(url, data, callback, 'json');
  };
  app.json = function(s) {
    return JQ.parseJSON(s);
  };
  app.ajax              = JQ.get;
}
if (window.MochiKit) {
  MK = MochiKit;
  app.partial           = MK.Base.partial;
  app.bind              = MK.Base.bind;
  app.node              = MK.DOM.getElement;
  app.elem              = MK.DOM.getElement;
  app.obj               = MK.DOM.getElement;
  app.onload            = MK.DOM.addLoadEvent;
  app.strip             = MK.Format.strip;
  app.each              = MK.Iter.forEach;
  app.schedule          = MK.Async.callLater;
  app.noop              = MK.Base.noop;
}


// Treat empty arrays, zeros, and empty strings as false.
app.f = function(obj) {
  return obj == '' || obj == 0 || obj == null || obj == undefined ||
  (typeof(obj) == 'object' && MK.Base.keys(obj).length == 0) ||
  (obj.jquery && JQ(obj).length == 0);
};

app.t = function(obj) {
  return !app.f(obj);
};


app.time = function() {
  return new Date().getTime();
};


app.translate = function(s) {
  return s;
};


app.css = function(node, defs) {
  if (!node.jquery) {
    node = JQ(app.node(node));
  }
  for (name in defs) {
    node.css(name, defs[name]);
  }
};


app.has_class = function(node, cls) {
  if (!node.jquery) {
    node = JQ(app.node(node));
  }
  return node.hasClass(cls);
};

app.add_class = function(node, cls) {
  if (!node.jquery) {
    node = JQ(app.node(node));
  }
  return node.addClass(cls);
};

app.remove_class = function(node, cls) {
  if (!node.jquery) {
    node = JQ(app.node(node));
  }
  return node.removeClass(cls);
};

app.toggle_class = function(node, cls) {
  if (!node.jquery) {
    node = JQ(app.node(node));
  }
  return node.toggleClass(cls);
};


app.attr = function(node, attr, new_val) {
  if (!node.jquery) {
    node = JQ(app.node(node));
  }
  return node.attr(attr, new_val);
};


app.position = function(node) {
  if (!node.jquery) {
    node = JQ(app.node(node));
  }
  var pt = node.position();
  pt.y = pt.top;
  pt.x = pt.left;
  return pt;
};


app.move = function(node, dy, dx) {
  if (dy == null) {
    dy = 0;
  }
  if (dx == null) {
    dx = 0;
  }
  MK.Visual.Move(node, {y: dy, x: dx, duration: 0.25});
};

app.move_to = function(node, top, left) {
  if (!node.jquery) {
    node = JQ(app.node(node));
  }
  var cur = node.position();
  // TODO: review if this is how position really works.
  var pos = node.css('position')
  if (top == null) {
    top = pos == 'absolute' ? cur.top : 0;
  }
  if (left == null) {
    left = pos == 'absolute' ? cur.left : 0;
  }
  node.stop().animate({top: top, left: left}, 250);
};


app.trap_enter = function(e, fn) {
  var key;
  try {
    key = e.keyCode;
  }
  catch(ex) {
    key = window.event.keyCode;
  }
  if (key == 13) {
    fn();
  }
  return key != 13;
};


app.load_js = function(path) {
  var script = document.createElement('script');
  script.type = 'text/javascript';
  script.src = path;
  JQ('head').append(script);
};

app.load_css = function(path) {
  var script = document.createElement('link');
  script.type = 'text/css';
  script.src = path;
  script.rel = 'stylesheet';
  JQ('head').append(script);
};


app.tooltip = function(node, opts) {
  var defaults = {
    ttIdPrefix  : 'tt-',
    ttClass     : 'tooltip',
    activeClass : 'tooltip-active',
    vAlign      : 'below',
    timeOut     : 500,
    delay       : 0,
    showEvent   : 'click'
  };
  if (!node.jquery) {
    node = JQ(app.node(node));
  }
  JQ(node).tt(merge(defaults, opts));
};


app.edit_field = function(event) {
  var node = JQ(event.target);
  if (app.t(app.attr(node, 'field'))) {
    node.replaceWith('<input type="text" value="' + node.html() + '" />');
  }
};


app.tpl = function(tpl, inplace) {
  if (app.f(inplace)) {
    return TrimPath.parseDOMTemplate(tpl);
  }
  return TrimPath.parseTemplate(tpl);
//   if (window.TrimPath != undefined) {
//     return parse(tpl, inplace);
//   }
//   else {
//     // This is asynchronous. Give it a chance to load.
//     // TODO: This is broken in Webkit
//     if (!app.tpl.loading) {
//       app.load_js('/js/trimpath.template.js');
//     }
//     app.tpl.loading = true;
//     app.schedule(10, parse, tpl, inplace);
//   }
};


// Set given node's contents to results of tpl.process(data).
app.draw_jst = function(node, tpl, data) {
  if (!node.jquery) {
    node = JQ(app.node(node));
  }
  node.html(TrimPath.processDOMTemplate(tpl, data));
  // TODO: Have to add things to the DOM first before some JQUI stuff will work.
  // node.html(app.beautify(TrimPath.processDOMTemplate(tpl, data)));
};


app.first_defined = function(items) {
  for (i in items) {
    if (items[i] != undefined) {
      return items[i];
    }
  }
  return undefined;
};

app.visible = function(node) {
  if (!node.jquery) {
    node = JQ(app.node(node));
  }
  return node.is(':visible');
};


app.toggle = function(node, fx) {
  if (!node.jquery) {
    node = JQ(app.node(node));
  }
  switch (fx) {
  case 'slide':
    return node.toggle('slide', {}, 'fast');
    break;
  case 'blind':
    return node.slideToggle('fast');
    break;
  case 'scale':
    return node.toggle('scale', {}, 'fast');
    break;
  default:
    return node.toggle();
  }
};

app.show = function(node, show) {
  if (!node) {
    console.error('Element does not exist: ' + node);
    return;
  }
  if (window.JQ) {
    if (!node.jquery) {
      node = JQ(app.node(node));
    }
    if (show == undefined || show) {
      node.show();
    }
    else {
      node.hide();
    }
  }
  else {
    if (show == undefined || show) {
      MK.Visual.appear(node);
    }
    else {
      MK.Visual.fade(node);
    }
  }
};

app.hide = function(node) {
  if (window.JQ) {
    if (!node.jquery) {
      node = JQ(app.node(node));
    }
    node.hide();
  }
  else {
    MK.Visual.fade(node);
  }
};


app.remove = function(node) {
  if (!node.jquery) {
    node = JQ(app.node(node));
  }
  node.trigger('remove');
  node.remove();
};


app.submit = function(form, url) {
  if (typeof(form) == 'string') {
    if (document[form]) {
      form = document[form];
    }
    else {
      form = app.node(form);
    }
  }
  form.action = url;
  form.submit();
};

app.html = function(node, s, opts) {
  if (!node.jquery) {
    node = JQ(app.node(node));
  }
  if (opts && opts.append) {
    return node.append(s);
  }
  return node.html(s);
};

app.val = function(node, new_val) {
  if (!node.jquery) {
    node = JQ(app.node(node));
  }
  if (new_val != undefined) {
    node.val(new_val);
  }
  return node.val();
};

app.formval = function(form, field) {
  if (typeof(form) == 'string') {
    form = app.first_defined([document[form], app.node(form)]);
  }
  var ctrl = form[field];
  if (!ctrl) {
    return null;
  }
  return app.ctrlval(ctrl);
};

app.ctrlval = function(ctrl) {
  if (typeof(ctrl) == 'string') {
    ctrl = app.node(ctrl);
  }
  switch (ctrl.type) {
  case "select-one":
    return ctrl.selectedIndex == -1 ? null : ctrl.options[ctrl.selectedIndex].value;
  case "select-multiple":
    vals = new Array();
    for (opt in ctrl.options) {
      if (opt.selected) {
        vals = vals.concat(new Array(opt.value));
      }
    }
    return vals;
  case "checkbox":
  case "radio":
    return ctrl.checked ? ctrl.value : null;
  case "text":
  case "textarea":
  case "password":
  case "submit":
  case "button":
  case "hidden":
    return ctrl.value;
  default:
    // Assume it's an Array
    var vals = new Array();
    for (i=0; i < ctrl.length; i++) {
      var x = app.ctrlval(ctrl[i]);
      if (x != null && !ctrl[i].disabled) {
        vals.push(x);
      }
    }
    if (ctrl[0].type == 'radio' || vals.length == 1) {
      vals = vals[0];
    }
  return vals;
  }
};

// Addressing lameness in Select interface.
// TODO: Probably should wean off Select's in favor of plain old Div's
// if we need to manipulate them with js.

app.option_by = function(list, key, val) {
  // Return first Option that matches given key ('selected', 'value', 'text').
  if (typeof(list) == 'string') {
    list = app.obj(list);
  }
  if (key == 'selected') {
    return list.options[list.selectedIndex];
  }
  else {
    return MK.Iter.ifilter(function (o) { return o[key] == val }, list.options).next();
  }
};

app.option_add = function(list, option, idx) {
  // Add given Option to given Select at given point.  If no idx
  // supplied, then append to end of option list.
  if (idx == null) {
    idx = list.length;
  }
  try {
    list.options.add(option, list.options[idx]);
  }
  catch (e) {
    try {
      list.add(option, idx)
        }
    catch (e) {
      // For Safari.  Always appends.  Should use DOM function instead?
      list.options.add(option, idx);
    }
  }
};

app.option_remove = function(list, idx) {
  list.remove(idx);
};


app.set_all = function(ctrls, checked) {
  if (ctrls.length) {
    for (i=0; i < ctrls.length; i++) {
      if (!ctrls[i].disabled) {
        ctrls[i].checked = checked;
      }
    }
  }
  else {
    if (!ctrls.disabled) {
      ctrls.checked = checked;
    }
  }
};

app.set_ranges = function(ctrls) {
  if (!ctrls.length) {
    return;
  }
  var in_range = false;
  for (i=0; i < ctrls.length; i++) {
    if (ctrls[i].checked) {
      in_range = !in_range;
      if (!ctrls[i].disabled) {
        ctrls[i].checked = true;
      }
    }
    else {
      if (!ctrls[i].disabled) {
        ctrls[i].checked = in_range;
      }
    }
  }
};

app.encode = function(s) { 
  return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); 
}; 

app.wordwrap = function(s) { 
  return s.replace(/(?:<[^>]+>|&amp;|&gt;|&lt;)|([a-z.][^<]{10})/ig,'$&<wbr/>'); 
};

app.to_url = function(params) {
  // Return given name/val pairs as a URL query string.
  var url = '';
  for (name in params) {
    if (url) {
      url += '&';
    }
    url += name + '=' + params[name];
  }
  return encodeURI(url);
};


app.query = function() {
  var data = {};
  var query = window.location.search.substring(1);
  var fill = function(f) {
    var field = f.split('=');
    data[field[0]] = field[1];
  }
  app.each(query.split('&'), fill)
  return data;
}

app.width = function(node) {
  if (!node) {
    node = JQ(window);
  }
  else if (!node.jquery) {
    node = JQ(app.node(node));
  }
  return node.width();
};

app.height = function(node) {
  if (!node) {
    node = JQ(window);
  }
  else if (!node.jquery) {
    node = JQ(app.node(node));
  }
  return node.height();
};


app.scroll_height = function(node) {
  if (!node) {
    node = JQ(window);
  }
  else if (!node.jquery) {
    node = JQ(app.node(node));
  }
  return node.scrollTop();
};


app.window_height = function() {
  return JQ(window).height();
  var height; 
  if (self.innerHeight) { 
    height = self.innerHeight; 
  } 
  else if (document.documentElement && document.documentElement.clientHeight) { 
    height = document.documentElement.clientHeight; 
  } 
  else if (document.body) { 
    height = document.body.clientHeight; 
  } 
  return parseInt(height); 
};


app.hover = function(node, in_func, out_func) {
  if (!node.jquery) {
    node = JQ(app.node(node));
  }
  if (!in_func) {
    in_func = app.hilite;
  }
  if (!out_func) {
    out_func = app.unhilite;
  }
  node.hover(function() { in_func(this); },
             function() { out_func(this); });  
};

app.hilite = function(node, klass) {
  if (!node.jquery) {
    node = JQ(app.node(node));
  }
  node.addClass(klass || 'hilite');
};

app.unhilite = function(node, klass) {
  if (!node.jquery) {
    node = JQ(app.node(node));
  }
  node.removeClass(klass || 'hilite');
};


app.dialog = function(body, options) {
  return new Dialog(body, options);
};


app.FormSubmission = function(form, opts) {

  this.submit = function() {
    var data = {};
    var fill_data = function(i) {
      if (i.name && !i.disabled) {
        // This i.form[i.name] seems weird but if did app.ctrlval(i),
        // it would not treat radio btns as one field.
        data[i.name] = app.ctrlval(i.form[i.name]);
      }
    };
    app.each(this.form.elements, fill_data);

    var callback = function(data) {
      if (this.msg) {
        app.remove(this.msg);
      }
      this.process_errors(data.errors);
      if (this.process_results) {
        this.process_results(data);
      }
    };
    app.get_json(this.url, data, app.bind(callback, this));
  };

  // TODO: Handle file uploads separately.
  // this.upload = this.success;

  this.process_errors = function(errors) {
    JQ(this.form).find('.ui-icon.error').remove();
    JQ(this.form).find('.ui-state-error').attr('title', '').removeClass('ui-state-error');
    if (!errors) {
      return;
    }
    for (field in errors) {
      var ctrl = this.form[field];
      var label = app.node(field + '-label');
      if (label) {
        label = JQ(label);
      }
      else {
        label = JQ(ctrl).parent().find('legend, .field');
      }
      app.tooltip(label.attr('title', errors[field].join('<br />')).addClass('ui-state-error'));
      label.prepend('<span class="error ui-icon ui-icon-alert" style="float: right">ERROR</span>');
    }
    this.msg = app.msg('There were problems with the input.  Click the <span class="ui-state-error">field name</span> for details.');
  };

  // Constructor code
  this.form = form;
  if (!opts) {
    opts = {};
  }
  this.url = opts.url || form.action || (form.id + '.cgi');
  this.process_results = opts.call || opts.on_results;
  if (opts.on_errors) {
    this.process_errors = opts.on_errors;
  }
};

app.submit_form = function(form, opts) {
  form = app.node(form);
  if (!opts) {
    opts = {};
  }
  if (!app.form_submissions) {
    app.form_submissions = {};
  }
  if (!app.form_submissions[form.id]) {
    app.form_submissions[form.id] = new app.FormSubmission(form, opts);
  }
  var submitter = app.form_submissions[form.id];
  if (!submitter.jerky) {
    // To avoid submitting form multiple times when user gets trigger happy.
    submitter.jerky = true;
    var f = function(o) {
      o.jerky = false;
    };
    app.schedule(3, app.partial(f, submitter));
    submitter.msg = app.msg('<img src="/images/icon/loading.gif" style="vertical-align: middle"/>' + (opts.msg || 'Processing request...'), {sticky: true});
    submitter.submit();
  }
  return false;
};

app.msg = function(msg, opts) {
  if (app.node('gut')) {
    return gut.msg(msg, opts);
  }
};

app.wait = function(msg, id) {
  if (!app.wait.msgs) {
    app.wait.msgs = {};
  }
  if (!id) {
    id = -1;
  }
  app.wait.msgs[id] = app.msg(msg || '<img src="/images/icon/loading.gif" style="vertical-align: middle"/> Please wait while processing request.');
};

app.stop_waiting = function(id) {
  if (!id) {
    id = -1;
  }
  if (app.wait.msgs && app.wait.msgs[id]) {
    app.wait.msgs[id].remove();
  }
};

app.processing = function(node) {
  // Replace give node's contents with our progess meter.
  app.html(node, '<center><img src="/images/progress.gif" /></center>');
};


app.auto_completion = function(input_el, results_el, ds, opts) {
  var defaults = {
    allowBrowserAutocomplete  : false,
    minQueryLength            : 0,
    autoHighlight             : true,
    queryDelay                : 1.0,
    // useIFrame                 : true,
    typeAhead                 : true,
    queryMatchSubset          : true,
    maxCacheEntries           : 100
  };
  return new YAHOO.widget.AutoComplete(input_el, results_el, ds, merge(defaults, opts));
};


app.sort_table = function(node, opts) {
  if (!node.jquery) {
    node = JQ(app.node(node));
  }
  var defaults = {cssAsc: 'up', cssDesc: 'down'};
  var headers = {};
  var define_headers = function(n) {
    headers[n] = {};
    if (!app.has_class(this, 'sortable')) {
      headers[n].sorter = false;
    }
  };
  node.find('thead th').each(define_headers);
  defaults.headers = headers;
  node.tablesorter(merge(defaults, opts));
};

app.accordion = function(node, opts) {
  if (!node.jquery) {
    node = JQ(app.node(node));
  }
  opts = merge({
        toggle        : true,
        header        : 'h3',
        collapsible   : true,
        active        : false,
        autoHeight    : false}, opts);

  if (opts.toggle) {
    var toggle_btn = JQ('<input type="button" style="float: right" value="Expand All" />');
    var toggle_all = function () {
      if (this.value == 'Expand All') {
        node.find(opts.header).next().show();
        this.value = 'Collapse All';
      }
      else {
        node.find(opts.header).next().hide();
        this.value = 'Expand All';
      }
    };
    toggle_btn.prependTo(node).click(toggle_all);
  }

  app.add_class(node, 'accordion');
  var toggle = function() {
    JQ(this).next().toggle();
  };
  node.find(opts.header).click(toggle);
  node.find(opts.header).next().hide();
};


app.show_user = function(id) {
  var msg = app.msg('Loading user info...', {sticky: true});
  var f = function(data) {
    app.draw_jst('user-info-results', 'user-info-jst', data);
    app.dialog('user-info', {show:true, destroy:true});
    app.remove(msg);
  };
  app.get_json('/portal/user-info.cgi', {id: id}, f);
};


app.beautify = function(node) {
  node = JQ(app.node(node));
  var add_button = function(btn) {
    var opts = {};
    var icons = app.attr(btn, 'icons');
    var l = icons ? icons.split(' ') : [];
    if (l.length > 0) {
      opts.icons = {primary: 'ui-icon-' + l[0]};
    }
    if (l.length > 1) {
      opts.icons.secondary = 'ui-icon-' + l[1];
    }
    if (app.f(app.attr(btn, 'text'))) {
      opts.text = false;
    }
    if (app.has_class(btn, 'ui-state-disabled') || app.has_class(btn, 'ui-button-disabled')) {
      opts.disabled = true;
    }
    btn = JQ(btn).button(opts);
  };
  app.each(node.find('.btn:not(.ui-button, .ui-helper-hidden-accessible), button:not(.ui-button), input:button:not(.ui-button), input:submit:not(.ui-button)'), add_button);
  node.find('.btn-set').buttonset();
  node.find('.tabs').tabs();
  return node;
};


app.configure_jquery = function() {
  // All hover and click logic for buttons.
  var hover_in = function() {
    app.add_class(this, 'ui-state-hover');
  };
  var hover_out = function() {
    app.remove_class(this, 'ui-state-hover');
  };
  var mouse_down = function() {
    $(this).parents('.fg-buttonset-single:first').find('.fg-button.ui-state-active').removeClass('ui-state-active');
    if(  $(this).is('.ui-state-active.fg-button-toggleable, .fg-buttonset-multi .ui-state-active') ) {
      app.remove_class(this, 'ui-state-active');
    }
    else {
      app.add_class(this, 'ui-state-active');
    }
  };
  var mouse_up = function() {
    if (! $(this).is('.fg-button-toggleable, .fg-buttonset-single .fg-button,  .fg-buttonset-multi .fg-button') ) {
      app.remove_class(this, 'ui-state-active');
    }
  };
  $('.fg-button:not(.ui-state-disabled)')
  .hover(hover_in, hover_out).mousedown(mouse_down).mouseup(mouse_up);
  app.beautify('body');
};
if (window.jQuery) {
  app.onload(app.configure_jquery);
}


// Local Variables:
//   mode              : javascript
//   font-lock-mode    : t
//   tab-width         : 2
//   indent-tabs-mode  : nil
// End:
