// Protoplus: extensions for the Prototype library
// Copyright (c) 2005, Michael Schuerig, michael@schuerig.de
//
// License
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// See http://www.gnu.org/copyleft/lesser.html
//
// REQUIREMENTS:
// * prototype.js - http://prototype.conio.net/ (included with Ruby on Rails)
//

var ProtoPlus = {
  Version: '0.2.1'
};


//----- General

function $$(element, msgPrefix, expectedTagName) {
  if (!msgPrefix) {
  	msgPrefix = '';
  }
  if (!element) {
    throw new Error(msgPrefix + 'Undefined argument');
  }
  var el = $(element);
  if (!el) {
    throw new Error(msgPrefix + 'Element does not exist: "' + element + '"');
  }
  if (expectedTagName) {
    if (el.tagName != expectedTagName.toUpperCase()) {
      throw new Error(msgPrefix + 'Element does not have expected tag name: "' + element + '"' + "\nexpected: " + expectedTagName);
    }
  }
  return el;
}


//----- Element

if (!window.Element) {
  var Element = new Object();
}

Object.extend(Element, {

  getTextContent: function(element) {
    element = $$(element, 'Element.getTextContent: ');
    var txt = '';
    var children = element.childNodes;
    var len = children.length;
    for (var i = 0; i < len; i++) {
      var child = children[i];
      if (child.nodeType == 3) {
        txt += child.data;
      } else {
        txt += Element.getTextContent(child);
      }
    }
    return txt;
  },

  getAbsolutePos: function(element) {
    return Element._getAbsolutePos($$(element, 'Element.getAbsolutePos: '));
  },

  // Essentially borrowed from Mihai Bazon's DHTML Calendar
  // http://www.dynarch.com/projects/calendar
  _getAbsolutePos: function(element) {
    var SL = 0, ST = 0;
    var is_div = /^div$/i.test(element.tagName);
    if (is_div && element.scrollLeft) {
      SL = element.scrollLeft;
    }
    if (is_div && element.scrollTop) {
      ST = element.scrollTop;
    }
    var r = { x: element.offsetLeft - SL, y: element.offsetTop - ST };
    if (element.offsetParent) {
      var tmp = this.getAbsolutePos(element.offsetParent);
      r.x += tmp.x;
      r.y += tmp.y;
    }
    return r;
  },

  getAncestorByTagName: function(element, tagName) {
    element = $$(element, 'Element.getAncestorByTagName: ');
    tagName = tagName.toUpperCase();
    var ancestor = element;
    while (ancestor && ancestor.tagName != tagName) {
    	ancestor = ancestor.parentNode;
    }
    return ancestor;
  },

  getAncestorByClassName: function(element, className) {
    element = $$(element, 'Element.getAncestorByClassName: ');
    var ancestor = element;
    while (ancestor && !Element.Class.has(ancestor, className)) {
      ancestor = ancestor.parentNode;
    }
    return ancestor;
  },

  // TODO extend to allow parent specification by id/element
  getPositionInAncestorByTagName: function(element, tagName) {
    element = $$(element, 'Element.getAncestorByTagName: ');
    tagName = tagName.toUpperCase();
    var ancestor = Element.getAncestorByTagName(element, tagName);
    if (!ancestor) {
    	return NaN;
    }
    var grandAncestor = ancestor.parentNode;
    if (!grandAncestor) {
    	return NaN;
    }
    var len = grandAncestor.childNodes.length;
    for (var i = 0, pos = 0; i < len; i++) {
      var node = grandAncestor.childNodes[i];
      if (node.tagName == tagName) {
        if (node === ancestor) {
          return pos;
        }
        pos++;
      }
    }
    return NaN;
  },

  switchClassName: function(element, className, present) {
    element = $$(element, 'Element.switchClassName: ');
    var hasClassName = Element.Class.has(element, className);
    var shouldHaveClassName = (present != undefined) ? present : !hasClassName;
    if (hasClassName && !shouldHaveClassName) {
      Element.Class.remove(element, className);
    } else if (!hasClassName && shouldHaveClassName) {
      Element.Class.add(element, className);
    }
  },

  findFirstMatchingChild: function(parent, matchingFunc) {
    parent = $$(parent, 'Element.findFirstMatchingChild: ');
    if (!matchingFunc) {
      throw new Error('Element.findFirstMatchingChild: matchingFunc is undefined');
    }
    var children = parent.childNodes;
    var len = children.length;
    for (var i = 0; i < len; i++) {
      var child = children[i];
      if (matchingFunc(child, i)) {
        return child;
      }
    }
    return undefined;
  },

  findMatchingChildren: function(parent, matchingFunc) {
    parent = $$(parent, 'Element.findMatchingChildren: ');
    if (!matchingFunc) {
      throw new Error('Element.findMatchingChildren: matchingFunc is undefined');
    }
    var children = parent.childNodes;
    var matches = [];
    var len = children.length;
    for (var i = 0; i < len; i++) {
      var child = children[i];
      if (matchingFunc(child, i)) {
        matches.push(child);
      }
    }
    return matches;
  },

  reparentChildren: function(fromParent, toParent, matchingFunc) {
    fromParent = $$(fromParent, 'Element.reparentChildren: ');
    toParent = $$(toParent, 'Element.reparentChildren: ');
    var idx = 0;
    while (fromParent.childNodes.length > idx) {
      var child = fromParent.childNodes[idx];
      if (!matchingFunc || matchingFunc(child)) {
        toParent.appendChild(child);
      } else {
        idx++;
      }
    }
  }
});


