function BBCodeEditorField(/*HTMLTextAreaElement*/ field, /*object*/ params) {
	field = typeof field == "string" ? document.getElementById(field) : field;
	this.field = field;
	var $c = this.$c = $(field).closest(".BBCodeEditorField");
	this.editMode = true;
	this.interval = null;
	this.activePanelName = null;
	this.tmp = {};

	this.lastSelectionCoords = [0, 0];

	this.params = $.extend({
		trim: false,
		maxlength: 500,
		maxlength_message: "maxlength_message",
		maxlength_invalid_message: "maxlength_invalid_message",
		img_width_digits_message: "img_width_digits_message",
		img_height_digits_message: "img_height_digits_message",
		img_url_message: "img_url_message",
		link_url_message: "link_url_message",
		youtube_url_message: "youtube_url_message",
		youtube_invalid_message: "youtube_invalid_message"
	}, params || {});

	var that = this;

	var changeFunction = this.field.onchange;
	$c.mousedown(function() { that.field.onchange = null; }).mouseup(function() { that.field.onchange = changeFunction; } );

	$(field)
		.focus(function() {
			if (!that.interval) {
				that.interval = setInterval(function() {
					that._interval();
				} , 200);
			}
		})
		.blur(function() {
			clearInterval(that.interval);
			that.interval = null;
		});

	$c.find(".action_edit").click(function() { if (that.isEditorLocked()) return; that.toPreview(); });
	$c.find(".action_preview").click(function(e) { if (that.isEditorLocked()) return; that.toEdit(); });
	//
	$c.find(".action_bold").unselectable().attr("href", "javascript:void(0);").click(function(e) { if (!that.editMode || that.isEditorLocked()) return;	that.wrapText("[b]", "[/b]"); e.preventDefault(); return false; });
	$c.find(".action_italic").unselectable().attr("href", "javascript:void(0);").click(function(e) { if (!that.editMode || that.isEditorLocked()) return; that.wrapText("[i]", "[/i]"); e.preventDefault(); });
	$c.find(".action_underline").unselectable().attr("href", "javascript:void(0);").click(function(e) { if (!that.editMode || that.isEditorLocked()) return; that.wrapText("[u]", "[/u]"); e.preventDefault(); });
	$c.find(".action_large_font").unselectable().attr("href", "javascript:void(0);").click(function(e) { if (!that.editMode || that.isEditorLocked()) return; that.wrapText("[large]", "[/large]"); e.preventDefault(); });
	$c.find(".action_small_font").unselectable().attr("href", "javascript:void(0);").click(function(e) { if (!that.editMode || that.isEditorLocked()) return; that.wrapText("[small]", "[/small]"); e.preventDefault(); });
	//
	$c.find(".action_img").attr("href", "javascript:void(0);").click(function(e) { if (!that.editMode || that.isEditorLocked()) return; that.togglePanel("img", this); e.preventDefault(); });
	$c.find(".action_url").attr("href", "javascript:void(0);").click(function(e) { if (!that.editMode || that.isEditorLocked()) return; that.togglePanel("url", this); e.preventDefault(); });
	$c.find(".action_youtube").attr("href", "javascript:void(0);").click(function(e) { if (!that.editMode || that.isEditorLocked()) return; that.togglePanel("youtube", this); e.preventDefault(); });
	$c.find(".close_panel").attr("href", "javascript:void(0);").click(function(e) { that.togglePanel(that._getActivePanelName(), null); });

	$c.find(".panel_img .insert_button").click(function(e) {
		e.preventDefault();
		var $panel = that.$c.find(".panels .panel_img");

		var $img_url_field = $panel.find(".image_url");
		if (!BBCodeEditorField.isValid($img_url_field, that.params.img_url_message)) {
			return;
		}
		var img_url = BBCodeEditorField.trim($img_url_field.val());

		var $image_width_field = $panel.find(".image_width");
		if (!BBCodeEditorField.isValid($image_width_field, null, BBCodeEditorField.emptyOrNumberValidator, that.params.img_width_digits_message)) {
			return;
		}
		var image_width = BBCodeEditorField.trim($image_width_field.val());		

		var $image_height_field = $panel.find(".image_height");
		if (!BBCodeEditorField.isValid($image_height_field, null, BBCodeEditorField.emptyOrNumberValidator, that.params.img_height_digits_message)) {
			return;
		}
		var image_height = BBCodeEditorField.trim($image_height_field.val());

		that.replaceText("[img"
			+ ((image_width.length > 0 || image_height > 0)
				? "=" + image_width + "x" + image_height
				: "")
			+ "]" + img_url + "[/img]");
		that.hidePanel("img");

	});
	$c.find(".panel_url .insert_button").click(function(e) {
		e.preventDefault();
		var $panel = that.$c.find(".panels .panel_url");
	
		var $url_field = $panel.find(".link_url");
		if (!BBCodeEditorField.isValid($url_field, that.params.link_url_message)) {
			return;
		}
		var url = BBCodeEditorField.trim($url_field.val());

		var label = BBCodeEditorField.trim($panel.find(".link_label").val());

		if (label.length == 0) {
			that.replaceText("[url]" + url + "[/url]");
		} else {
			that.replaceText("[url=" + url + "]" + label + "[/url]");
		}
		that.hidePanel("url");

	});
	$c.find(".panel_youtube .insert_button").click(function(e) {
		e.preventDefault();
		var $panel = that.$c.find(".panels .panel_youtube");

		var $url_field = $panel.find(".youtube_url");
		if (!BBCodeEditorField.isValid($url_field, that.params.youtube_url_message, function () { return BBCodeEditorField.youtubeValidator.apply(that, arguments); }, that.params.youtube_invalid_message)) {
			return;
		}
		var url = BBCodeEditorField.trim(that.tmp.youtube_result);

		that.replaceText("[youtube]" + url + "[/youtube]");

		that.hidePanel("youtube");
	});

	this.toEdit0();
}

