(function($){

  var plgName = 'wellForms';
  var sh = 'wf';
  var version = '0.9';
  var author = 'Igor Szyporyn';
  var copyright = 'Igor Szyporyn - Monkeyworks 2010';

  var wfDOM  =  {
    wrapper              : sh+'-wrapper',
    itemSelected        : sh+'-item-selected',
    itemHover            : sh+'-item-hover',
    active              :  sh+'-active',
    disabled            :  sh+'-disabled',
    selected            :  sh+'-selected',
    clear                :  sh+'-clear-all',
// RADIOBUTTONS DOM CLASSNAMES
    radiobutton          :  sh+'-radiobutton',
// CHECKBOX DOM CLASSNAMES
    checkbutton          :  sh+'-checkbox',
// TEXTAREA DOM CLASSNAMES
// SELECT DOM CLASSNAMES
    selActive            :  sh+'-select-active',
    selWrap              :  sh+'-wrapper-select',
    selUp                :  sh+'-select-up',
    selDown              :  sh+'-select-down',
    selTitle            :  sh+'-select-content',
    selClicker          :  sh+'-select-clicker',
    optSingle            :  sh+'-select-single',
    optWrap              : sh+'-wrapper-options',
    optScroller          : sh+'-options-scroller',
    optContent          : sh+'-options-content',
// FILE DOM CLASSNAMES
    fileWrap            :  sh+'-file-wrapper',
    fileTitle            :  sh+'-file-title',
    fileBtnWrap          :  sh+'-file-button-wrapper',
    fileBtnTitle        :  sh+'-file-button-title',
// SLIDER DOM CLASSNAMES
    sliderWrapper        :  sh+'-wrapper-slider',
    slider              :  sh+'-slider',
    sliderButton        :  sh+'-slider-button',
// FRAME DOM CLASSNAMES
    frameTop            :  sh+'-frame-top',
    frameTopLeft        :  sh+'-frame-top-left',
    frameTopRight        :  sh+'-frame-top-right',
    frameMiddle          :  sh+'-frame-middle',
    frameMiddleLeft      :  sh+'-frame-middle-left',
    frameContent        :  sh+'-frame-content',
    frameMiddleRight    :  sh+'-frame-middle-right',
    frameBottom         :  sh+'-frame-bottom',
    frameBottomLeft      :  sh+'-frame-bottom-left',
    frameBottomRight    :  sh+'-frame-bottom-right',
// ERROR TIP CLASSNAMES
    errorWrapper        :  sh+'-error-wrapper',
    errorFrame          :  sh+'-error-frame',
    errorTop            :  sh+'-error-top',
    errorTopLeft        :  sh+'-error-top-left',
    errorTopCenter      :  sh+'-error-top-center',
    errorTopRight        :  sh+'-error-top-right',
    errorMiddle          :  sh+'-error-middle',
    errorMiddleLeft      :  sh+'-error-middle-left',
    errorMiddleCenter    :  sh+'-error-middle-center',
    errorMiddleRight    :  sh+'-error-middle-right',
    errorBottom          :  sh+'-error-bottom',
    errorBottomLeft      :  sh+'-error-bottom-left',
    errorBottomCenter    :  sh+'-error-bottom-center',
    errorBottomRight    :  sh+'-error-bottom-right'
  };

  var wellForms = function(element, options) {

    var elem = $(element);
    var obj = this;

    var config = $.extend({
      errorDoInit      :  true,
      maxLines        : 8,
      uploadBtnTxt    : 'Browse'
    }, options || {});

    this.getConfig = function() {
      return config;
    };
    this.getForm = function() {
      return elem;
    };

    // RADIO BUTTON METHODS
    this.radioInit = function(el, obj, father) {

      var elem = new objInit(el, obj, father);

      var functions = {
        DOMcreate    : function() {
          this.wrapper    = $('<span />').addClass(wfDOM.wrapper).insertAfter(this.$original);
          this.button      = $('<a />').addClass(wfDOM.radiobutton).addClass(this.group).attr({'rel':this.name, 'id':this.name}).appendTo(this.wrapper);
          this.label      = this.$form.find('label[for="'+this.name+'"]');
          this.clearer    =  $('<br />').addClass('wellforms-clear').appendTo(this.$original.parent());
          return this;
        },
        DOMupdate    : function() {
          this.$original.parent().find('br.wellforms-clear:not(:last)').remove();
          if ( this.$original.attr('checked') == true ) { this.button.addClass(wfDOM.active); }
          this.wrapperHeight = this.wrapper.height();
          this.labelAlign();
          this.marginCopy();
          return this;
        },
        changeState  : function() {
          if ( this.button.hasClass(wfDOM.active) == false ) {
            this.wrapper.parent().parent().find('a').removeClass(wfDOM.active);
            this.button.addClass(wfDOM.active);
            this.$form.find(':radio[name="'+this.group+'"]').attr('checked',false);
            this.$original.attr('checked',true);
          } else {
            this.button.removeClass(wfDOM.active);
            this.$original.attr('checked',false);
          }
           return this;
        }
      };

       elem = $.extend(functions, elem || {});
      elem.name = elem.$original.attr('id');
      elem.group = elem.$original.attr('name');
      elem.DOMcreate();
      elem.DOMupdate();
      elem.errorTipInit();
      elem.errorTip.build();
      elem.errorTip.update();
      elem.updateRootErrorTips(father);
      elem.$form.find('label[for="'+this.name+'"]').click(function(e) {
        e.preventDefault();
        elem.changeState();
      });
      elem.button.click(function(e) {
        e.preventDefault();
        elem.changeState();
      });
      elem.isWellformed = true;
      elem.$original.hide();
      return obj[elem.index] = elem;
    };
    this.radioCreate = function(elId) {
      this.radioInit($('#'+elId), this.wfRadiobutton, this);
    };

    // CHECKBOX METHODS
    this.checkboxInit = function(el, obj, father) {

      var functions = {
        DOMcreate      :  function() {
          this.wrapper = $('<span />').insertAfter($(elem.original)).addClass(wfDOM.wrapper);
          this.button = $('<a />').appendTo(elem.wrapper).addClass(wfDOM.checkbutton).attr({'rel':elem.name});
          this.clearer = $('<br />').addClass('wellforms-clear').appendTo(elem.$original.parent());
        },
        DOMupdate      :  function() {
          if ( this.$original.attr('checked') == true ) { this.button.addClass(wfDOM.active); }
          this.wrapperHeight = this.wrapper.height();
          this.labelAlign();
          this.marginCopy();
        },
        changeState    : function() {
          if ( !this.button.hasClass(wfDOM.active) ) {
            this.button.addClass(wfDOM.active);
          } else {
            this.button.removeClass(wfDOM.active);
            this.$original.removeAttr('checked');
          }
        }
      };

      var elem = new objInit(el, obj, father);
       elem = $.extend(functions, elem || {});

      elem.DOMcreate();
      elem.DOMupdate();
      elem.errorTipInit();
      elem.errorTip.build();
      elem.errorTip.update();
      elem.updateRootErrorTips(father);
      elem.$form.find('label[for="'+this.name+'"]').click(function(e) {
        e.preventDefault();
        elem.changeState();
      });
      elem.button.click(function(e) {
        e.preventDefault();
        elem.changeState();
      });
      elem.isWellformed = true;
      elem.$original.hide();
      return obj[elem.index] = elem;
    };
    this.checkboxCreate = function(elId) {
      this.checkboxInit($('#'+elId), this.wfCheckbox, this);
    }

    // SELECT METHODS
    this.selectInit = function(el, obj, father) {

      var elem = new objInit(el, obj, father);

      var functions = {
        createElements  : function() {
          this.wrapper        = $('<div />').addClass(wfDOM.selWrap).insertAfter(this.$original);
          this.optWrap        = $('<div />').addClass(wfDOM.optWrap).appendTo(this.wrapper);
          if (this.type == 'single') {
            this.optWrap.addClass(wfDOM.optSingle);
          };
          this.optScroller    = $('<div />').addClass(wfDOM.optScroller).prependTo(this.optWrap);
          this.optTop          = $('<div />').addClass(wfDOM.frameTop).insertBefore(this.optScroller);
          this.optTopLeft      = $('<div />').addClass(wfDOM.frameTopLeft).appendTo(this.optTop);
          this.optTopRight    = $('<div />').addClass(wfDOM.frameTopRight).appendTo(this.optTop);
          this.selClicker      = $('<span />').addClass(wfDOM.selClicker).prependTo(this.wrapper);
          this.selTitle        = $('<span />').addClass(wfDOM.selTitle).prependTo(this.wrapper);
          this.selClickerBtn  = $('<a />').prependTo(this.selClicker);
          this.optContent      = $('<ul />').addClass(wfDOM.optContent).prependTo(this.optScroller);
          this.optRight        = $('<div />').addClass(wfDOM.frameMiddleRight).insertAfter(this.optContent);
          this.optBottom      = $('<div class="'+wfDOM.frameBottom+'" />').appendTo(this.optWrap);
          this.optBottomLeft  = $('<div class="'+wfDOM.frameBottomLeft+'" />').appendTo(this.optBottom);
          this.optBottomRight  = $('<div class="'+wfDOM.frameBottomRight+'" />').appendTo(this.optBottom);

          $('<br />').addClass('wellforms-clear').insertAfter(this.selClicker);
          $('<br />').addClass('wellforms-clear').appendTo(elem.$original.parent());
          return this;
        },
        populateOptions  :  function() {
          this.optContent.empty();
          this.$original.find('option').each(function(i){
            oLi = $('<li index="'+ i +'" rel="'+$(this).val()+'" class="select-item">'+$(this).html()+'</li>');
  
            if ( $(this).attr('selected') ) {
              if ( !$(this).attr('disabled') ) { oLi.addClass(wfDOM.itemSelected); }
              elem.currentOption = $(this).val();
            }
            if ( $(this).attr('disabled') ) {
              oLi.addClass(wfDOM.disabled);            
            }
            oLi.hover(function() {
              if ( !$(this).hasClass(wfDOM.disabled) ) { $(this).addClass(wfDOM.itemHover); }
            }, function() {
              if ( !$(this).hasClass(wfDOM.disabled) ) { $(this).removeClass(wfDOM.itemHover); }
            });
            elem.optContent.append(oLi);
          });
          this.optionsLength = $('li', this.optContent).length;
          var line = this.optContent.find('li:first');
          this.optionsLineHeight = parseFloat(line.css('line-height')) + parseFloat(line.css('margin-bottom'));
          this.populated = true;
          if (this.optionsLength > options.maxLines) {
            this.optionsHeight = options.maxLines * this.optionsLineHeight;
          } else {
            this.optionsHeight = this.optionsLineHeight * this.optionsLength;
          }
  
          this.optContent.find('li.select-item').click(function(e) {
            if ( !$(this).hasClass(wfDOM.disabled) ) {
            // CLICK ACTION FOR SINGLE SELECT
            if (elem.type == 'single') {
              e.stopPropagation();
              elem.selTitle.html($(this).html());
               elem.$original.val($(this).attr('rel'));
              if (!$(this).parent().hasClass(wfDOM.itemSelected)) {
                elem.optContent.find('li.select-item').removeClass(wfDOM.itemSelected);
                $(this).addClass(wfDOM.itemSelected);
                elem.$form.find('.'+wfDOM.selActive).removeClass(wfDOM.selActive);
              }
              elem.optWrap.hide();
              elem.$original.change();
            // CLICK ACTION FOR MULTI SELECT
            } else {
              if(e.ctrlKey) { 
                if ($(this).hasClass(wfDOM.itemSelected)) {
                  if (elem.optContent.find('.'+wfDOM.itemSelected).length != 1) {
                    elem.$original.find('option[value="'+$(this).attr('rel')+'"]').attr('selected',false);
                    $(this).removeClass(wfDOM.itemSelected);
                  }
                } else {
                  elem.$original.find('option[value="'+$(this).attr('rel')+'"]').attr('selected',true);
                  $(this).addClass(wfDOM.itemSelected);
                }
              } else {
                elem.optContent.find('li.select-item').removeClass(wfDOM.itemSelected);
                $.each(elem.$original.find('option'), function() {
                  if ($(this).attr('selected')) {
                    $(this).attr('selected',false);
                  }
                });
                elem.$original.find('option[value="'+$(this).attr('rel')+'"]').attr('selected',true);
                $(this).addClass(wfDOM.itemSelected);
              }
            }
          }
          });
          return this;
        },
        adjustSelect  : function() {
          this.selTitle.css({
            'line-height'    :  this.selTitle.outerHeight() + 'px'
          });
          this.selTitle.css({
            'width'          :  this.baseWidth - this.selectPadding + 'px'
          });
          $(elem.wrapper).ready(function () {
            elem.selectHeight = elem.selTitle.outerHeight();
            elem.selectOffset = elem.wrapper.offset().top;
          });
          return this;
        },
        adjustOptions  : function() {
          if (this.type == 'single') {
            this.optWrap.css({
              'position'    :  'absolute'
            });
            this.optionsPos();
            this.optWrap.hide();
          } else {
            this.selTitle.hide();
            this.selClicker.hide();
          };
          this.optWrap.css({
            'height'        : this.optionsHeight + this.diff + 'px'
          });
          this.optScroller.css({
            'height'      :  this.optionsHeight +'px'
          });
          this.optBottomLeft.css({
            'width'          : this.baseWidth +'px'
          });
          this.optBottomRight.css({
            'width'          : this.fullWidth - this.baseWidth +'px'
          });
          this.optTopLeft.css({
            'width'          : this.baseWidth +'px'
          });
          this.optTopRight.css({
            'width'          : this.fullWidth - this.baseWidth +'px'
          });
          if (this.hasSlider) {
            this.optWrap.css({
              'height'       :  this.optionsHeight + this.optBottomLeft.height() + 'px'
            });
          } else {
            this.optRight.css({
              'height'      : this.optScroller.css('height')
            });
            this.optWrap.css({
              'height'      :  this.optScroller.css('height')
            });
            this.optScroller.css({
              'width'        :  this.fullWidth +'px'
            });
            this.optContent.css({
              'width'        :  this.fullWidth - this.optionsPadding - this.optRight.width() +'px'
            });
          };
          return this;
        },
        setDirection  :  function() {
          var that = this;
          $(document).ready(function() {
            var documentHeight = $(document).height();
            that.direction = (that.selectOffset + that.selectHeight + that.optionsFullHeight > documentHeight) ? 'up' : 'down';
            var directionClass = (that.direction == 'up') ? wfDOM.selUp : wfDOM.selDown;
            that.wrapper.addClass(directionClass);
            if (that.type == 'single') {
              if (that.direction == 'up') {  that.optBottom.hide(); } else {  that.optTop.hide();  }
            }
            return that;
          });
           return this;
        },
        selectInit  : function() {
          this.optWrap.show();
          this.selectPadding = parseFloat(this.selTitle.css('padding-left')) + parseFloat(this.selTitle.css('padding-right'));
          this.optionsPadLeft = parseFloat(this.selTitle.css('padding-left')) - parseFloat(this.optContent.css('padding-left'));
          this.optionsPadding = parseFloat(this.optContent.css('padding-left')) + parseFloat(this.optContent.css('padding-right'));
          this.fullWidth = this.optContent.outerWidth() + this.selClicker.outerWidth();
          this.baseWidth = this.optContent.outerWidth();
          this.optionsFullHeight = this.optionsHeight + this.optBottom.height() + this.optTop.height();
          this.optionsWidth = this.optContent.width();
          this.contentWidth = this.optContent.innerWidth() + this.optionsPadding ;
          this.selClickerBtn.css({'margin-top': ( this.selTitle.height() - this.selClickerBtn.height() ) / 2 + 'px'});
          this.label = this.$original.parent().find('label[for="'+this.name+'"]').css('line-height', this.selTitle.outerHeight() + 'px');
          if (this.optionsLength > options.maxLines) {
            this.hasSlider = true;
            this.addSlider();
            this.adjustSlider();          
          } else {
            this.hasSlider = false;
          }
          this.adjustSelect();
          this.setDirection();
          this.adjustOptions();
          if (this.type == 'multiple') {
            this.selTitle.remove();
            this.selClicker.next().remove().end().remove();
          }
          this.marginCopy();
          return this;
        },
        adjustSlider  :  function() {
          this.sliderButtonHeight = parseFloat($('a', this.slider).css('height'));
          this.sliderHeight = this.sliderButtonHeight;
          this.sliderWrapper.css({'padding-top':$('a', this.slider).innerHeight() + 'px', 'padding-bottom': $('a', this.slider).innerHeight() +'px'});
          this.sliderWrapper.css({'height':(this.optionsHeight - ( parseInt(this.sliderWrapper.css('padding-bottom')) + parseInt(this.sliderWrapper.css('padding-top')))) +'px'});
          this.slider.css({ 'height': this.optionsHeight - ( this.sliderButtonHeight * 2 ) + 'px' });
          return this;
        },
        sliderMoveTo  :  function(uiVal) {
          if (this.optionsLength > options.maxLines) {
            var maxScroll = (this.optionsLength - options.maxLines) * this.optionsLineHeight;
            this.options
            return slideTo = (uiVal * (maxScroll / 100) * 1);
          }
          return this;      
        },
        addSlider  :  function() {
          this.sliderWrapper = $('<div />').addClass(wfDOM.sliderWrapper).insertAfter(this.optContent);
          this.slider = $('<div />').addClass(wfDOM.slider).appendTo(this.sliderWrapper);
          $('<br />').addClass('wellforms-clear').insertAfter(this.sliderWrapper);
          this.optRight.hide();
          
            this.slider.slider({
              orientation: 'vertical',
              min: 0,
              max: 100,
              value: 100,
              slide: function(event, ui) {
                var val = (ui.value - 100);
                var slideTo = elem.sliderMoveTo(val);
                elem.optContent.css('top',slideTo+'px');
              },
              change: function(event, ui) {
                var val = (ui.value - 100);
                var slideTo = elem.sliderMoveTo(val);
                elem.optContent.css('top',slideTo+'px');
              }
            });
          return this;        
        },
        optionsPos  :  function() {
          $(document).ready(function() {
            var diff = (elem.direction == 'up') ? elem.optBottomLeft.height() : elem.optTopLeft.height();
            var posTo = (elem.direction == 'up') ? elem.selectOffset - elem.optionsFullHeight + diff : elem.selectOffset + elem.selectHeight;
             elem.optWrap.css({
              'top'  :   posTo + 'px'
             });
          });
          return this;
        },
        eventHandler  :  function() {
          
          $(document).click(function() {
            var present = elem.$form.find('.'+wfDOM.selWrap+'.'+wfDOM.selActive)
            present.removeClass(wfDOM.selActive);
            present.find('.'+wfDOM.optWrap).hide();
          });
          
          this.selTitle.click(function(e) {
            e.stopPropagation();
            selectToggle(elem, this);          
          });
          this.selClicker.click(function(e) {
            e.preventDefault();
            e.stopPropagation();
            selectToggle(elem, this);  
          }); 
          return this;
        },
        setTitle  :  function() {
          this.currentOption = this.$original.children('option:selected');
          this.selectText = this.currentOption.text();
          this.selTitle.html(this.selectText);
          return this;
        }        
      }

      var options = this.getConfig();

       elem = $.extend(functions, elem || {});
      elem.type = (elem.$original.attr('multiple')) ? 'multiple' : 'single' ;

      elem.createElements();
      elem.populateOptions();
      elem.selectInit();
      elem.errorTipInit();
      elem.errorTip.build();
      elem.errorTip.update();
      elem.updateRootErrorTips(father);
      elem.eventHandler();
      elem.setTitle();
      elem.isWellformed = true;
      elem.$original.hide();
      return obj[elem.index] = elem;
    };
    this.selectCreate = function(trgt) {
      this.selectInit($('#'+trgt), this.wfSelect, this);
    };
    this.selectUpdate = function(trgt) {
      var obj = this;
      $.each(this.wfSelect, function() {
        if (this.name === trgt) {
          this.populateOptions();
          this.addSlider();
          this.selectInit();
        }
      });
    };
    function selectToggle(elem, obj) {
      if (!elem.wrapper.hasClass(wfDOM.selActive)) {
        elem.$form.find('.'+wfDOM.optWrap+'.'+wfDOM.optSingle).hide();
        elem.$form.find('.'+wfDOM.selActive).removeClass(wfDOM.selActive);
        elem.wrapper.addClass(wfDOM.selActive);
        elem.optWrap.show();
      } else {
        elem.$form.find('.'+wfDOM.selActive).removeClass(wfDOM.selActive);
        elem.optWrap.hide();
      }
    };

    // INPUT METHODS
    this.inputInit = function(el, obj, father) {

      var elem = new objInit(el, obj, father);
      elem.getSize = function() {
        this.outerW = this.$original.outerWidth();
        this.innerW = this.$original.width();
        this.outerH = this.$original.outerHeight();
        this.innerH = this.$original.height();
        return this;
      };
      elem.createDOM = function() {
        // Empty in case of already wellformed
        if (this.isWellformed) {
          this.marginRestore();
          this.$original.appendTo(this.wrapper.parent());
          this.wrapper.next().remove();
          this.wrapper.remove();
          this.wellformed = false;
        }
        this.wrapper = $('<div />').addClass(wfDOM.wrapper).insertAfter(this.$original);
        this.frameTop = $('<div />').addClass(wfDOM.frameTop).appendTo(this.wrapper);
        this.frameTopLeft = $('<div />').addClass(wfDOM.frameTopLeft).appendTo(this.frameTop);
        this.frameTopRight = $('<div />').addClass(wfDOM.frameTopRight).appendTo(this.frameTop);
        this.frameMiddle = $('<div />').addClass(wfDOM.frameMiddle).appendTo(this.wrapper);
        this.frameContent = $('<div />').addClass(wfDOM.frameContent).appendTo(this.frameMiddle);
        this.frameMiddleRight = $('<div />').addClass(wfDOM.frameMiddleRight).appendTo(this.frameMiddle);
        this.frameBottom = $('<div />').addClass(wfDOM.frameBottom).appendTo(this.wrapper);
        this.frameBottomLeft = $('<div />').addClass(wfDOM.frameBottomLeft).appendTo(this.frameBottom);
        this.frameBottomRight = $('<div />').addClass(wfDOM.frameBottomRight).appendTo(this.frameBottom);
        this.$original.appendTo(this.frameContent);
        $('<br class="wellforms-clear"/>').insertAfter(this.wrapper);
        this.wrapper.parent().find('br.wellforms-clear:not(:last)').remove();
        return this;
      };
      elem.alignDOM = function() {
        var that = this;
        this.wrapper.css({
          // LOOK INTO!
          'width'          :  this.getSize().outerW + (2 * (parseFloat(this.frameTop.css('height'))))+'px',
          'height'        :  this.getSize().outerH + this.frameTop.height() + this.frameBottom.height() + 'px'
        });
        this.frameTopLeft.css({
          'width'          :  this.getSize().outerW + parseFloat(this.frameTop.css('height'))+'px'
        });
        this.frameTopRight.css({
          'width'          :  this.frameTop.css('height')
        });
        this.frameContent.css({
          'padding-left'  : this.frameTop.css('height'),
          'height'        : this.$original.outerHeight()+'px'         
        });
        this.frameMiddleRight.css({
          'width'         : this.frameTop.css('height'),
          'height'        : this.$original.outerHeight()+'px'      
        });
        this.frameBottomLeft.css({
          'width'          :  this.getSize().outerW + parseFloat(this.frameTop.css('height'))+'px'
        });
        this.frameBottomRight.css({
          'width'          :  this.frameTop.css('height')
        });
        if ( this.$original.attr('disabled') ) {
          this.wrapper.css('opacity','0.7');
          this.$original.addClass(wfDOM.disabled);
        };
         this.wrapperHeight = this.wrapper.height();
        this.labelAlign();
        this.marginCopy();
        return this;
      };
      elem.createDOM();
      elem.alignDOM();
      elem.errorTipInit();
      elem.errorTip.build();
      elem.errorTip.update();
      elem.updateRootErrorTips(father);
      this.isWellformed = true;
      return obj[elem.index] = elem;  
    };
    this.inputCreate = function(trgt) {
      this.inputInit($('#'+trgt), this.wfInput, this, this);
    };
    this.inputUpdate = function(trgt) {
      $.each(this.wfInput, function() {
        if (this.name === trgt) {
          this.createDOM();
          this.alignDOM();
        }
      });
    };   

    // FILE INPUT METHODS
    this.fileInit = function (el, obj, father) {
      var elem = new objInit(el, obj, father);
      elem.marginLeft = elem.$original.css('margin-left');
      elem.marginRight = elem.$original.css('margin-right');
      var options = this.getConfig();
      $.each(obj, function(j) {  i = Math.max(j) + 1; });      
      frameWrapper(elem);
      
      elem.fileWrapper = $('<div />').addClass(wfDOM.fileWrap).prependTo(elem.wrapper);
      elem.frameTop.appendTo(elem.fileWrapper);
      elem.frameMiddle.appendTo(elem.fileWrapper);
      elem.frameBottom.appendTo(elem.fileWrapper);
      elem.fileTitle = $('<span />').addClass(wfDOM.fileTitle).prependTo(elem.frameContent);
      elem.buttonWrapper = $('<div />').addClass(wfDOM.fileBtnWrap).insertAfter(elem.fileWrapper);

      elem.btnTop = $('<div />').addClass(wfDOM.frameTop).appendTo(elem.buttonWrapper);
      elem.btnTopRight = $('<div />').addClass(wfDOM.frameTopRight).appendTo(elem.btnTop);
      elem.btnMiddle = $('<div />').addClass(wfDOM.frameMiddle).appendTo(elem.buttonWrapper);
      elem.btnTitle = $('<span />').addClass(wfDOM.fileBtnTitle).appendTo(elem.btnMiddle);
      elem.btnBottom = $('<div />').addClass(wfDOM.frameBottom).appendTo(elem.buttonWrapper);
      elem.btnBottomRight = $('<div />').addClass(wfDOM.frameBottomRight).appendTo(elem.btnBottom);

      elem.frameTopRight.remove();
      elem.frameMiddleRight.remove();
      elem.frameBottomRight.remove();
      elem.btnTitle.html(options.uploadBtnTxt);
    
      elem.alignDOM = function() {
        this.$original.css({
          'margin-left'    : '0px',
          'margin-right'  :  '0px',
          'position'      : 'absolute',
          'left'          : elem.fileTitle.offset().left + 'px',
          'opacity'        :  '0'
        });
        this.wrapper.css({
          'margin-left'    :  this.marginLeft,
          'margin-right'  :  this.marginRight
        });
        this.frameTopLeft.css({
          'width'          :  this.getSize().outerW - ( elem.buttonWrapper.outerWidth() ) + 'px'
        });
        this.frameContent.css({
          'padding-left'  : parseFloat(this.frameTop.css('height')) + 'px',
          'height'        : elem.$original.outerHeight()+'px',
          'width'          :  ( elem.$original.outerWidth() - parseFloat(this.frameTop.css('height')) ) - ( elem.buttonWrapper.outerWidth() ) + 'px',
          'overflow'      :  'hidden'
        });
        this.frameBottomLeft.css({
          'width'          :  this.getSize().outerW - ( elem.buttonWrapper.outerWidth() ) + 'px'
        });
        this.wrapper.parent().find('label').css({
          'line-height'    : elem.wrapper.outerHeight() + 'px'
        });
        this.fileWrapper.css({
          'width'          :  this.getSize().outerW - ( elem.buttonWrapper.outerWidth() ) + 'px'
        });
        this.fileTitle.css({
          'line-height'    :  elem.$original.outerHeight()+'px'
        });
        this.btnTitle.css({
          'line-height'    :  elem.$original.outerHeight()+'px',
          'padding-left'  :  parseFloat(this.frameTop.css('height')) + 'px'
          //'padding-right'  :  parseFloat(this.frameTop.css('height')) + 'px'
        });
        this.btnTopRight.css({ 'width' : this.btnTitle.outerWidth() + 'px' });
        this.btnBottomRight.css({ 'width' : this.btnTitle.outerWidth() + 'px' });
      }
      elem.getSize = function() {
        this.outerW = this.$original.outerWidth();
        this.innerW = this.$original.width();
        this.outerH = this.$original.outerHeight();
        this.innerH = this.$original.height();
        return this;
      };
      elem.alignDOM();

      elem.$original.change(function() {
        elem.fileTitle.html(elem.$original.val().replace(/^.*\\/, ''));
      });

      return obj[elem.index] = elem;      
    };

    // TEXTAREA METHODS
    this.textareaInit = function(el, obj, father) {

      var elem = new objInit(el, obj, father);

      var functions = {
        DOMcreate  :  function() {
          frameWrapper(elem);
          this.sliderWrapper = $('<div />').addClass(wfDOM.sliderWrapper).appendTo(this.frameContent);
          this.slider = $('<div class="'+wfDOM.slider+'" />').prependTo(this.sliderWrapper);
          return this;
        },
        DOMupdate  :  function() {
          this.outerHeight = this.$original.attr('rows') * parseInt(this.$original.css('line-height'));
          this.innerHeight = this.$original.attr('rows') * parseInt(this.$original.css('line-height'));
          this.outerWidth = this.$original.outerWidth();
          this.innerWidth = this.$original.width()
          this.$original.css({'width':this.innerWidth-10+'px', 'height':this.innerHeight+'px'});
          this.sliderWrapper.css({'float':'left', 'position':'absolute', 'left':this.wrapper.offset().left + this.outerWidth - this.slider.width() });
          this.frameTop.css('width',this.outerWidth+'px');
          this.frameTopLeft.css({'width':this.outerWidth - parseFloat(this.slider.css('width')) +'px'});
          this.frameTopRight.css({'width':this.slider.css('width')});
          this.frameMiddleRight.remove();
          this.frameBottomLeft.css({'width':this.outerWidth - parseFloat(this.slider.css('width')) +'px'});
          this.frameBottomRight.css({'width':this.slider.css('width')});
          this.frameBottom.css('width',this.outerWidth+'px');
          this.marginCopy();
        },
        getCurRow  : function() {
          var curPos = this.$original.getCursorPosition();
          var fullStr = elem.$original.val();
          var curStr = fullStr.substring(0,curPos);
          var str = curStr.split('\n');
          var curRow = 0;
          $.each(str, function(i) {
            var k = 0;
            var k = Math.ceil(str[i].length / 70);
            if ( k <= 1) { k = 0; }
            curRow = curRow + 1 + k;
          });
          return curRow;
        },
        addSlider  : function() {
          var that = this;
          this.slider.slider({
            orientation: 'vertical',
            min: 0,
            max: 100,
            value: 0,
            slide: function(event, ui) {
              slideTo = that.slideToFromSelf(ui.value);
              that.$original.scrollTop(slideTo);
            },
            change: function(event, ui) {
              slideTo = that.slideToFromSelf(ui.value);
              that.$original.scrollTop(slideTo);
            }
          });
          this.sliderHeight = parseInt($('a', this.slider).css('height'));
          this.slider.css({ 'height': this.$original.outerHeight() - ( this.sliderHeight * 2 ) + 'px' });
          this.sliderButton = $('a', this.slider);
          this.sliderWrapper.css({'padding-top':$('a', elem.slider).innerHeight() + 'px', 'padding-bottom': $('a', this.slider).innerHeight() +'px'});
          this.sliderWrapper.css({'height':(this.frameContent.height() - ( parseInt(this.sliderWrapper.css('padding-bottom')) + parseInt(this.sliderWrapper.css('padding-top')))) +'px'});
          this.sliderButton.hide();
          return this;
        },
        slideToFromTextarea  : function(row) {
          var curRow = ( row == '' ) ? row : this.getCurRow();
          var maxRows = this.lineCount();
          if (curRow == 1) { curRow = 0; }
          var ratio = curRow / maxRows;
          var slideTo = 100 - Math.round(ratio * 100);
          slideTo = Math.floor(slideTo / 10);
          if (slideTo <= 1 ) { slideTo = 0; }
          this.sliderButton.css('bottom',slideTo * 10+'%');
        },
        slideToFromSelf  : function(sliderValue) {
          var scrollIncrement = parseFloat(this.$original.css('line-height'));
          var maxScroll = (this.lineCount() - this.$original.attr('rows')) * scrollIncrement;
          var slideTo = (maxScroll / 100) * (100 - sliderValue);        
          return slideTo;
        },
        lineCount  : function() {
          return $.countLines(this.$original).visual;
        },
        sliderInitPos  : function(to) {
          var rows = this.$original.attr('rows');
          if (this.lineCount() >= rows+1) { this.sliderButton.show(); }
          if (this.lineCount() <= rows) { this.sliderButton.hide(); }
          if ( to === '' ) {
            this.slideToFromTextarea();
          } else {
            this.slideToFromTextarea(to);
          }
          return this;
        }
      }

       elem = $.extend(functions, elem || {});

      elem.DOMcreate();
      elem.DOMupdate();
      elem.addSlider();
      elem.sliderInitPos(0);
      elem.$original
        .click(function() {
          elem.sliderInitPos();
        })
        .keyup(function() {
          elem.sliderInitPos();
        })
         .keydown(function() {
          elem.sliderInitPos();
        })
        .focus(function() {
          elem.sliderInitPos();          
        });
      elem.errorTipInit();
      elem.errorTip.build();
      elem.errorTip.update();
      elem.updateRootErrorTips(father);
      return obj[elem.index] = elem;
    };

    if ( typeof(options.errorPlugin) === 'function' ) {
      this.errorPlugin = options.errorPlugin;
    };

    return this;
  };

  var frameWrapper = function(elem) {
    if (elem.isWellformed) {
      elem.$original.appendTo(elem.wrapper.parent());
      elem.wrapper.next().remove();
      elem.wrapper.remove();
      elem.wellformed = false;
    }
    elem.wrapper = $('<div />').addClass(wfDOM.wrapper).insertAfter(elem.$original);
    elem.frameTop = $('<div />').addClass(wfDOM.frameTop).appendTo(elem.wrapper);
    elem.frameTopLeft = $('<div />').addClass(wfDOM.frameTopLeft).appendTo(elem.frameTop);
    elem.frameTopRight = $('<div />').addClass(wfDOM.frameTopRight).appendTo(elem.frameTop);
    elem.frameMiddle = $('<div />').addClass(wfDOM.frameMiddle).appendTo(elem.wrapper);
    elem.frameContent = $('<div />').addClass(wfDOM.frameContent).appendTo(elem.frameMiddle);
    elem.frameMiddleRight = $('<div />').addClass(wfDOM.frameMiddleRight).appendTo(elem.frameMiddle);
    elem.frameBottom = $('<div />').addClass(wfDOM.frameBottom).appendTo(elem.wrapper);
    elem.frameBottomLeft = $('<div />').addClass(wfDOM.frameBottomLeft).appendTo(elem.frameBottom);
    elem.frameBottomRight = $('<div />').addClass(wfDOM.frameBottomRight).appendTo(elem.frameBottom);
    elem.$original.appendTo(elem.frameContent);
    $('<br class="wellforms-clear"/>').insertAfter(elem.wrapper);
    elem.isWellformed = true;
    elem.wrapper.parent().find('br.wellforms-clear:not(:last)').remove();

    return elem;
  };

/*  var updateRootErrorTips = function(obj, root) {
    var i = 0;
    $.each(obj, function(i, o) {
      $.each(o, function(id, val) {
        if ( id == 'errorTip' ) {
          var len = root.errorTips.length;
          var k = ( typeof(len) === 'undefined') ? i : len + 1 ;
          return root.errorTips[k] = val;
        }        
      });
    });
    return obj;
  };
*/
  // METHOD PARENT CONSTRUCTOR
  var objInit = function(el, obj, father) {
    var elem = new Object();
    elem.index = 0;
    elem.root = father;
    elem.$form = father.getForm();
    $.each(obj, function(j) {  elem.index = Math.max(j) + 1; });
    elem.$original = el;
    elem.original = $(el).get(0);
    elem.name = ( el.attr('name') ) ? el.attr('name') : el.attr('id');
    elem.errorDoInit = father.getConfig().errorDoInit;
    elem.errorIsInit = false;
    elem.isWellformed = false;
    elem.disabled = ( el.attr('disabled') ) ? true :  false;
    elem.marginCopy = function() {
      this.wrapper.css({
        'margin-left'    :  this.$original.css('margin-left'),
        'margin-right'  :  this.$original.css('margin-right')
      });
      this.$original.css({
        'margin-left'    :  '0px',
        'margin-right'  :  '0px'
      });
      return this;
    }
    elem.marginRestore = function() {
      this.$original.css({
        'margin-left'    :  this.wrapper.css('margin-left'),
        'margin-right'  :  this.wrapper.css('margin-right')
      });
      return this;
    };
    elem.disable = function() {
      this.wrapper.addClass(wfDOM.disabled);
      this.$original.attr('disabled','disabled');
      this.disabled = true;
      return this;
    };
    elem.enable = function() {
      this.wrapper.removeClass(wfDOM.disabled);
      this.$original.removeAttr('disabled');
      this.disabled = false;
      return this;
    };
    elem.labelAlign = function() {
      this.wrapper.parent().children('label').css('line-height',this.wrapperHeight + 'px');
      return this;
    };
    elem.updateRootErrorTips = function(father) {
      if ( !father.errorTips ) { 
        father.errorTips = [];
      }
      var len = father.errorTips.length;
      father.errorTips[len] = this.errorTip;
      return this;
    }

    elem.errorTipInit = function() {
      if ( !this.errorIsInit ) {
        this.errorTip = new InitErrorTip(this);
        return this;
      };
    };
    elem.parent = elem.root;
    return elem;
  };

  var InitErrorTip = function(father) {

    var tip = new Object();

    tip = {
      build    :  function() {
        this.wrapper  = $('<div />').addClass(wfDOM.wrapper).addClass(wfDOM.errorWrapper).prependTo(this.father.wrapper.parent());
        this.wrapper.css('visibility','hidden');
        this.isBuilt = true;
        return this;
      },
      update  :  function() {
        if (!this.isBuilt) { return false; }
        this.wrapper.css({
          'left'        :  this.father.wrapper.width() + this.father.wrapper.offset().left + 'px'
        });
        return this;
      },
      remove  :  function() {},
      show    :  function() {}
    };

    tip.father  = father;
    tip.root  =  father.parent;
    tip.isBuilt = false;
    father.errorIsBuilt = false;
    father.errorIsInit = true;
    return tip;
  }

  // CONSTRUCTORS FOR PLUGIN
  var _objRadiobutton = function(wf) {
    var radioBtns = this;
    var root = wf.getForm();
    $(':radio', root).each(function() {
      wf.radioInit($(this), radioBtns, wf);
    });
    return radioBtns;
  };
  var _objCheckbox = function(wf) {
    var checkBoxes = this;
    var root = wf.getForm();
    $(':checkbox', root).each(function() {
      wf.checkboxInit($(this), checkBoxes, wf);
    });
    return checkBoxes;    
  };
  var _objSelect = function(wf) {
    var selectObjs = this;
    var root = wf.getForm();
    $('select', root).each(function(i) {
       wf.selectInit($(this), selectObjs, wf);
    });
    return selectObjs;    
  };
  var _objTextarea = function(wf) {
    var textAreas = this;
    var root = wf.getForm();
    var options = wf.getConfig();
    $('textarea', root).each(function() {
      wf.textareaInit($(this), textAreas, wf);
    });    
  };
  var _objInput = function(wf) {
    var inputObjs = this;
    var root = wf.getForm();
    var options = wf.getConfig();
    $('input[type=text]', root).each(function() {
      wf.inputInit($(this), inputObjs, wf);
    });
    $('input[type=submit]', root).each(function() {
      wf.inputInit($(this), inputObjs, wf);
    });
    $('input[type=password]', root).each(function() {
      wf.inputInit($(this), inputObjs, wf);
     });
    $('input[type=button]', root).each(function() {
      wf.inputInit($(this), inputObjs, wf);
    });
  };
  var _objFile = function(wf) {
    var fileObjs = this;
    var root = wf.getForm();
    $('input[type=file]', root).each(function() {
      wf.fileInit($(this), fileObjs, wf);
    });
  }

  // PLUGIN CONTRUCTOR
  $.fn.wellforms = function(options) {
    return this.each(function() {
      var element = $(this);

      // Return early if this element already has a plugin instance
      if (element.data('wellforms')) return;
      
      var defaults = [];
      defaults = {
        errorPlugin  :  function() {}        
      };

      var o = [];
      o = $.extend(defaults, options);

      if ( !element.hasClass('wellforms') ) { element.addClass('wellforms'); }
      
      var wf = new wellForms(this, o);
      
      wf.info = {
        version     :  version,
        pluginName  :  plgName,
        author      :  author,
        copyright    :  copyright
      };

      wf.wfRadiobutton  = new _objRadiobutton(wf);
      wf.wfCheckbox      = new _objCheckbox(wf);
      wf.wfSelect        = new _objSelect(wf);
      wf.wfTextarea      = new _objTextarea(wf);
      wf.wfInput        = new _objInput(wf);
      wf.wfFile          = new _objFile(wf);
      wf.errorPlugin();

/*      $.each(wf.wfInput, function(i) {
        console.log(i);
        //return wf.errorTips[i] = this.errorTip;
      });*/

      element.wfSwapValue();

      // Store plugin object in this element's data
      element.data({'wellforms':wf});
    });
  };    

  jQuery.fn.getCursorPosition = function() {
      var pos = 0;
      var el = $(this).get(0);
      // IE Support
      if (document.selection) {
          el.focus();
          var Sel = document.selection.createRange();
          var SelLength = document.selection.createRange().text.length;
          Sel.moveStart('character', -el.value.length);
          pos = Sel.text.length - SelLength;
      }
      // Firefox support
      else if (el.selectionStart || el.selectionStart == '0')
          pos = el.selectionStart;
  
      return pos;
  };

  jQuery.fn.wfSwapValue = function (options) {
   
      var defaults = [];
      defaults = {
          colorBlur : '#999',
          colorFocus : '#000'
      }
   
      var o = [];
      o = $.extend(defaults, options);
   
      var swapVal = [];                      
   
      this.find('.wf-swap').each(function(i) {
          swapVal[i] = $(this).val();
          $(this).css({'color':o.colorBlur})
              .focus(function() {
                  //$(this).css({'color':o.colorFocus});
                  if ($(this).val() == swapVal[i]) {
                      $(this).val('');
                  }
              })
              .blur(function() {
                  if ($.trim($(this).val()) == '') {
                      $(this).css({'color':o.colorBlur});
                      $(this).val(swapVal[i]);
                  }
              });
      });
      return this;
  };

  $.countLines = function(textarea, o){
// The textarea
var ta;

if (typeof textarea == "string")
    ta = $(textarea);
else if (typeof textarea == "object")
    ta = textarea;

if (ta.size() != 1)
    return;

// Get the textarea value
var value = ta.val();

var options = $.extend({
    recalculateCharWidth : false,
    chars : "",
    charsMode : "random",
    fontAttrs : ["font-family", "font-size", "text-decoration", "font-style", "font-weight"]
}, o);
var masterCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
var counter;
switch (options.charsMode){
    case "random":
  // Build a random collection of characters
  options.chars = "";
  masterCharacters += ".,?!-+;:'\"";
  for (counter = 1; counter <= 12; counter++)
      options.chars += masterCharacters[(Math.floor(Math.random() * masterCharacters.length))];
  break;
    case "alpha":
  options.chars = masterCharacters;
  break;
    case "alpha_extended":
  options.chars = masterCharacters + ".,?!-+;:'\"";
  break;
    case "from_ta":
  // Build a random collection of characters from the textarea
  if (value.length < 15)
      options.chars = masterCharacters;
  else
      for (counter = 1; counter <= 15; counter++)
    options.chars += value[(Math.floor(Math.random() * value.length))];
  break;
         case "custom":
              // Already defined in options.chars
              break;
}

// Decode chars
if (!$.isArray(options.chars))
    options.chars = options.chars.split("");

// Generate a span after the textarea with a random ID
var id = "";
for (counter = 1; counter <= 10; counter++)
    id += (Math.floor(Math.random() * 10) + 1);

ta.after("<span id='s" + id + "'></span>");
var span = $("#s" + id);

// Hide the span
span.hide();

// Apply the font properties of the textarea to the span class
$.each(options.fontAttrs, function(i, v){
    span.css(v, ta.css(v));
});

// Get the number of lines
var lines = value.split("\n");
var linesLen = lines.length;

var averageWidth;

// Check if the textarea has a cached version of the average character width
if (options.recalculateCharWidth || ta.data("average_char") == null) {
    // Get a pretty good estimation of the width of a character in the textarea. To get a better average, add more characters and symbols to this list
    var chars = options.chars;

    var charLen = chars.length;
    var totalWidth = 0;

    $.each(chars, function(i, v){
  span.text(v);
  totalWidth += span.width();
    });

    // Store average width on textarea
    ta.data("average_char", Math.ceil(totalWidth / charLen));
}

averageWidth = ta.data("average_char");

// We are done with the span, so kill it
span.remove();

// Determine missing width (from padding, margins, borders, etc); this is what we will add to each line width
var missingWidth = (ta.outerWidth() - ta.width()) * 2;

// Calculate the number of lines that occupy more than one line
var lineWidth;

var wrappingLines = 0;
var wrappingCount = 0;
var blankLines = 0;

$.each(lines, function(i, v){
    // Calculate width of line
    lineWidth = ((v.length + 1) * averageWidth) + missingWidth;
    // Check if the line is wrapped
    if (lineWidth >= ta.outerWidth()){
  // Calculate number of times the line wraps
  var wrapCount = Math.floor(lineWidth / ta.outerWidth());
  wrappingCount += wrapCount;
  wrappingLines++;
    }

    if ($.trim(value) == "")
  blankLines++;
});

var ret = {};
ret["actual"] = linesLen;
ret["wrapped"] = wrappingLines;
ret["wraps"] = wrappingCount;
ret["visual"] = linesLen + wrappingCount;
ret["blank"] = blankLines;

return ret;
  };






})(jQuery);


