(function($) {

  function Jsonform () {
    this.defaults = {
      response : null,
      controller : null,
      action : null,
      redirect : false,
      clearFormOnSuccess : true,
      extraData : null//object, array or function
    };
  }

  $.extend(Jsonform.prototype, {

    error : function (e) {
      alert(e);
    },

    idClassPrefix : 'jsonformid_',

    defaultvaluekey : 'jsonformdefaultvalue',

    currentId : null,

    settings : {},

    setting : function (param) {
      var data = this.currentId? this.settings[this.currentId] : this.defaults;
      return param? data[param] : data;
    },


    attach : function(target, options) {
      var uid = $.unique_id();
      var s = $.extend({}, this.defaults, options);
      if ($(target).get(0).tagName.toLowerCase() == 'form') {
        $.jsonform.saveDefaults(target);
        $(target).bind('submit', function(e) {
          e.preventDefault();
          $.jsonform.submit(this);
        });
        $(target).addClass(this.idClassPrefix + uid);
        this.settings[uid] = s;
      }
    },

    saveDefaults : function(target) {
      var key = this.defaultvaluekey;
      $(target).find('input, textarea, select').each(function() {
        $(this).data(key, $(this).val());
      });
    },

    setDefaults : function(target) {
      var key = this.defaultvaluekey;
      $(target).find('input, textarea, select').each(function() {
        $(this).val($(this).data(key));
      });
    },

    collectData : function(target) {
      var data = {};
      var setVal = function() {
        name = $(this).attr('name');
        if (name) {
          val = $(this).val();
          if (name.indexOf('[]') == (name.length - 2))
            data[name]? data[name].push(val) : data[name] = [val];
          else
            data[name] = val? val : '';
        }
      }
      $(target).find('input[type=hidden], input[type=text], input[type=password], textarea, select').each(setVal);
      $(target).find('input[type=radio]:checked').each(setVal);
      $(target).find('input[type=checkbox]:checked').each(setVal);

      return data;
    },

    submit : function (target) {
      try {
        var newId = this.getId(target);
        if (this.currentId && this.currentId != newId)
          throw new Error('double_submit');


        this.currentId = newId;
        var data = this.collectData(target);
        if (this.setting('extraData'))
          data = $.extend({}, data, $.isFunction(this.setting('extraData'))? this.setting('extraData').call() : this.setting('extraData'));
        $.json(this.setting('controller'), this.setting('action'), data, function(j, status) {
          $.jsonform.response(target, j, status);
        });
        this.unsetErrors(target);
      } catch(e) {
        this.error(e);
      }
    },
    response : function (target, j, status) {
      try {

        if (!this.currentId)
          throw new Error('no_submit_id');

        var target = $('.' + this.idClassPrefix + this.currentId);
        if (j.fielderrors || j.subseterrors) {
          this.setErrors(target, j);
        } else if (status == 'success') {
          if (this.setting('redirect') && j.redirect)
            return document.location = j.redirect;
          else if (this.setting('clearFormOnSuccess'))
            this.setDefaults(target);
        }

        var response = this.setting('response');
        if ($.isFunction(response))
          response(j, status);

        this.currentId = null;
      } catch(e) {
        this.error(e);
      }
    },

    getId: function(target) {
      var classes = $(target).attr('class').split(' ');
      var pl = this.idClassPrefix.length;
      for (var i = 0, l = classes.length; i < l; i++ ) {
        if (classes[i].length > pl && classes[i].substr(0, pl) == this.idClassPrefix) {
          return classes[i].substr(pl);
        }
      }
    },

    setErrors : function(target, j) {
      if (j.fielderrors) {
        for (field in j.fielderrors) {
          target.find('[name=' + field + ']').parents(".row").addClass('error')
        }
      }
      if (j.subseterrors) {
        var row, l;
        for (subset in j.subseterrors) {
          //                  for (row = 0, l = j.subseterrors[subset].length; row < l; row++ ) {
          for (row in j.subseterrors[subset]) {
            for (field in j.subseterrors[subset][row].data) {
              target.find(':name(' + [subset, row, field].join(',') + ')').parents(".row").addClass('error')
            }
          }
        }
      }
    },

    unsetErrors : function(target) {
      $(target).find('.error').removeClass('error');
    }


  });

  $.fn.jsonform = function(options){
    return this.each(function() {
      $.jsonform.attach(this, options);
    });
  };

  $.jsonform = new Jsonform();

})(jQuery);

