/*
    Usage:
    var myForm = new SmartForm('formId', { parameters });

    parameters are:
        requiredClass        : className for required elements
        conditionalClass    : className for conditional elements
        errorDiv            : div to show if required check fails
        (each following parameter can be an array or just a single element, and each element can be a DOM object or string id)
        requiredFields        : array of required elements
        contingentFields    : array of associative arrays, each one has keys 'contingent' and 'dependent',
                              each being either a single element or an array of elements.  If all of the
                              fields in 'contingent' have values, then all in 'dependent' become required
        conditionalFields    : array of arrays, each one containing multiple elements, only one of which is required

    additions to prototype:
    TAGS('tagName1'[, 'tagName2'...]) - an alias for getElementsByTagName() (i.e. var myDivs = TAGS("div")).
        can pass in multiple tag types if desired; case-sensitivity removed
    Form.Element.getLabel('id' or element) - returns the associated label element for a form element (<label for='id'>)
*/

function TAGS() {
  var all_elements = new Array();

  for (var i = 0; i < arguments.length; i++) {
    var tag = arguments[i];

    if (typeof tag == 'string')
      elements = document.getElementsByTagName(tag.toUpperCase());

    if (arguments.length == 1) 
      return elements;

    for (element in elements) 
        all_elements.push(element);
  }

  return all_elements;
}

Form.Element.allLabels = null;
Object.extend(Form.Element, {
    getLabel:    function(element) {
        element = $(element);
        if (! Form.Element.allLabels) {
            Form.Element.allLabels = TAGS("label");
        }
        for (var i=0; i<Form.Element.allLabels.length; i++) {
            var label = Form.Element.allLabels[i];
            if (label.htmlFor == element.id) {
                return label;
            }
        }
        return null;
    }
});

var SmartForm = Class.create();
Object.extend(SmartForm.prototype, {
    defaultDef: {
        requiredClass: "",
        conditionalClass: "",
        errorDiv: "",
        requiredFields : [],
        contingentFields : [],
        conditionalFields : []
    },
    initialize:    function(element, parameters) {
        this.element = $(element);
        this.options = Object.extend(this.defaultDef, parameters || {});

        if (typeof this.options.requiredFields == 'string')
          this.options.requiredFields = [ this.options.requiredFields ];
        if (this.options.contingentFields.contingent)
          this.options.contingentFields = [ this.options.contingentFields ];
        if (typeof this.options.conditionalFields[0] == 'string')
          this.options.conditionalFields = [ this.options.conditionalFields ];

        for (var i=0; i<this.options.requiredFields.length; i++) {
          this.options.requiredFields[i] = new SmartForm.Element(this.options.requiredFields[i]);
          var label = this.options.requiredFields[i].getLabel();
          if (label) {
            label.style.fontWeight = 'bold';
          }
        }

        for (var i=0; i<this.options.contingentFields.length; i++) {
            var hash = this.options.contingentFields[i];
            if (typeof hash.contingent == 'string')
                hash.contingent = [ hash.contingent ];
            if (typeof hash.dependent == 'string')
                hash.dependent = [ hash.dependent ];

            for (var j=0; j<hash.contingent.length; j++)
                hash.contingent[j] = new SmartForm.Element(hash.contingent[j]);
            for (var j=0; j<hash.dependent.length; j++)
                hash.dependent[j] = new SmartForm.Element(hash.dependent[j]);
        }

        for (var i=0; i<this.options.conditionalFields.length; i++) {
            for (var j=0; j<this.options.conditionalFields[i].length; j++)
                this.options.conditionalFields[i][j] = new SmartForm.Element(this.options.conditionalFields[i][j]);
        }
        Event.observe(window, 'load', function () {
            Event.observe(this.element, "submit", this.onSubmit.bindAsEventListener(this));
        }.bind(this));
    },
    onSubmit: function(event) {
        if (this.options.haltOn && this.options.haltOn()) {
            Event.stop(event);
            return false;
        }

        var preventSubmit = false;
        var missing = this.checkHighlight(this.options.requiredClass, this.options.requiredFields);
        if (missing > 0) 
            preventSubmit = true;

        for (var i=0; i<this.options.contingentFields.length; i++) {
            var checkDependents = true;
            for (var j=0; j<this.options.contingentFields[i].contingent.length; j++) {
                var element = this.options.contingentFields[i].contingent[j];
                if (element.getValue().search(/\S+/) == -1) {
                    checkDependents = false;
                    break;
                }
            }
            if (checkDependents) {
                var missing = this.checkHighlight(this.options.requiredClass, this.options.contingentFields[i].dependent);
                if (missing > 0) 
                    preventSubmit = true;
            }
            else {
                this.removeHighlight(this.options.requiredClass, this.options.contingentFields[i].dependent);
            }
        }

        for (var i=0; i<this.options.conditionalFields.length; i++) {
            var passed = false;
            for (var j=0; j<this.options.conditionalFields[i].length; j++) {
                var element = this.options.conditionalFields[i][j];
                if (element.getValue().search(/\S/) > -1) {
                    passed = true;
                    break;
                }
            }
            if (passed) {
                this.removeHighlight(this.options.conditionalClass, this.options.conditionalFields[i]);
            }
            else {
                this.addHighlight(this.options.conditionalClass, this.options.conditionalFields[i]);
                preventSubmit = true;
            }
        }

        if (preventSubmit && this.options.errorDiv) {
            var errorDiv = $(this.options.errorDiv);
            errorDiv.style.display = 'block';
            errorDiv.scrollIntoView(true);
            Event.stop(event);
        }
        else if (this.options.onSubmit) {
            this.options.onSubmit();
        }

        return !preventSubmit;
    },
    getElements: function() {
        if (! this.smartElements) {
            this.smartElements = Form.getElements(this.element);
            for (var i=0; i<this.elements.length; i++) {
                this.smartElements[i] = new SmartForm.Element(this.elements[i]);
            }
        }
        return this.smartElements;
    },
    addHighlight: function(className, elements) {
        for (var i=0; i<elements.length; i++) {
            elements[i].addClassName(className);
        }
    },
    removeHighlight: function(className, elements) {
        for (var i=0; i<elements.length; i++) {
            elements[i].removeClassName(className);
        }
    },
    checkHighlight: function(className, elements) {
        var highlighted = 0;
        for (var i=0; i<elements.length; i++) {
            var element = elements[i];
            if (element.getValue().search(/\S/) > -1) {
                element.removeClassName(className);
            }
            else {
                element.addClassName(className);
                highlighted++;
            }
        }
        return highlighted;
    }
});