BBCodeEditorField.isValid = function($field, emptyMessage, validatorFunction, invalidMessage) {
	var value = BBCodeEditorField.trim(epoint.ow.Utils.replaceNewLineCharacters($field.val()));
	if (emptyMessage != null) {
		if (value.length == 0) {
			alert(emptyMessage);
			$field.focus();
			return false;
		}
	}
	if (!validatorFunction) {
		return true;
	}
	if (!validatorFunction(value)) {
		alert(invalidMessage);
		$field.focus();
		return false;
	}
	return true;
}

BBCodeEditorField.emptyOrNumberValidator = function(val) {
	return val.length == 0 || (parseInt(val) + "" == val);
}

BBCodeEditorField.youtubeValidator = function(val) {
	var match;

	var youtu_be = new RegExp("^https?://youtu.be/(.*)");
	var youtube_com = new RegExp("^https?://www.youtube.[a-zA-Z]{2,3}/watch\?.*?(?:(?:v=)([^&]+))");

	match = val.match(youtu_be);
	if (match) {
		this.tmp.youtube_result = match[1];
		return true;
	}

	match = val.match(youtube_com);
	if (match) {
		this.tmp.youtube_result = match[1];
		return true;
	}

	return false;
}

BBCodeEditorField.prototype.replaceText = function(text) {
	this.field.focus();
	var coords = BBCodeEditorField.getSelectionCoords(this.field);
	if (coords[0] == 0 && coords[1] == 0) {
		coords = this.lastSelectionCoords;
	}
	this.field.value = this.field.value.substring(0, coords[0]) + text +  this.field.value.substring(coords[1]);
	BBCodeEditorField.setSelectionRange(this.field, coords[0] + text.length, coords[0] + text.length);
}

