/*
  Expandable Fieldsets v0.1.1 (expandable_fieldsets.js)
  Manages the dynamic addition, deletion, and serialization of form fields.
  http://code.google.com/p/expandable-fieldsets

  Depends on prototype.js >= 1.5.1.

  Copyright (c) 2007 Brandon Arbini/Sevenwire
  http://www.opensource.org/licenses/mit-license.php
*/

var ExpandableFieldset = Class.create()

ExpandableFieldset.prototype = {
  snippetIndex: 0,
  snippetCount: 0,

  initialize: function(fieldsContainer, snippetTemplate, options) {
    this.fieldsContainer = $(fieldsContainer)
    this.snippetTemplate = new Template(snippetTemplate)
    this.options = Object.extend({
      min:1,
      snippetClass: this.snippetTemplate.evaluate({}).match(/class=['"](.+?)(\s|['"])/)[1],
      removerSnippet: '<a class="remover">Remove</a>',
      removerClass: 'remover',
      adderSnippet: '<a class="adder">Add</a>',
      adderClass: 'adder'
    }, options || {})

    this.createFields()
  },

  event: function(eventName, e) {
    if (this.options[eventName]) this.options[eventName](this, e)
  },

  createFields: function() {
    this.event('beforeCreate')
    var snippetValues = null
    if (this.options.dataField) {
      try {
        snippetValues = $(this.options.dataField).value.strip().evalJSON()
      } catch(e) {
        snippetValues = null
      }
    }
    if (snippetValues && snippetValues.each) {
      snippetValues.each(function(snippetValue) {
        this.addSnippet(snippetValue)
      }.bind(this))
    } else {
      for (var i=1; i<=Math.max(this.options.min,1); i++) {
        this.addSnippet()
      }
    }
    if (!this.options.max || this.snippetCount < this.options.max) {
      this.appendAdder()
    }
    if (this.options.dataField) {
      this.toDataField()
    }
    this.event('afterCreate')
  },

  addSnippet: function(snippet) {
    this.event('beforeAdd',{ snippet: snippet })
    var replacements = { index: '#ef' + this.snippetIndex.toString() + '#' }
    if (this.options.min && this.snippetIndex < this.options.min) {
      replacements['remover'] = ''
    } else {
      replacements['remover'] = (new Template(this.options.removerSnippet)).evaluate({ index: this.snippetIndex })
    }
    Object.keys(snippet).each(function(key) {
      replacements[key] = snippet[key]
    })
    new Insertion.Bottom($(this.fieldsContainer), this.snippetTemplate.evaluate(replacements))
    var newSnippet = $(this.fieldsContainer).getElementsByClassName(this.options.snippetClass).last()
    this.getFormElements(newSnippet).each(function(field) {
      var fieldName = field.getAttribute('name').sub(/#ef\d+#/,'')
      var fieldType = field.tagName == 'SELECT' ? 'select' : field.getAttribute('type')
      if (typeof(replacements[fieldName]) !== 'undefined') {
        switch (fieldType) {
          case 'radio':
          case 'checkbox':
            if (field.value == replacements[fieldName]) field.setAttribute('checked', 'checked')
            break
          case 'select':
            // IE doesn't auto-translate values, so a loop is required
            for (var i = 0; i < field.options.length; ++i) {
              if (field.options[i].value == replacements[fieldName]) field.selectedIndex = i
            }
            break
          default:
            field.value = replacements[fieldName]
        }
      }
      if (this.options.dataField) {
        // Safari doesn't support onchange for radio buttons
        var eventToObserve = (fieldType == 'radio') ? 'click' : 'change'
        Event.observe(field, eventToObserve, function() {
          this.toDataField()
        }.bindAsEventListener(this))
        Event.observe(field, 'keyup', function(){
          this.toDataField()
        }.bindAsEventListener(this))
      }
    }.bind(this))
    newSnippet.getElementsByClassName(this.options.removerClass).each(function(remover) {
      Event.observe(remover, 'click', function(e) {
        this.removeSnippet(Event.element(e).up('.'+this.options.snippetClass))
      }.bindAsEventListener(this))
    }.bind(this))
    this.snippetIndex++
    this.snippetCount++
    if (this.options.max && this.snippetCount == this.options.max) {
      this.removeAdder()
    }
    this.event('afterAdd', { snippet: newSnippet })
  },

  removeSnippet: function(snippet) {
    this.event('beforeRemove')
    Element.remove(snippet)
    this.snippetCount--
    if (this.options.max && this.snippetCount == this.options.max-1) {
      this.appendAdder()
    }
    if (this.options.dataField) {
      this.toDataField()
    }
    this.event('afterRemove')
  },

  appendAdder: function() {
    this.event('beforeAppendAdder')
    new Insertion.After($(this.fieldsContainer), (new Template(this.options.adderSnippet)).evaluate({}))
    Event.observe($(this.fieldsContainer).next(), 'click', function(){
      this.addSnippet()
    }.bindAsEventListener(this))
    this.event('afterAppendAdder')
  },

  removeAdder: function() {
    this.event('beforeRemoveAdder')
    if (adder = $(this.fieldsContainer).next('.'+this.options.adderClass,0)) {
      Element.remove(adder)
    }
    this.event('afterRemoveAdder')
  },

  toDataField: function() {
    this.event('beforeUpdate')
    var snippets = $(this.fieldsContainer).getElementsByClassName(this.options.snippetClass).collect(function(snippet) {
      var snippetObj = {}
      this.getFormElements(snippet).each(function(field) {
        var fieldName = field.name.sub(/#ef\d+#/,'')
        var fieldType = field.tagName == 'SELECT' ? 'select' : field.getAttribute('type')
        switch (fieldType) {
          case 'checkbox':
          case 'radio':
            if (field.checked) snippetObj[fieldName] = field.value
            break
          case 'select':
            snippetObj[fieldName] = field.options[field.selectedIndex].value
            break
          default:
            snippetObj[fieldName] = field.value
        }
      })
      return snippetObj
    }.bind(this))
    $(this.options.dataField).value = snippets.toJSON()
    this.event('afterUpdate', { snippets: snippets })
  },

  getFormElements:function(form){
    return $A($(form).getElementsByTagName('*')).select(function(element){
      return Form.Element.Serializers[element.tagName.toLowerCase()]
    })
  }
}