//----- Form

if (!window.Form) {
  var Form = new Object();
}

Form.blockMultipleSubmits = function(form) {
  form = $$(form, 'Form.blockMultipleSubmits: ');
  form.__hasBeenSubmitted = false;
  Event.observe(form, 'submit', Form.multipleSubmitBlocker);
};

Form.blockMultipleSubmitsForAllForms = function() {
  var forms = document.forms;
  for (var i = 0; i < forms.length; i++) {
    Form.blockMultipleSubmits(forms[i]);
  }
};

Form.resetSubmitBlock = function(form) {
  $(form).__hasBeenSubmitted = false;
};

Form.resetSubmitBlockForAllForms = function() {
  var forms = document.forms;
  for (var i = 0; i < forms.length; i++) {
    Form.resetSubmitBlock(forms[i]);
  }
};

Form.multipleSubmitBlocker = function(event) {
  var form = Event.element(event);
  if (form.__hasBeenSubmitted) {
    Event.stop(event);
  }
  var submitButtons = Form.Element.findElements(form, 'submit');
  for (var i = 0; i < submitButtons.length; i++) {
    submitButtons[i].disabled = true;
  }
  form.__hasBeenSubmitted = true;
};


//----- Form.Element

Form.Element.findElements = function(form, typeName, name) {
  form = $(form);
  return [].concat(
    Form.Element.selectByTypeAndName(form.getElementsByTagName('input'), typeName, name),
    Form.Element.selectByTypeAndName(form.getElementsByTagName('select'), typeName, name),
    Form.Element.selectByTypeAndName(form.getElementsByTagName('button'), typeName, name)
  );
}


Form.Element.selectByTypeAndName = function(elements, typeName, name) {
  if (!typeName && !name) {
    return elements;
  }
  var matches = [];
  for (var i = 0; i < elements.length; i++) {
    var element = elements[i];
    if (typeName && element.type != typeName) {
      continue;
    }
    if (name && element.name != name) {
      continue;
    }
    matches.push(element);
  }
  return matches;
};


Form.Element.setValue = function(element, value) {
  var element = $(element);
  var method = element.tagName.toLowerCase();
  var parameter = Form.Element.Deserializers[method](element, value);
};

Form.Element.Deserializers = {
  input: function(element, value) {
    switch (element.type.toLowerCase()) {
      case 'submit':
      case 'hidden':
      case 'password':
      case 'text':
        Form.Element.Deserializers.textarea(element, value);
      case 'checkbox':
      case 'radio':
        Form.Element.Deserializers.inputSelector(element, value);
    }
  },

  inputSelector: function(element, value) {
    element.checked = value;
  },

  textarea: function(element, value) {
    element.value = value;
  },

  select: function(element, values) {
    if (!values) {
      values = [];
    }
    if (! (values instanceof Array)) {
      values = [ values ];
    }
    for (var i = 0; i < element.length; i++) {
      var opt = element.options[i];
      opt.selected = values.contains(opt.value);
    }
  }
}


var $S = Form.Element.setValue;


//----- Form.Label

Form.Label = {

  getFor: function(element) {
    element = $$(element, 'Form.Label.getFor: ');
    var id = element.id;
    var form = element.form;
    if (!form) {
      return;
    }
    var labels = form.getElementsByTagName('label');
    var len = labels.length;
    for (var i = 0; i < len; i++) {
      var label = labels[i];
      if (label.htmlFor == id) {
      	return label;
      }
    }
    return undefined;
  },

  getTextFor: function(element) {
    var label = Form.Label.getFor(element);
    if (label) {
      return Element.getTextContent(label);
    }
    return '';
  }
};


//----- Form.Select

