(function($){

	$.fn.wysiwyg = function(settings) {
		var defaults = {
			skin     : 'default',
			smilies  : [],
			controls : [
				'bold,italic,underline'
			],
			// minimum widths and height are for the text editing area
			width       : null,
			height      : null,
			min_width   : 150,
			min_height  : 40,
			max_width   : 800,
			max_height  : 600,
			resizable   : false,
			show_footer : false,
			key_funcs   : null,
			scrolling   : true
		};

		settings = $.extend(defaults, settings);

		return this.each(function() {
			Wysiwyg(this, settings);
		});
	};

	function Wysiwyg(element, settings)
	{
		return this instanceof Wysiwyg ?
			this.init(element, settings) :
			new Wysiwyg(element, settings);
	}
	
	$.extend(Wysiwyg.prototype, {
		init : function(element, settings) {
			if(element.nodeName.toLowerCase() != 'textarea')
				return;

			var self = this;
			this.settings = settings;
			
			this.editor = element;

			// get the element's width and height
			var width  = this.settings.width || $(element).width();
			var height = this.settings.height || $(element).height();

			// store the textarea element
			this.textarea = element;

			this.cpanel = $('<ul></ul>')
				.addClass('cpanel');

			// create the editor frame
			this.editor	= $('<iframe frameborder="0"'+(!this.settings.scrolling?' scrolling="no"':'')+'></iframe>')
				.width(width)
				.height(height)
				.css({overflow:'auto'})
				.attr('id', $(element).attr('id')+'_iframe');
			
			// create overlay to cover entire wysiwyg
			this.editor_overlay = $('<div></div>')
				.css({display:'none',position:'absolute',top:'0px',left:'0px',opacity:0.0,background:'#000'});

			self.data = {};
			self.data.mouse   = {};
			self.data.element = {};
			self.data.editor  = {};
			this.resizing = false;
			this.resizer = $('<div></div>')
				.addClass('resizer')
				.css({cursor:'nw-resize'})
				.bind('mousedown', function(e) {
					self.data.mouse.start_x = mX;
					self.data.mouse.start_y = mY;
					self.data.element.start_width  = $(self.element).width();
					self.data.element.start_height = $(self.element).height();
					self.data.editor.start_width   = $(self.editor).width();
					self.data.editor.start_height  = $(self.editor).height();
					$(document.body).css({cursor: 'nw-resize'});
					$(document).bind('mousemove', {self: self}, self.do_resize);
					$(document).bind('mouseup',   {self: self}, self.stop_resize);

					$(self.editor_overlay).show();
					return false;
				})
				.bind('mouseup', function(e) {
					self.resizing = false;
				});

			if(!this.settings.resizable)
			{
				this.resizer
					.css({display : 'none'});
			}

			this.footer = $('<div></div>')
				.addClass('footer')
				.append(this.resizer);

			if(!this.settings.show_footer)
			{
				this.footer
					.css({display : 'none'});
			}

			// recreate the element
			this.element = $('<div></div>')
				.width(width)
				.addClass('wysiwyg')
				.css({position:'relative'})
				.append(this.cpanel)
				.append($('<div><!-- --></div>').css({clear: 'both'}))
				.append(this.editor)
				.append(this.editor_overlay)
				.append($('<div><!-- --></div>').css({clear: 'both'}))
				.append(this.footer)
				.bind('mousedown',function(e) {
					return false;
				});

			this.controls          = {};
			this.control_shortcuts = {};
			this.build_controls();

			// hide the old element and add the new
			$(element)
				.hide()
				.before(this.element);

			// init the frame
			this.init_frame(element.innerHTML);
		},

		do_resize : function(e) {
			var self = e.data.self;
			
			var change_x = mX - self.data.mouse.start_x;
			var change_y = mY - self.data.mouse.start_y;
			// change element
			var new_width  = self.data.element.start_width  + change_x;
			var new_height = self.data.element.start_height + change_y;
			// width restraint
			if(new_width < self.settings.min_width)
			{
				new_width = self.settings.min_width;
			}
			else if(new_width > self.settings.max_width)
			{
				new_width = self.settings.max_width;
			}
			// height restraint
			if(new_height < self.settings.min_height+$(self.footer).height()+$(self.cpanel).height())
			{
				new_height = self.settings.min_height+$(self.footer).height()+$(self.cpanel).height();
			}
			else if(new_height > self.settings.max_height+$(self.footer).height()+$(self.cpanel).height())
			{
				new_height = self.settings.max_height+$(self.footer).height()+$(self.cpanel).height();
			}
			$(self.element).width(new_width);
			$(self.element).height(new_height);
			// overlay
			$(self.editor_overlay).width(new_width);
			$(self.editor_overlay).height(new_height);

			// change editor
			var new_width  = self.data.editor.start_width  + change_x;
			var new_height = self.data.editor.start_height + change_y;
			// width restraint
			if(new_width < self.settings.min_width)
			{
				new_width = self.settings.min_width;
			}
			else if(new_width > self.settings.max_width)
			{
				new_width = self.settings.max_width;
			}
			// accommodate for 1px border in cpanel and footer
			if(new_height < self.settings.min_height-$.parse_int($(self.cpanel).css('borderBottomWidth'))-$.parse_int($(self.footer).css('borderTopWidth')))
			{
				new_height = self.settings.min_height-$.parse_int($(self.cpanel).css('borderBottomWidth'))-$.parse_int($(self.footer).css('borderTopWidth'));
			}
			else if(new_height > self.settings.max_height-$.parse_int($(self.cpanel).css('borderBottomWidth'))-$.parse_int($(self.footer).css('borderTopWidth')))
			{
				new_height = self.settings.max_height-$.parse_int($(self.cpanel).css('borderBottomWidth'))-$.parse_int($(self.footer).css('borderTopWidth'));
			}
			$(self.editor).width(new_width);
			$(self.editor).height(new_height);
		},

		stop_resize : function(e) {
			var self = e.data.self;

			$(document.body).css({cursor: 'default'});
			$(document).unbind('mousemove', self.do_resize);
			$(document).unbind('mouseup',   self.stop_resize);

			$(self.editor_overlay).hide();
		},

		init_frame : function(content)
		{
			if(!$.isIE())
			{
				content += '<br />';
			}
			
			var self = this;

			this.editor_doc  = $(this.editor)[0].contentWindow.document;

			this.editor_doc.designMode = 'on';

			// write the original content
			this.editor_doc.open();
			this.editor_doc.write('<html><body>'+content+'</body></html>');
			this.editor_doc.close();

			$(this.editor_doc).find('body').css({cursor:'text', margin:'0px', padding:'10px'});
			if(!this.settings.scrolling)
			{
				$(this.editor_doc).find('body').css({whiteSpace:'nowrap'});
			}
			if(!$.isIE())
			{
				this.editor_doc.execCommand('styleWithCSS', false, false);
			}

			// enable functionality for Ctrl + ? shortcut commands
			this.data.is_ctrl = false;
			$(this.editor_doc)
				.bind('keyup keydown', function(e) {
					self.process_keystroke(e);
				});

			// menu highlights when caret is in affected areas
			$(this.editor_doc)
				.bind('keyup click', function(e) {
					self.move_caret(e);
					self.highlight_controls(e);
				});
		},

		get_sel: function() {
			return $(this.editor)[0].contentWindow.getSelection ? $(this.editor)[0].contentWindow.getSelection() : $(this.editor)[0].contentWindow.document.selection;
		},
		
		move_caret : function(e) {
			var sel   = this.get_sel();
			var range = this.get_range();

			// only move caret if nothing is selected
			if(($.isIE() && range.boundingWidth > 0) || (!$.isIE() && !range.collapsed))
			{
				return;
			}

			// determine target
			var target;
			if($.isIE())
			{
				target = range.parentElement();
			}
			// curtail behavior so that when we are at the beginning
			// of one node and at the end of a previous node, we
			// automatically focus on the end of the previous node
			//   example: [i]hi[/i]|there => [i]hi|[/i]there
			// in the above example, newly typed text will be italic
			// rather than normal
			// IE does this already
			else
			{
				target = range.endContainer;
				
				// if target is text node, examine previous node
				if(target.nodeName == '#text')
				{
					var prevNode = target.previousSibling;
					
					// if the previous node is not null, keep mining
					// until we find the last child down the entire tree
					if(prevNode != null)
					{
						while(prevNode.childNodes.length > 0)
						{
							prevNode = prevNode.lastChild;
						}
					}
					// otherwise, we must examine the parent node and its previous node
					else
					{
						var parentNode = target.parentNode;
						// do nothing if the parent node is the BODY tag
						// there is no previous node
						if(parentNode.nodeName != 'BODY')
						{
							// we must now examine the parent node's previous node
							var prevNode = parentNode.previousSibling;
							// if the previous node is not null, keep mining
							// until we find the last child down the entire tree
							if(prevNode != null)
							{
								while(prevNode.childNodes.length > 0)
								{
									prevNode = prevNode.lastChild;
								}
							}
						}
					}
				}

				// if we are at the very beginning of a node, try
				// to go to the very end of the previous node
				if(prevNode != null && range.startOffset == 0)
				{
					range.setStart(prevNode, 0);
					range.setEnd(prevNode, prevNode.length);
					sel.removeAllRanges();
					sel.addRange(range);
					sel.collapseToEnd();
				}
			}
		},

		get_range : function() {
			var r, sel = this.get_sel();
			try {
				r = (sel.rangeCount > 0) ? sel.getRangeAt(0) : (sel.createRange ? sel.createRange() : this.editor_doc.createRange());
			} catch(ex) {}

			try {
				if(!r)
				{
					r = $.isIE() ? this.editor_doc.body.createTextRange() : this.editor_doc.createRange();
				}
			} catch(ex) {}
			
			return r;
		},
		
		process_keystroke : function(e) {
			if(!e) { // ie
				keycode = window.event.keyCode;
				escapeKey = 27;
				deleteKey = 46;
			} else { // mozilla
				keycode = e.keyCode;
				if(typeof e.DOM_VK_ESCAPE != 'undefined') {
					escapeKey = e.DOM_VK_ESCAPE;
					deleteKey = e.DOM_VK_DELETE;
				} else {
					escapeKey = 27;
					deleteKey = 46;
				}
			}

			enterKey = 13;
			upKey    = 38;
			downKey  = 40;
			spaceKey = 32;
			ctrlKey  = 17;
			
			if(keycode == ctrlKey)
			{
				if(e.type == 'keyup')
				{
					this.data.is_ctrl = false;
				}
				else if(e.type == 'keydown')
				{
					this.data.is_ctrl = true;
				}
			}

			var letter = String.fromCharCode(keycode).toLowerCase();
			
			if(this.settings.key_funcs != null && !this.data.is_ctrl && e.type == 'keydown')
			{
				for(key in this.settings.key_funcs)
				{
					// if correct key is pressed, execute the function
					if((key == 'enter' && keycode == enterKey) || key == letter)
					{
						var func      = this.settings.key_funcs[key].func;
						var ubbc_tags = this.settings.key_funcs[key].ubbc_tags || [];
						var clear     = this.settings.key_funcs[key].clear || false;
						// execute func
						var content = this.get_content(ubbc_tags);
						if(content != '')
						{
							func.apply(this, [content]);
							if(clear)
							{
								if($.isIE())
								{
									$(this.editor_doc).find('body').html('');
								}
								else
								{
									$(this.editor_doc).find('body').html('<br />');
								}
							}
						}

						// refocus
						this.editor_doc.body.focus();

						// disable default behavior
						$.disable_event(e);
						return false;
					}
				}
			}

			if(this.data.is_ctrl && e.type == 'keydown')
			{
				var name = this.control_shortcuts[letter] || null;
				if(name)
				{
					this.exec_command(name);
				}
				// disable default behavior
				$.disable_event(e);
				return false;
			}
		},

		get_content : function(ubbc_tags) {
			var ubbc_tags = ubbc_tags || [];
			var content = $(this.editor_doc).find('body').html();

			// make &nbsp; into spaces
			content = content.replace(/&nbsp;/,' ');

			if(ubbc_tags.length == 0)
			{
				if(!$.isIE() && (content.match(/<br>$/) || content.match(/<br\/>$/) || content.match(/<br \/>$/)))
				{
					content = content.replace(/<br>$/,'').replace(/<br\/>$/,'').replace(/<br \/>$/,'');
				}
				return content;
			}

			if(!$.isIE() && (content.match(/<br>$/) || content.match(/<br\/>$/) || content.match(/<br \/>$/)))
			{
				content = content.replace(/<br>$/,'').replace(/<br\/>$/,'').replace(/<br \/>$/,'');
				$(this.editor_doc).find('body').html(content);
			}

			var regex;
			var matches;
			var ubbc_tags_length = ubbc_tags.length;
			for(var i=0;i<ubbc_tags_length;i++)
			{
				// switch all relevant tags to the proper ubbc tag
				var tags = this.CONTROLS[this.UBBC_REF[ubbc_tags[i]]].tags;
				var tags_length = tags.length;
				for(var ti=0;ti<tags_length;ti++)
				{
					// beginning tags
					regex = new RegExp('<('+tags[ti]+'[^>]*)>','i');
					matches = content.match(regex);
					if(matches && matches.length > 1)
					{
						content = content.replace(matches[0],'['+ubbc_tags[i]+']');
					}
					// ending tags
					regex = new RegExp('<(/'+tags[ti]+'[^>]*)>','i');
					matches = content.match(regex);
					if(matches && matches.length > 1)
					{
						content = content.replace(matches[0],'[/'+ubbc_tags[i]+']');
					}
				}
			}

//			content = this.mine_ubbc_content(this.editor_doc.body, ubbc_tags);
			return content;
		},

/*		mine_ubbc_content : function(obj, ubbc_tags) {
			var children = obj.childNodes;
			if(obj.nodeType == 3 || children == null || children.length == 0)
			{
				if(obj.nodeValue == null)
				{
					return '';
				}
				else
				{
					return obj.nodeValue;
				}
			}

			var str = '';

			for(var i=0;i<children.length;i++)
			{
				if(children[i].nodeType == 1 && $.inArray(children[i].nodeName.toLowerCase(), ubbc_tags)) // a tag
				{
					var controls_length = this.settings.controls.length;
					for(var c_i=0; c_i<controls_length; c_i++)
					{
						var line = this.settings.controls[c_i].split(',');
						var line_length = line.length;
						for(var l_i=0; l_i<line_length; l_i++)
						{
							var name = line[l_i];
							var control = this.CONTROLS[name];
							
							var valid = false;
							if(control.tags)
							{
								for(var ti=0;ti<control.tags.length;ti++)
								{
									if(children[i].nodeName.toLowerCase() == control.tags[ti])
									{
										valid = true;
									}
								}
							}
							
							if(control.css && !valid)
							{
								valid = true;
								for(var css_key in control.css)
								{
									// if one of the attributes is not right, don't validate
									if(children[i].style[css_key].toLowerCase() != control.css[css_key])
									{
										valid = false;
									}
								}
							}
							
							if(valid)
							{
								str += '['+control.ubbc+']'+this.mine_ubbc_content(children[i], ubbc_tags)+'[/'+control.ubbc+']';
							}
							else
							{
								str += '<'+children[i].nodeName.toLowerCase()+'>'+this.mine_ubbc_content(children[i], ubbc_tags)+'</'+children[i].nodeName.toLowerCase()+'>';
								
								var blah = '';
								var elm = children[i];
								for(var ppp in elm)
								{
									try{
										blah += ppp+': '+elm[ppp]+'<br />';
									}catch(ex){}
								}
								$('#log').html(blah);
								str += this.mine_ubbc_content(children[i], ubbc_tags);
							}
						}
					}
				}
				else
				{
					str += this.mine_ubbc_content(children[i], ubbc_tags);
				}
			}

			return str;
		},*/

		highlight_controls : function(e) {
			var sel   = this.get_sel();
			var range = this.get_range();
			var target;

			if($.isIE())
			{
				target = range.parentElement();
			}
			else
			{
				target = range.endContainer;
			}

			// go through each control and add it
			var controls_length = this.settings.controls.length;
			for(var c_i=0; c_i<controls_length; c_i++)
			{
				var line = this.settings.controls[c_i].split(',');
				var line_length = line.length;
				for(var l_i=0; l_i<line_length; l_i++)
				{
					var name = line[l_i];
					// separator
					if(name != '|')
					{
						var control = this.CONTROLS[name];
						if(typeof control == 'object' && control != null)
						{
							var command   = control.command   || name;
							var func      = control.func      || null;
							var args      = control.args      || [];
							var className = control.className || name;

							$('.'+className, this.cpanel).removeClass('active');

							var element = target;
							// go while we are hitting text nodes (including spans)
							while(typeof element == 'object' && (element.nodeType == 1 || element.nodeType == 3))
							{
								if(element.nodeType == 1)
								{
									if(control.tags)
									{
										for(var i=0;i<control.tags.length;i++)
										{
											if(element.nodeName.toLowerCase() == control.tags[i])
											{
												$('.'+className, this.cpanel).addClass('active');
											}
										}
									}

									if(control.css)
									{
										for(var key in control.css)
										{
											if($(element).css(key).toString().toLowerCase() == control.css[key])
											{
												$('.'+className, this.cpanel).addClass('active');
											}
										}
									}
								}

								element = element.parentNode;
							}
						}
					}
				}
			}
		},

		build_controls : function() {
			var self = this;
			var args = args || [];

			// go through each control and add it
			var controls_length = this.settings.controls.length;
			for(var c_i=0; c_i<controls_length; c_i++)
			{
				var line = this.settings.controls[c_i].split(',');
				var line_length = line.length;
				for(var l_i=0; l_i<line_length; l_i++)
				{
					var name = line[l_i];
					// separator
					if(name == '|')
					{
						$('<li class="separator"></li>').appendTo(this.cpanel);
					}
					else
					{
						var control = this.CONTROLS[name];
						if(typeof control == 'object' && control != null)
						{
							this.add_control(name, control);
						}
					}
				}
			}
			var controls = this.settings.controls
			for(name in this.settings.controls)
			{
				var control = this.settings.controls[name];

				if(control.separator)
				{
					if( control.visible !== false )
					this.appendMenuSeparator();
				}
				else if ( control.visible )
				{
					this.appendMenu(
						control.command || name, control.arguments || [],
						control.className || control.command || name || 'empty', control.exec
					);
				}
			}
		},
		
		add_control : function(name, control)
		{
			var self = this;

			var new_control = {};
			new_control.command   = control.command   || name;
			new_control.func      = control.func      || null;
			new_control.args      = control.args      || [];
			new_control.className = control.className || name;
			new_control.shortcut  = control.shortcut  || null;
			this.controls[name] = new_control;

			if(new_control.shortcut)
			{
				this.control_shortcuts[new_control.shortcut] = name;
			}

			$('<li></li>').append(
			$('<a><!-- --></a>').addClass(new_control.className)
			).mousedown(function() {
				self.exec_command(name);
				$('a', this).toggleClass('active');
				return false;
			}).appendTo(this.cpanel);
		},
		
		exec_command : function(name)
		{
			var sel   = this.get_sel();
			var range = this.get_range();

			if($.isIE())
			{
				range.select();
			}
			else
			{
				sel.addRange(range);
			}

			var control = this.controls[name];

			var command   = control.command   || name;
			var func      = control.func      || null;
			var args      = control.args      || [];
			var className = control.className || name;

			if(func)
				func.apply(this);
			else
				this.editor_doc.execCommand(command, false, args);
			
			this.editor_doc.body.focus();
		},

		CONTROLS : {
			bold : {
				tags     : ['b','strong'],
				shortcut : 'b',
				css      : { fontWeight : 'bold' }
			},
			italic : {
				tags     : ['i','em'],
				shortcut : 'i',
				css      : { fontStyle : 'italic' }
			},
			underline : {
				tags     : ['u'],
				shortcut : 'u',
				css      : { textDecoration : 'underline' }
			},
			image : {
				tags     : ['img']
			}
		},

		UBBC_REF : {
			'b'   : 'bold',
			'i'   : 'italic',
			'u'   : 'underline',
			'img' : 'image'
		}
	});


/*
		$.extend(Wysiwyg.prototype, {
			
		});
		// what would this be used for?
*/
})(jQuery);