BBCodeEditorField.prototype.wrapText = function(start, end) {
	this.field.focus();
	var coords = BBCodeEditorField.getSelectionCoords(this.field);
	this.field.value = this.field.value.substring(0, coords[0]) + start + this.field.value.substring(coords[0], coords[1]) + end + this.field.value.substring(coords[1]);
	var selectionSize = coords[1] - coords[0];
	if (selectionSize > 0) {
		BBCodeEditorField.setSelectionRange(this.field, coords[0], coords[1] + start.length + end.length);
	} else {
		BBCodeEditorField.setSelectionRange(this.field, coords[0] + start.length, coords[0] + start.length);
	}
}

BBCodeEditorField.prototype._getActivePanelName = function() {
	return this.activePanelName;
}

BBCodeEditorField.prototype.togglePanel = function(panelName, panelButton) {
	var activePanelName = this._getActivePanelName();
	if (activePanelName != null) {
		this.hidePanel(activePanelName);
		if (activePanelName == panelName) {
			return;
		}
	}
	this.showPanel(panelName, panelButton);
}

BBCodeEditorField.prototype.hidePanel = function(panelName, panelButton) {
	var activePanelName = this._getActivePanelName();
	if (activePanelName != panelName) {
		return;
	}
	var $panels = this.$c.find(".panels").hide();
	var $panel = $panels.find(".panel_" + panelName).hide();
	$panel.find("input").val("");
	$(panelButton || ".action_" + panelName).removeClass("panel_button__selected");
	this.activePanelName = null;
}

BBCodeEditorField.prototype.showPanel = function(panelName, panelButton) {
	var activePanelName = this._getActivePanelName();
	if (activePanelName == panelName) {
		return;
	}
	if (activePanelName != null) {
		this.hidePanel(activePanelName);
	}
	var $panels = this.$c.find(".panels");
	var $panel = $panels.find(".panel_" + panelName).show();
	$(panelButton || ".action_" + panelName).addClass("panel_button__selected");
	$panels.show();
	this.activePanelName = panelName;
}

BBCodeEditorField.prototype.toPreview = function() {
	if (!this.editMode) {
		return;
	}
	clearInterval(this.interval);
	this.interval = null;
	this.$c.find(".tools").css('opacity', 0.5);
	this.$c.find(".mode_tabs IMG").removeClass("active_tab").hide();
	this.$c.find(".mode_tabs IMG.action_preview").addClass("active_tab").show();
	var code = BBCodeEditorField.getCodePreview(this.field.value);
	this.$c.find(".edit_area").hide();
	var $previewArea = this.$c.find(".preview_area");
	$previewArea.find(".preview").html(code);
	$previewArea.show();
	this.editMode = false;
}

BBCodeEditorField.prototype.toEdit = function() {
	if (this.editMode) {
		return;
	}
	this.toEdit0();
	this.editMode = true;
}

BBCodeEditorField.prototype.toEdit0 = function() {
	this.$c.find(".tools").css('opacity', 1);
	this.$c.find(".mode_tabs IMG").removeClass("active_tab").hide();
	this.$c.find(".mode_tabs IMG.action_edit").addClass("active_tab").show();
	this.$c.find(".edit_area").show();
	this.$c.find(".preview_area").hide();
	var that = this;
	if (!this.interval) {
		this.interval = setInterval(function() {
			that._interval();
		} , 200);
	}
}

BBCodeEditorField.prototype._interval = function() {
	this._updateMaxLength();
	this.lastSelectionCoords = BBCodeEditorField.getSelectionCoords(this.field);
}

BBCodeEditorField.prototype._updateMaxLength = function() {
	var length = epoint.ow.Utils.replaceNewLineCharacters(this.field.value).length;
	if (length > this.params.maxlength) {
		this.$c.find(".length_limit_info").addClass('limit_exceeded').text(this.params.maxlength_invalid_message);
	} else {
		this.$c.find(".length_limit_info").removeClass('limit_exceeded').text(this.params.maxlength_message.replace("{left}", this.params.maxlength - length));
	}
}

BBCodeEditorField.prototype.isEditorLocked = function() {
  return this.$c.hasClass('editor_locked');
}