Form.Select = {

  // for use in 'click' event listeners on select-one elements
  clickedOption: function(event) {
    var target = Event.element(event);
    if (target.tagName == 'OPTION') {
        return target;
    } else if (target.tagName == 'SELECT') {
      var idx = target.selectedIndex;
      if (idx > -1) {
        return target.options[idx];
      }
      return undefined;
    }
  },

  allValues: function(selectElement) {
    selectElement = $$(selectElement, 'Form.Select.allValues: ', 'select');
    var values = [];
    var options = selectElement.options;
    var len = options.length
    for (var i = 0; i < len; i++) {
      values.pushUnlessNull(options[i].value);
    }
    return values;
  },

  allSelectedValues: function(selectElement) {
    selectElement = $$(selectElement, 'Form.Select.allSelectedValues: ', 'select');
    var values = [];
    var options = selectElement.options;
    var len = options.length
    for (var i = 0; i < len; i++) {
      var option = options[i];
      if (option.selected) {
        values.pushUnlessNull(option.value);
      }
    }
    return values;
  },

  allSelectedOptions: function(selectElement) {
    selectElement = $$(selectElement, 'Form.Select.allSelectedOptions: ', 'select');
    var values = [];
    var options = selectElement.options;
    var len = options.length
    for (var i = 0; i < len; i++) {
      var option = options[i];
      if (option.selected) {
        values.pushUnlessNull(option);
      }
    }
    return values;
  },

  selectAll: function(selectElement) {
    Form.Select.setSelectedForAll(selectElement, true);
  },

  deselectAll: function(selectElement) {
    Form.Select.setSelectedForAll(selectElement, false);
  },

  setSelectedForAll: function(selectElement, selected) {
    selectElement = $$(selectElement, 'Form.Select.setSelectedForAll: ', 'select');
    var options = selectElement.options;
    var len = options.length
    for (var i = 0; i < len; i++) {
      options[i].selected = selected;
    }
  },

  selectAllOnSubmit: function(selectElement) {
    var __selectElement = $$(selectElement, 'Form.Select.selectAllOnSubmit: ', 'select');
    Event.observe(__selectElement.form, 'submit',
      function() { Form.Select.selectAll(__selectElement); return true; });
  },

  optionsAsArray: function(selectElement) {
    selectElement = $$(selectElement, 'Form.Select.optionsAsArray: ', 'select');
    var options = selectElement.options;
    var len = options.length
    var arr = [];
    for (var i = 0; i < len; i++) {
      arr.push(options[i]);
    }
    return arr;
  },

  sort: function(selectElement, compareFunc) {
    function compareText(opt1, opt2) {
      return String.compare(opt1.text, opt2.text);
    }
    selectElement = $$(selectElement, 'Form.Select.sort: ', 'select');
    Form.Select.appendOptions(selectElement, // don't set selectElement.options.length = 0, it confuses IE
      Form.Select.optionsAsArray(selectElement).sort(
        compareFunc || compareText));
  },

  moveSelectedOptions: function(fromSelect, toSelect) {
    fromSelect = $$(fromSelect, 'Form.Select.moveSelectedOptions, fromSelect: ', 'select');
    toSelect = $$(toSelect, 'Form.Select.moveSelectedOptions, toSelect: ', 'select');
    Form.Select.deselectAll(toSelect);
    Form.Select.appendOptions(toSelect, Form.Select.allSelectedOptions(fromSelect));
    Form.Select.sort(fromSelect);
    Form.Select.sort(toSelect);
  },

  removeOptions: function(selectElement, minusSelect) {
    function valueEquality(opt1, opt2) {
      return (opt1.value == opt2.value);
    }
    selectElement = $$(selectElement, 'Form.Select.removeOptions, selectElement: ', 'select');
    minusSelect = $$(minusSelect, 'Form.Select.removeOptions, minusSelect: ', 'select');
    var newOpts = Form.Select.optionsAsArray(selectElement).difference(
      Form.Select.optionsAsArray(minusSelect), valueEquality);
    Form.Select.setOptions(selectElement, newOpts);
  },

  setOptions: function(selectElement, options) {
    selectElement = $$(selectElement, 'Form.Select.setOptions: ', 'select');
    selectElement.length = 0;
    Form.Select.appendOptions(selectElement, options);
  },

  appendOptions: function(selectElement, options) {
    selectElement = $$(selectElement, 'Form.Select.appendOptions: ', 'select');
    var len = options.length;
    for (var i = 0; i < len; i++) {
      selectElement.appendChild(options[i]);
    }
  }
};


//----- Protoplus


ProtoPlus.IndicatorShower = Class.create();
ProtoPlus.IndicatorShower.prototype = {
  initialize: function() {
  },

  initIndicator: function(options) {
    if (options.indicator) {
      this.indicator = $(options.indicator)
    }
    if (this.indicator) {
      this.startIndicator = this._startIndicator.bind(this);
      this.stopIndicator = this._stopIndicator.bind(this);
    } else {
      this.startIndicator = Prototype.emptyFunction;
      this.stopIndicator = Prototype.emptyFunction;
    }
  },

  _startIndicator: function() {
    if (this.indicator) {
      Element.show(this.indicator);
    }
  },

  _stopIndicator: function() {
    if (this.indicator) {
      Element.hide(this.indicator);
    }
  },

  wrapIndicatorAround: function(ajaxUpdaterOptions) {
    if (!this.indicator) {
      return ajaxUpdaterOptions;
    }
    if (ajaxUpdaterOptions.onLoading) {
      ajaxUpdaterOptions.onLoading = Function.sequence(
        this.startIndicator,
        ajaxUpdaterOptions.onLoading);
    } else {
      ajaxUpdaterOptions.onLoading = this.startIndicator;
    }
    if (ajaxUpdaterOptions.onComplete) {
      ajaxUpdaterOptions.onComplete = Function.sequence(
        ajaxUpdaterOptions.onComplete,
        this.stopIndicator);
    } else {
      ajaxUpdaterOptions.onComplete = this.stopIndicator;
    }
    return ajaxUpdaterOptions;
  }
};