SmartForm.Element = Class.create();
Object.extend(SmartForm.Element.prototype, {
    initialize:        function(element) {
        this.element    = $(element);
    },
    getValue:        function () {
        var value = Form.Element.getValue(this.element);
        // a multi-select returns an array, but for our use, we just want a string
        return (typeof value == 'string') ? value : value.join(",");
    },
    setValue:       function (value) {
      this.element.value = value;
    },
    getLabel:        function () {
        return Form.Element.getLabel(this.element);
    },
    getText:        function () {
        var value = '';
        if (this.element.type == 'select-one') {
          var index = this.element.selectedIndex;
          if (index >= 0)
            value = this.element.options[index].text;
        }
        else {
          value = new Array();
          for (var i = 0; i < this.element.length; i++) {
            var opt = this.element.options[i];
            if (opt.selected && opt.text) {
              value.push(opt.text);
            }
          }
        }
        return (typeof value == 'string') ? value : value.join(",");;
    },
    addClassName:     function (className) {
        Element.addClassName(this.element, className);
    },
    removeClassName: function (className) {
        Element.removeClassName(this.element, className);
    },
    emptyOptions:     function () {
        if (this.element.type.indexOf("select") != -1) {
            for (var i=this.element.options.length-1; i>=0; i--) {
                this.element.options[i] = null;
            }
        }
    },
    addOption:         function(value, text, selected) {
        if (this.element.type.indexOf("select") != -1) {
            var i = this.element.options.length;
            this.element.options[i] = new Option(text, value, false, false);
            if (selected)
                this.element.options[i].selected = true;
        }
    },
    selectByText:      function (text) {
      if (this.element.type.indexOf("select") != -1) {
        var count = this.element.options.length;
        for (var i=0; i<count; i++) {
          if (this.element.options[i].text.toUpperCase() == text.toUpperCase()) {
            this.element.options[i].selected = true;
            return true;
          }
        }
      }
      return false;
    },
    selectByValue:     function (value) {
      if (this.element.type.indexOf("select") != -1) {
        var count = this.element.options.length;
        for (var i=0; i<count; i++) {
          if (this.element.options[i].value == value) {
            this.element.options[i].selected = true;
            return true;
          }
        }
      }
      return false;
    },
    clearSelected:     function (value) {
      if (this.element.type.indexOf("select") != -1) {
        var count = this.element.options.length;
        for (var i=0; i<count; i++) {
          this.element.options[i].selected = false;
        }
      }
    }
});