BBCodeEditorField.getCodePreview = function(/*string*/ bbcode) {
	var protocol = document.location.protocol;

	return bbcode
		.replace(/\n/g, "<br>")
		.replace(/\[b\](.*?)?\[\/b\]/g, "<b>$1</b>")
		.replace(/\[i\](.*?)?\[\/i\]/g, "<i>$1</i>")
		.replace(/\[u\](.*?)?\[\/u\]/g, "<u>$1</u>")
		.replace(/\[small\](.*?)?\[\/small\]/g, "<small>$1</small>")
		.replace(/\[large\](.*?)?\[\/large\]/g, "<big>$1</big>")
		.replace(/\[img(?:=(\d*)x(\d*))?\](.+?)\[\/img\]/g, function(all, width, height, src) {
			return "<img src=\"" + src + "\"" +
				(width != "" ? " width=\"" + width + "\"" : "") +
				(height != "" ? " height=\"" + height + "\"" : "") +
				" alt=\"\">"; })
		.replace(/\[url(?:=(.*?))?\](.+?)\[\/url\]/g, function(all, url, label_or_url) {
			return "<a href=\"" +
				(url != "" ? url : label_or_url) + "\">" + label_or_url + "</a>"; })
		.replace(/\[youtube\](.*?)\[\/youtube\]/g, "<iframe width=\"420\" height=\"345\" src=\"" + protocol + "//www.youtube.com/embed/$1\" frameborder=\"0\"></iframe>");
}

/*void*/ BBCodeEditorField.setSelectionRange = function(/*Node*/ field, /*number*/ start, /*number*/ end) {
	if (field.setSelectionRange) {
		// MOZILLA, OPERA, SAFARI
		// Gdy pole ma display none w MOZILLA wystepuje blad
		try {
			field.setSelectionRange(start, end);
		} catch (e) {
			// empty
		}
	} else
	if (field.createTextRange) {
		// MSIE
		var range = field.createTextRange();
		range.collapse(true);
		range.moveStart("character", start);
		range.moveEnd("character", end - start);
		range.select();
	}
}

/*number[]*/ BBCodeEditorField.getSelectionCoords = function(/*Node*/ field) {
// Proba odwolania sie w GECKO do wlasciwosci selectionStart lub selectionEnd
// potrafi zaowocowac bledem. Dlatego sprawdzam obecnosc metody
// setSelectionRange i pobranie wlasciwosci jest w bloku try.
	if (typeof field.setSelectionRange != "undefined") {
		try {
			return [field.selectionStart, field.selectionEnd];
		} catch (e) {
			//empty
		}
	} else
	if (document.selection && document.selection.createRange) {
		// MSIE 6 gdy dokument jest w iframe wywolanie createRange powoduje Security error
		try {
			var range = document.selection.createRange();
			if (range.parentElement() == field) {
				var trn = field.createTextRange();
				trn.moveToBookmark(range.getBookmark());
				trn.moveEnd('character', field.value.length);
				selectionStart = field.value.substring(0, field.value.length - trn.text.length).length;
				selectionEnd   = selectionStart + range.text.length;
				return [selectionStart, selectionEnd];
			}
		} catch (e) {
			//empty
		}
	}
	return [0, 0];
}

BBCodeEditorField.trim = (function() {
	if (String.prototype.trim) {
		return function(text) {
			return text.trim();
		}
	} else {
		return function(/*string*/ text) {
			return text.replace(/^\s*|\s*$/g, "");
		}
	}
})();

if (!jQuery.fn.unselectable) {
	jQuery.fn.unselectable = function() {
		return this.each(function() {
			$(this)
				.css('-moz-user-select', 'none')		// FF
				.css('-khtml-user-select', 'none')		// Safari, Google Chrome
				.css('user-select', 'none');			// CSS 3
			if ($.browser.msie) {						// IE
				$(this).each(function() {
					this.ondrag = function() {
						return false;
					};
				});
				$(this).each(function() {
					this.onselectstart = function() {
						return (false);
					};
				});
			}
			//else if($.browser.opera) {
			$(this).attr('unselectable', 'on');
			//}
		})
	}
}

