/*
  @author: remy sharp / http://remysharp.com
  @params:
    feedback - the selector for the element that gives the user feedback. Note that this will be relative to the form the plugin is run against.
    hardLimit - whether to stop the user being able to keep adding characters. Defaults to true.
    useInput - whether to look for a hidden input named 'maxlength' instead of the maxlength attribute. Defaults to false.
    words - limit by characters or words, set this to true to limit by words. Defaults to false.
  @license: Creative Commons License - ShareAlike http://creativecommons.org/licenses/by-sa/3.0/
  @version: 1.2
  @changes: code tidy via Ariel Flesler and fix when pasting over limit and including \t or \n
*/

(function (jQuery) {

jQuery.fn.maxlength = function (settings) {

    if (typeof settings == 'string') {
        settings = { feedback : settings };
    }

    settings = jQuery.extend({}, jQuery.fn.maxlength.defaults, settings);

    function length(el) {
    	var parts = el.value;
    	if ( settings.words ){
    		parts = el.value.length ? parts.split(/\s+/) : { length : 0 };
		return parts.length;
	}else{
		return(lengthWithNL_CR(parts));
	}
    }

    return this.each(function () {
        var field = this,
        	$field = jQuery(field),
        	$form = jQuery(field.form),
        	limit = settings.useInput ? $form.find('input[name=maxlength]').val() : $field.attr('maxlength'),
        	$charsLeft = $form.find(settings.feedback);

		if(limit && (limit<0))limit=null;

    	function limitCheck(event) {
        	var len = length(this),
        	    exceeded = (len >= limit),
        		code = event.keyCode;

        	if ( !exceeded )
        		return;

            switch (code) {
                case 8:  // Backspace
                case 46: // Delete
 
		case 16: // Shift
                case 17: // Ctrl
		case 18: // Alt

                case 33: // Page Up
                case 34: // Page Down

                case 36: // Home
                case 35: // End

                case 37: // cursor
                case 38: // cursor
                case 39: // cursor
                case 40: // cursor
                return;

                default:
                    return settings.words && code != 32 && code != 13 && len == limit;
            }
        }


        var updateCount = function() {
	if(limit && (limit>0)){
            var len = length(field),
            	diff = limit - len;

            $charsLeft.html( diff || "0" );

/*var res='',a;
for(a=0;a<field.value.length;a++)res+=field.value.charCodeAt(a)+', ';
$charsLeft.html( $charsLeft.html()+' ('+field.value.length+':'+len+') = '+ res);*/

            // truncation code
            if (settings.hardLimit && (diff < 0)) {
            	field.value = settings.words ? 
            	    // split by white space, capturing it in the result, then glue them back
            		field.value.split(/(\s+)/, (limit*2)-1).join('') :
            		substrWithNL_CR(field.value, limit);
	        updateCount();
            }
	}
        };

        $field.keyup(updateCount).change(updateCount);
        if (settings.hardLimit) {$field.keydown(limitCheck);}
        updateCount();
    });
};

jQuery.fn.maxlength.defaults = {
    useInput : false,
    hardLimit : true,
    feedback : '.charsLeft',
    words : false
};

})(jQuery);

    function lengthWithNL_CR(str){
		var len=0,a;
		for(a=0;a<str.length;a++){
			switch(str.charCodeAt(a)){
				case 10:
					len+=2;
				break;
				case 13:
					if((a<(str.length-1)) && (str.charCodeAt(a+1)==10)){continue;}else{len++;}
				break;
				default: 
					len++;
				break;
			}
		}
		return(len);
    }

	function substrWithNL_CR(str, length){
	var res='',a,cl,len=0;
	for(a=0;a<str.length;a++){
	switch(str.charCodeAt(a)){
		case 10: 
			cl=2;
		break;
		case 13:
			if((a<(str.length-1)) && (str.charCodeAt(a+1)==10)){if(len+2<=length){res+=str.charAt(a);}continue;}
		break;
		default: cl=1;
	}
	if(len+cl<=length){res+=str.charAt(a);len+=cl;}

	}
	//alert(str.length+' '+res.length+' '+len+': "'+res+'"');
	return(res);
	}
