(function($) {
	/** html_entity_decode
	  * parameters: quote style (see translate arguments in get_html_translation_table below)
	  *
	  * returns an html block with entities decoded
	*/
	
	String.prototype.html_entity_decode = function(quote_style) {
		
		/** get_html_translation_table
		  * parameters: table (must be HTML_ENTITIES), quote_style
		  *
		  * returns the internal translation table used by htmlToPlain
		  * analagous to its PHP namesake. (HTML_SPECIALCHARS table stripped)
		*/
		
		function get_html_translation_table(table, quote_style) {

		    var entities = {}, hash_map = {}, decimal = 0, symbol = '';
		    var constMappingTable = {}, constMappingQuoteStyle = {};
		    var useTable = {}, useQuoteStyle = {};

		    // Translate arguments
		    constMappingTable[0]      = 'HTML_SPECIALCHARS';
		    constMappingTable[1]      = 'HTML_ENTITIES';
		    constMappingQuoteStyle[0] = 'ENT_NOQUOTES';
		    constMappingQuoteStyle[2] = 'ENT_COMPAT';
		    constMappingQuoteStyle[3] = 'ENT_QUOTES';

		    useTable       = !isNaN(table) ? constMappingTable[table] : table ? table.toUpperCase() : 'HTML_SPECIALCHARS';
		    useQuoteStyle = !isNaN(quote_style) ? constMappingQuoteStyle[quote_style] : quote_style ? quote_style.toUpperCase() : 'ENT_COMPAT';

		    if (useTable !== 'HTML_SPECIALCHARS' && useTable !== 'HTML_ENTITIES') {
		        throw new Error("Table: "+useTable+' not supported');
		        // return false;
		    }

		    entities['38'] = '&amp;';
		    if (useTable === 'HTML_ENTITIES') {
		        entities['160'] = '&nbsp;';
		        entities['161'] = '&iexcl;';
		        entities['162'] = '&cent;';
		        entities['163'] = '&pound;';
		        entities['164'] = '&curren;';
		        entities['165'] = '&yen;';
		        entities['166'] = '&brvbar;';
		        entities['167'] = '&sect;';
		        entities['168'] = '&uml;';
		        entities['169'] = '&copy;';
		        entities['170'] = '&ordf;';
		        entities['171'] = '&laquo;';
		        entities['172'] = '&not;';
		        entities['173'] = '&shy;';
		        entities['174'] = '&reg;';
		        entities['175'] = '&macr;';
		        entities['176'] = '&deg;';
		        entities['177'] = '&plusmn;';
		        entities['178'] = '&sup2;';
		        entities['179'] = '&sup3;';
		        entities['180'] = '&acute;';
		        entities['181'] = '&micro;';
		        entities['182'] = '&para;';
		        entities['183'] = '&middot;';
		        entities['184'] = '&cedil;';
		        entities['185'] = '&sup1;';
		        entities['186'] = '&ordm;';
		        entities['187'] = '&raquo;';
		        entities['188'] = '&frac14;';
		        entities['189'] = '&frac12;';
		        entities['190'] = '&frac34;';
		        entities['191'] = '&iquest;';
		        entities['192'] = '&Agrave;';
		        entities['193'] = '&Aacute;';
		        entities['194'] = '&Acirc;';
		        entities['195'] = '&Atilde;';
		        entities['196'] = '&Auml;';
		        entities['197'] = '&Aring;';
		        entities['198'] = '&AElig;';
		        entities['199'] = '&Ccedil;';
		        entities['200'] = '&Egrave;';
		        entities['201'] = '&Eacute;';
		        entities['202'] = '&Ecirc;';
		        entities['203'] = '&Euml;';
		        entities['204'] = '&Igrave;';
		        entities['205'] = '&Iacute;';
		        entities['206'] = '&Icirc;';
		        entities['207'] = '&Iuml;';
		        entities['208'] = '&ETH;';
		        entities['209'] = '&Ntilde;';
		        entities['210'] = '&Ograve;';
		        entities['211'] = '&Oacute;';
		        entities['212'] = '&Ocirc;';
		        entities['213'] = '&Otilde;';
		        entities['214'] = '&Ouml;';
		        entities['215'] = '&times;';
		        entities['216'] = '&Oslash;';
		        entities['217'] = '&Ugrave;';
		        entities['218'] = '&Uacute;';
		        entities['219'] = '&Ucirc;';
		        entities['220'] = '&Uuml;';
		        entities['221'] = '&Yacute;';
		        entities['222'] = '&THORN;';
		        entities['223'] = '&szlig;';
		        entities['224'] = '&agrave;';
		        entities['225'] = '&aacute;';
		        entities['226'] = '&acirc;';
		        entities['227'] = '&atilde;';
		        entities['228'] = '&auml;';
		        entities['229'] = '&aring;';
		        entities['230'] = '&aelig;';
		        entities['231'] = '&ccedil;';
		        entities['232'] = '&egrave;';
		        entities['233'] = '&eacute;';
		        entities['234'] = '&ecirc;';
		        entities['235'] = '&euml;';
		        entities['236'] = '&igrave;';
		        entities['237'] = '&iacute;';
		        entities['238'] = '&icirc;';
		        entities['239'] = '&iuml;';
		        entities['240'] = '&eth;';
		        entities['241'] = '&ntilde;';
		        entities['242'] = '&ograve;';
		        entities['243'] = '&oacute;';
		        entities['244'] = '&ocirc;';
		        entities['245'] = '&otilde;';
		        entities['246'] = '&ouml;';
		        entities['247'] = '&divide;';
		        entities['248'] = '&oslash;';
		        entities['249'] = '&ugrave;';
		        entities['250'] = '&uacute;';
		        entities['251'] = '&ucirc;';
		        entities['252'] = '&uuml;';
		        entities['253'] = '&yacute;';
		        entities['254'] = '&thorn;';
		        entities['255'] = '&yuml;';
		    }

		    if (useQuoteStyle !== 'ENT_NOQUOTES') {
		        entities['34'] = '&quot;';
		    }
		    if (useQuoteStyle === 'ENT_QUOTES') {
		        entities['39'] = '&#39;';
		    }
		    entities['60'] = '&lt;';
		    entities['62'] = '&gt;';


		    // ascii decimals to real symbols
		    for (decimal in entities) {
		        symbol = String.fromCharCode(decimal);
		        hash_map[symbol] = entities[decimal];
		    }

		    return hash_map;
		}
		
		// Convert all HTML entities to their applicable characters  

	    var histogram = {}, symbol = '', tmp_str = '', entity = '';
	    tmp_str = this.toString();

	    if (false === (histogram = get_html_translation_table('HTML_ENTITIES', quote_style))) {
	        return false;
	    }

	    // &amp; must be the last character when decoding!
	    delete(histogram['&']);
	    histogram['&'] = '&amp;';

	    for (symbol in histogram) {
	        entity = histogram[symbol];
	        tmp_str = tmp_str.split(entity).join(symbol);
	    }

	    return tmp_str;
	}
	
	
	String.prototype.urlize = function(options) {
	  var options = $.extend({
			separator: '-'
		}, options);
	  var string = this;
    string = string.toLowerCase();
    string = string.replace(/[^a-zA-Z0-9\ \-\_]+/g, '');
    string = string.replace(/[\-\_\s]+/g, options.separator);
    return string;
	}
	
	/** htmlToPlain
	  * parameters: none
	  *
	  * converts an HTML string to plaintext for character counting
	*/

	String.prototype.htmlToPlain = function() {
		// used exclusively for character count checking -- needs additional logic for 
		// proper html to plaintext conversion
	
		var text = this;

		text = text.replace(/<\S[^><]*>/g, ""); // first strip tags
		text = text.html_entity_decode(); // now decode entities
  
		// now strip invisible characters : line feeds, carriage returns and tabs
		
		text = text.replace(/\r|\n|\t/g, ""); // 
		 
		return text;
	}
	
	/** clickToSelect
	  * parameters: none
	  *
	  * hooks a field so that when clicked, all text is selected (used by share for embed code)
	*/

	$.fn.clickToSelect = function() {
		$(this).each(function() {
			var item = $(this);
			item.bind( "click" , function(){  		  
				if(document.createRange) {
					// Firefox & WebKit
					var range = document.createRange();
					range.selectNodeContents(item.get(0));
					var curSelect = window.getSelection();
					curSelect.addRange(range);
					return false;
				} else if(document.body && document.body.createTextRange ) {
					// MSIE
					var range = document.body.createTextRange();
					range.moveToElementText(item.get(0));
					range.select();
					return false;
				}
			});
		});
		return $(this);
	};

	/** pluralize
	  * parameters: options
	  *
	  * DEPRECATED (currently unused)
	*/

	$.fn.pluralize = function(options) {
		var options = $.extend({
			addedClass: "added",
			addButton: "<a>Add</a>",
			removeButton: "<a>Remove</a>",
			addButtonClass: "add-button",
			removeButtonClass: "remove-button",
			max: 10
		}, options);

		$(this).each(function() {
			var field = $(this);
			var defaultValue = (typeof options.defaultValue == "undefined") ? field.val() : options.defaultValue;

			var initField = function(field) {
				var wrapper = field.wrap($("<div/>")).parent();
				field.data("removeButton", $(options.removeButton)
					.addClass(options.removeButtonClass)
					.appendTo(wrapper)
					.click(function() {
						var prev = field.data("prev"), next = field.data("next");
						if(prev) {
							if(next) {
								prev.data("next", next);
							} else {
								prev.removeData("next");
							}
						} else {
							if(!next.data("next")) {
								next.data("removeButton").hide();
							}
						}
						if(next) {
							if(prev) {
								next.data("prev", prev);
							} else {
								next.removeData("prev");
							}
						} else {
							prev.data("addButton").show();
							if(!prev.data("prev")) {
								prev.data("removeButton").hide();
							}
						}
						wrapper.remove();
					})
				);
				field.data("addButton", $(options.addButton)
					.addClass(options.addButtonClass)
					.appendTo(wrapper)
					.click(function() {
						var added = field.clone().insertAfter(wrapper);
						initField(added);
						added.data("prev", field);
						field.data("next", added);
						field.data("removeButton").show();
						field.data("addButton").hide();
					})
				);
				field.val(defaultValue);
			};

			initField(field);
			field.data("removeButton").hide();
		});

		return $(this);
	};

	/** invalid
	  * parameters: options
	  *
	  * basic field validation. forms of validation are added to options, along with 
	  * error messages (defaults are provided)
	*/
	
	$.fn.invalid = function(options) {
		var options = $.extend({
			messages: {}
		}, options);


		var value = $(this).val();
		var valLength;
		
		if (value.htmlToPlain) valLength = value.htmlToPlain().length;
		else valLength = value.length;
				
		var error = false;
		if(options.presence && !value) {
			error = options.messages.presence || "This field may not be left blank."
		} else if (options.minLength && value && valLength < options.minLength) {
			error = options.messages.minLength || "This field must have at least " + options.minLength + " characters."
		} else if (options.maxLength && value && valLength > options.maxLength) {
			error = options.messages.maxLength || "This field is limited to " + options.maxLength + " characters."
		} else if (options.match && value && !value.match(options.match)) {
			error = options.messages.match || "Invalid parameter";
		} else if (options.doubleCheck && value != $(options.doubleCheck).val()) {
			error = options.messages.doubleCheck || "Mismatch parameter";
		} else if (options.func) {
			error = options.func.call(this, value);
		}
		return error;
	};

	/** validate
	  * parameters: fields to validate, options
	  *
	  * validates a form by using $.fn.invalid on the passed fields. selectors
	  * and objects with validation key value-pairs are passed themselves 
	  * as key-value pairs through options
	*/

	$.fn.validate = function(fields, options) {
		var options = $.extend({
			status: true,
			statusClass: "status",
			notificationClass: "notification"
		}, options);

		var invalid = false;
		$(this).each(function() {
			var form = $(this);
			$.each(fields, function(key, val) {
				form.find(key).each(function() {
					var field = $(this);
					var status = field.data('validate_status') || val.status || options.status;
					if(status && !status.jquery) {
						status = (typeof status == "boolean") ?
							$("<div/>").addClass(val.statusClass || options.statusClass).insertAfter(field) :
							$(status);
						field.data('validate_status', status);
					}
					var error = field.invalid(val);
					if(error) {
						status.text(error);
						field.addClass(val.notificationClass || options.notificationClass);
					} else {
						status.html("");
						field.removeClass(val.notificationClass || options.notificationClass);
					}
					invalid |= !!error;
				});
			});
		});
		return !invalid;
	};

	/**
	  * jQuery Maxlength plugin 1.0.1
	  *
	  * http://www.anon-design.se
	  *
	  * Copyright (c) 2008 Emil Stjerneman <emil@anon-design.se>
	  *
	  * Dual licensed under the MIT and GPL licenses:
	  * http://www.opensource.org/licenses/mit-license.php
	  * http://www.gnu.org/licenses/gpl.html
	  */
	$.fn.maxLength = function(options) {
		var options = jQuery.extend({
			// element to show status indicator bewlow the element or boolean
			status: true,
			statusClass: "lengthStatus",
			// the status text
			statusText: "characters left",
			statusTextWithSingleCharacter: "character left",
			// Will be added to the emement when maxLength is reached
			notificationClass: "lengthNotification"
		}, options );

		$(this).each(function() {
			var item = $(this);
			var maxLength = options.maxLength || $(this).attr("maxlength") || 10;
			var status = false;

			function charactersLength() {
				if(options.ignoreClass && item.is("."+options.ignoreClass)) {
					return 0;
				}

				var text = "";
				if ( (options.tinymceEditor) && (options.tinymceEditor.id == item.attr("id")) ){
					try {
						// we have to strip our tags and de-htmlentitify
						
						text = options.tinymceEditor.getContent({format: 'raw'});
						
					} catch(e) {
						// tinyMCE hasn't been initialized.
						text = item.val();
					}
					text = text.htmlToPlain();
				} else {
					text = item.val();
				}
								
				
				return (options.ignoreText && text == options.ignoreText) ? 0 : text.length;
			}

			var updateStatus = function() {
				if(status) {
					var charactersLeft = maxLength - charactersLength();
					/* Allow for negative count
					if(charactersLeft < 0) {
						charactersLeft = 0;
					}
					*/
					status.html(charactersLeft + " " + (charactersLeft == 1 ? options.statusTextWithSingleCharacter : options.statusText));
					if(charactersLeft < 0) {
					  status.addClass('error');
					}
					else {
					  status.removeClass('error');
					}
				}
			};


			var checkChars = function() {
				var container = (options.tinymceEditor && (options.tinymceEditor.id == item.attr("id")) ) ? $(options.tinymceEditor.getContainer()) : item;
				// Too many chars?
				if(charactersLength() >= maxLength) {
					// Add the notifycation class when we have to many chars
					container.addClass(options.notificationClass);
					// Cut down the string
					if(options.tinymceEditor && (options.tinymceEditor.id == item.attr("id")) ) {
          
          } else {
						item.val(item.val().substr(0, maxLength));
					}
				} else {
					// Remove the notification class
					if(container.hasClass(options.notificationClass)) {
						container.removeClass(options.notificationClass);
					}
				}
				updateStatus();
			};

			if(options.status) {
				status = (typeof options.status == "boolean") ?
					$("<div/>").addClass(options.statusClass).insertAfter(item) : $(options.status);
				updateStatus();
			}

			if (options.tinymceEditor && (options.tinymceEditor.id == item.attr("id")) ) {
				options.tinymceEditor.onChange.add(function(ed, e) {
					checkChars();
				});
				options.tinymceEditor.onKeyUp.add(function(ed, e) {
					checkChars();
				});
			} else {
				item.keyup(function(e) {
					checkChars();
				});
			}
		});

		return $(this);
	};
	
	tinymce_caption_encode = function(co) {
	  return co.replace(/\[caption([^\]]+)\]([\s\S]+?)\[\/caption\][\s\u00a0]*/g, function(a,b,c){
      var id, cls, w, cap, div_cls;

      b = b.replace(/\\'|\\&#39;|\\&#039;/g, '&#39;').replace(/\\"|\\&quot;/g, '&quot;');
      c = c.replace(/\\&#39;|\\&#039;/g, '&#39;').replace(/\\&quot;/g, '&quot;');
      cls = b.match(/align=['"]([^'"]+)/i);
      w = b.match(/width=['"]([0-9]+)/);
      cap = b.match(/caption=['"]([^'"]+)/i);

      cls = ( cls && cls[1] ) ? cls[1] : 'alignnone';
      w = ( w && w[1] ) ? w[1] : '';
      cap = ( cap && cap[1] ) ? cap[1] : '';
      if ( ! w || ! cap ) return c;

      div_cls = (cls == 'aligncenter') ? 'mceTemp mceIEcenter' : 'mceTemp';

      return '<div class="'+div_cls+'"><dl id="'+id+'" class="caption '+cls+'" style="width: '+(10+parseInt(w))+
      'px"><dt class="caption-dt">'+c+'</dt><dd class="caption-dd">'+cap+'</dd></dl></div>';
    });
	}
	
	tinymce_caption_decode = function(co){
	  return co.replace(/<div class="mceTemp[^"]*">\s*<dl([^>]+)>\s*<dt[^>]+>([\s\S]+?)<\/dt>\s*<dd[^>]+>(.+?)<\/dd>\s*<\/dl>\s*<\/div>\s*/gi, function(a,b,c,cap){
			var id, cls, w;

			id = b.match(/id=['"]([^'"]+)/i);
			cls = b.match(/class=['"]([^'"]+)/i);
			w = c.match(/width=['"]([0-9]+)/);

			cls = ( cls && cls[1] ) ? cls[1] : 'alignnone';
			w = ( w && w[1] ) ? w[1] : '';

			if ( ! w || ! cap ) return c;
			cls = cls.match(/align[^ '"]+/) || 'alignnone';
			cap = cap.replace(/<\S[^<>]*>/gi, '').replace(/'/g, '&#39;').replace(/"/g, '&quot;');

			return '[caption align="'+cls+'" width="'+w+'" caption="'+cap+'"]'+c+'[/caption]';
		});
	}
	
	/**
	  */
	$.fn.tinymceEditor = function(options, menu) {
		var options = jQuery.extend({
			height: null,
			maxLength: 1500,
			plugins: 'safari',
			buttons: 'bold,italic,|,bullist,numlist,|,blockquote,|,link-form',
			valid_elements: 'a[href|title],strong/b,em/i,blockquote,ol,ul,li,p,br'
		}, options );
		
    if (menu == undefined) {
      var menu = $("#node-edit-compose-link")
      .appendTo("body").show()
      .dropdown({
        trigger: false,
        leftOffset: false,
        topOffset: false,
        zIndex: 1100,
        close: false,
        closeByAnchorClick: false,
        onopen: function(){
          if( $.browser.msie ) this.find(".title").css("position","relative");
        }
      });
      menu.find("input[name=source]")
      .change(function() {
        var names = {"url": false, "sharelist": false};
        names[$(this).val()] = true;
        $.each(names, function(name, enable) {
          menu.find("[name=" + name + "]:first").attr("disabled", enable ? "" : "disabled");
        });
      });
      menu.find("form:first")
      .submit(function() {
        var url, title;
        switch(menu.find("input[name=source]:checked").val()) {
          case "url":
          title = url = $(this).find("input[name=url]").val();
          break;
          case "friends_sharelist":
          var option = menu.find("select[name=friends_sharelist] option:selected");
          url = option.val();
          title = option.text();
          break;
        }
        if(typeof tinyMCE != 'undefined') {
          var ed = tinyMCE.activeEditor;
          if(ed) {
            var selection = ed.selection.getContent({format : 'text'});
            if (selection) {
              var html = $("<div/>").append($("<a/>").attr("href", url).text(selection)).html();              
            }
            else {
              var selection = ed.selection.getContent();
              if (selection) {
                var html = $("<div/>").append($("<a/>").attr("href", url).html(selection)).html();
              }
              else {
                var html = $("<div/>").append($("<a/>").attr("href", url).text(title)).html();
              }
            }
            ed.execCommand("mceInsertContent", false, html);
          }
        }
        menu.trigger("close");
        return false;
      })
      .find(".form-cancel").click(function() {
        menu.trigger("close");
        return false;
      });
    }

    $(this).each(function() {
      var textarea = $(this),
      setup = {
        script_url : Drupal.settings.tinymcePath,
        theme: "advanced",
        plugins: options.plugins,
        theme_advanced_buttons1: options.buttons,
        theme_advanced_buttons2: "",
        theme_advanced_toolbar_location: "top",
        theme_advanced_toolbar_align: "left",
        theme_advanced_resizing_min_height: options.height,
        convert_urls: false,
        relative_urls: false,
        paste_auto_cleanup_on_paste: true,
				paste_strip_class_attributes: "all",
				paste_remove_spans: true,
				paste_remove_styles: true,
        pagebreak_separator: '<!--break-->',
        setup: function(ed) {
          ed.onBeforeSetContent.add(function(ed, o) {
            o.content = tinymce_caption_encode(o.content);
          });
          
          ed.onPostProcess.add(function(ed, o) {
            if (o.get) {
              o.content = tinymce_caption_decode(o.content);
            }
          });

          ed.addButton("link-form", {
            title: "Link",
            "class": "mceIcon mce_link",
            onclick: function(e) {
              var offset = $(e.target).offset();
              ed.focus(true);
              menu.css({left: (offset.left - 15) + "px", top: (offset.top + 24) + "px"});
              menu.trigger("open");
            }
          });
          if (options.addbuttons) {
            for (var i in options.addbuttons) {
              var button = options.addbuttons[i];
              ed.addButton(button.name, {
                title: button.title,
                "class": button.classes,
                onclick: button.onclick
              });
            }
          }
          if (options.maxLength > 0) {
            textarea.maxLength({
              tinymceEditor: ed,
              statusClass: "lengthStatus",
              notificationClass: "lengthNotification",
              maxLength: options.maxLength
            });
          }
        }
      };
      if (Drupal.settings.tinymceCSS) setup['content_css'] = Drupal.settings.tinymceCSS+'?'+ new Date().getTime();
      if (options.valid_elements) setup['valid_elements'] = options.valid_elements;
      if (options.blockformats) setup['theme_advanced_blockformats'] = options.blockformats;
      if (options.height) setup['height'] = options.height;
      if (options.width) setup['width'] = options.width;
      
      textarea.tinymce(setup);
    });

    return $(this);
  };
	
})(jQuery);
