var zEditor = function(textarea_id, style_options, flexible, connect_id) {
	this.junkies = {};

	// this.attachment = new Attachment();
	this.textarea = document.getElementById(textarea_id);
	this.styles = style_options;
	this.flexible = (flexible == "flexible") ? true : false;
	this.connect_id = connect_id;
	this.doc_root_id = "page_content";
	this.iframe_id = "zeditor_container";

	this.character_count = 0;

	this.default_container = "<p>";
	this.default_class = "zEditor";
	this.allow_containers = { p:true, h1:true, h2:true, ol:true, ul:true, div:true };
	this.eventor = null;
	this.selection = null;

	this.buttons = [
		// {name:"HTML", action:"src"},
		{name:"B", action:"bold", icon:"btn_b.gif"},
		{name:"I", action:"italic", icon:"btn_i.gif"},
		{name:"space", action:"space"},
		{name:"P/V", action:"upload_asset", icon:"btn_photo.gif"},
		{name:"Web", action:"external_web", icon:"btn_web.gif"},
		{name:"Mail", action:"email", icon:"btn_email.gif"}
		// {name:"Map", action:"gmap", icon:"btn_map.gif"}
		// {name:"이미지 업로드", action:"attachment"},
		// {name:"글씨초기화", action:"removeformat"}
		// {name:"텍스트", action:"text"},
		// {name:"내보내기", action:"export"}
		// {name:"이미지 링크", action:"external_image"},
		// {name:"동영상 업로드", action:"insert_embed"},
		// {name:"언링크", action:"unlink"},
		// {name:"글씨색", action:"forecolor"},
		// {name:"배경색", action:"hilite"},
		// {name:"굵게", action:"bold"},
		// {name:"기울이기", action:"italic"},
		// {name:"가운데선", action:"strikethrough"},
		// {name:"글씨초기화", action:"removeformat"}
		// {name:"quote"},
		// {name:"code"},
		// {name:"olist", action:"insertorderedlist"},
		// {name:"ulist", action:"insertunorderedlist"},
		// {name:"indent"},
		// {name:"outdent"},
	];

	this.extra_case_of_ie = { "inserthtml":true, "hilite":true };
	this.extra_case_of_mozilla = { "hilite":true };

	// temporary.
	this.editor_container = document.getElementById("editor_container");
	this.toolbar_container = document.getElementById("custom_toolbar_container") || this.editor_container;
	if(this.flexible) this.body_clone = this.generate_clone();
	this.iframe = this.render_editor();
	this.bind_event();
}

zEditor.prototype = {
	caret_touch: function() {
		var next_overflow = ( this.iframe.style.overflow != "auto" ) ? "auto" : "hidden";
		this.iframe.style.overflow = next_overflow;
	},

	sync: function() {
		try {
			var html = this.iframe.contentWindow.document.body.innerHTML;
			html = html.replace(/[\r\n]/g, '');
			html = html.replace(/&nbsp;<\/p>/ig, '<br></p>');
			this.textarea.value = html;
			if(this.flexible) this.body_clone.innerHTML = html;
		} catch(e) {
			// Log(e)
		}
	},

	save_caret: function() {
		if(Browser.msie) this.win().document._caret = this.win().document.selection.createRange();
	},

	restore_caret: function() {
		if(Browser.msie && this.win().document._caret && this.win().document._caret.select) this.win().document._caret.select();
	},

	focus_on: function() {
		this.iframe.contentWindow.focus();
	},

	design_on: function() {
		if(Browser.msie) this.win().document.body.contentEditable = true;
		else this.win().document.designMode = "on";
	},

	design_off: function() {
		if(Browser.msie) this.win().document.body.contentEditable = false;
		else this.win().document.designMode = "off";
	},

	activate_selection_buttons: function() {
		// Log("activate button by selection");
	},

	content: function(_data) {
		if(typeof(_data) == "string") this.doc().innerHTML = _data;
		else return this.doc().innerHTML;
	},

	generate_clone: function() {
		var _c = document.createElement("div");
		_c.id = "body_clone_to_resize";
		this.editor_container.appendChild(_c);

		return _c;
	},

	render_editor: function() {
		var _editor_core_wrapper = document.createElement("div");
		_editor_core_wrapper.unselectable = "on";
		_editor_core_wrapper.id = "editor_core_wrapper";

		// for connect_note_asset
		// var _asset_layer = document.createElement("div");
		// _asset_layer.id = "asset_layer";
		// _editor_core_wrapper.appendChild(_asset_layer);

		var _iframe = document.createElement("iframe");
		_iframe.id = this.iframe_id;
		_iframe.frameBorder = "0";
		//_iframe.style.width = "98%";
		//var window_height = window.innerHeight || Math.min(document.documentElement.offsetHeight, document.documentElement.clientHeight);
		//_iframe.style.height = (window_height - 224) + "px";
		_iframe.style.overflow = "auto";
		_iframe.style.width = "185px";

		if(this.styles) {
			for(var key in this.styles) {
				var css_name = key.replace(/(?:-)(\w{1})/g, function(s, m) { return m.toUpperCase(); });
				_iframe.style[css_name] = this.styles[key];
			}
		}

		// this.editor_container.appendChild(this.create_toolbar_element_with_buttons());
		this.toolbar_container.appendChild(this.create_toolbar_element_with_buttons());
		_editor_core_wrapper.appendChild(_iframe);

		if (/^\/connects/.test(location.pathname) || /^\/demo_connect/.test(location.pathname)) {
			var shadow = document.createElement("div");
			shadow.className = "shadow";
			shadow.appendChild(_editor_core_wrapper);
			this.editor_container.appendChild(shadow);
		}
		else {
			this.editor_container.appendChild(_editor_core_wrapper);
		}

		_iframe.contentWindow.document.designMode = "on";

		var _html = "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>";
		_html += "<html>";
		_html += "<head><title>zedit</title></head>";
		_html += '<link href="/stylesheets/wyg.css?' + new Date().getTime() + '" media="screen" rel="Stylesheet" type="text/css" />';
		_html += "<body id='" + this.doc_root_id + "' style='margin:0;padding:0px'>";

		// ToDo: decode_entities is right solution?
		if(this.textarea) _html += this.textarea.value.__decode_entities();
		_html += "</body>";
		_html += "</html>";

		_iframe.contentWindow.document.open("text/html", "replace");
		_iframe.contentWindow.document.write(_html);
		_iframe.contentWindow.document.close();

		return _iframe;
	},

	resize_editor: function() {
		var sT = this.body_clone.scrollHeight;

		if(sT >= 80) {

			// Warn: Dirty MAGIC (Broong Only)
			var row_no1 = document.getElementById("connect_note_container_0");
			var fixed = (Browser.msie) ? 94 : 92;
			if(row_no1) row_no1.style.marginTop = sT + fixed + "px";

			this.iframe.style.height = sT + 20 + "px";
		}
	},

	reset_size: function() {
		this.iframe.style.height = "100px";
	},

	bind_event: function() {
		var keyDownEvent = (Browser.msie) ? "keydown" : "keypress";
		OnEvent.add(this.iframe.contentWindow.document, keyDownEvent, BindEvent(this, this.hijack));
		OnEvent.add(this.iframe.contentWindow.document, "keyup", BindEvent(this, this.hijack));
		OnEvent.add(this.iframe.contentWindow.document, "focus", BindEvent(this, this.hijack));
		OnEvent.add(this.iframe.contentWindow.document, "click", BindEvent(this, this.hijack));
		OnEvent.add(this.iframe.contentWindow.document, "mouseup", BindEvent(this, this.hijack));
		OnEvent.add(this.iframe.contentWindow.document, "mousedown", BindEvent(this, this.hijack));
		// OnEvent.add(this.iframe.contentWindow.document, "mouseover", BindEvent(this, this.hijack));
		// OnEvent.add(this.iframe.contentWindow.document, "mouseout", BindEvent(this, this.hijack));
	},

	hijack: function(_event) {
		if(/^body text$/i.test(this.doc().innerHTML)) this.doc().innerHTML = "";

		this.eventor = new Eventor(_event || this.iframe.contentWindow.event);
		this.selection = new Selection(this.iframe.contentWindow);
		// if(["keyup", "mouseup"].include(this.eventor._type()) && !String(this.selection._html()).is_empty()) this.activate_selection_buttons();

		var action_type = {
			focus: "focus",
			blur: "blur",
			click: "click",
			mouseover: "mouseover",
			mouseout: "mouseout",
			mouseup: "mouseup",
			mousedown: "mousedown",
			keydown: "key_down",
			keypress: "key_down",
			keyup: "key_up",
			paste: "paste",
			drop: "drop"
		};

		var gap = (this.character_count > 0) ? Math.abs(this.character_count - this.doc().innerHTML.length) : this.character_count;
		this.character_count = this.doc().innerHTML.length;

		if((!this.enter_pressed() && gap > 10)) {
			// check whole tags
			// alert("I doubt paste.");
		} else {
			this["case_" + action_type[this.eventor._type()]]();
		}
	},

	case_mousedown: function() {
		// this.design_on();
	},

	case_mouseover: function() {
		// if(/^a$/i.test(this.eventor._node().nodeName)) {
			// if(Browser.msie) this.design_off();
			// else if(Browser.mozilla) this.eventor._node().style.cssText = ";cursor:pointer !important;"
		// }
	},
	
	case_mouseout: function() {
		// if(/^a$/i.test(this.eventor._node().nodeName)) if(Browser.msie) this.design_on();
	},

	case_drop: function() {
		// var _href = this.eventor._event().dataTransfer.getData("Text");
		// this.execCmd("inserthtml", zPageLink(_href));

		// this.eventor.break_event();
	},

	case_focus: function() {
		if(!this.doc().innerHTML) this.bind_container();
	},

	case_click: function() {
		if(this.eventor._event().button == 2) {
			this.eventor.break_event();
			return false;
		}
	},

	case_mouseup: function() {
		this.sync();
	},

	//
	// Key Down
	case_key_down: function() {
		this.insert_nbsp_on_empty_container();
		if(this.body_clone) {
			var bH = this.body_clone.clientHeight;
			var fH = this.iframe.clientHeight;
			if(bH >= 100) {
				if(Math.abs(bH - fH) > 10) {
					this.resize_editor();
					// this.iframe.style.height = bH + "px";
				}
			}
		}

	},

	//
	// Key Up
	case_key_up: function() {
		this.bound_container_when_caret_on_bare_doc();
		this.bound_container_if_disallow_container();

		if(this.enter_pressed()) {
			this.peek_at_current_and_previous_container();
			if(this.flexible) this.resize_editor();
		}

		this.sync();

	},

	insert_nbsp_on_empty_container: function() {
		if(this.backspace_pressed()) {
			var _html = this.selection._node().innerHTML
			if(/^([\s\u00a0]|&nbsp;|<br>)$/.test(_html)) {
				this.selection._node().innerHTML = _html.replace(/([\s\u00a0]|&nbsp;|<br>)(?=$)/i, '&nbsp;');
			}
		}

		if(this.enter_pressed() && Browser.mozilla) {
			this.execCmd("inserthtml", "&nbsp;");
		}
	},

	peek_at_current_and_previous_container: function() {
		var _current_container = this.find_top_container();

		this.mark_my_node(_current_container);
		this.peek_at_previous_container(_current_container);
	},

	peek_at_previous_container: function(_node) {
		var _previous_container = (_node) ? _node.previousSibling : this.selection._node().previousSibling;
		if(!_previous_container) return false;

		this.mark_my_node(_previous_container);
		if(Browser.msie) this.clean_up_duplicated_nbsp(_previous_container);
		this.replace_weird_tag(_previous_container);
	},

	clean_up_duplicated_nbsp: function(_node) {
		var _html = _node.innerHTML || "";
		_html = _html.replace(/([\s\u00a0]|&nbsp;)+<br[^>]*>$/i, '<br>');
		_html = _html.replace(/([\s\u00a0]|&nbsp;)+(<br>)?$/i, '<br>');
		_node.innerHTML = _html;
	},

	mark_my_node: function(_node) {
		if(!new RegExp(this.default_class, "i").test(_node.className)) {
			_node.className += " " + this.default_class;
		}
	},

	// ToDo: refactoring.
	replace_weird_tag: function(_node) {
		var _html = _node.innerHTML;
		var result_html = _html;
		var pattern = /<(\/)?([a-z]+)([^>]*)>/gi;
		var match = null;
		while(match = pattern.exec(_html)) {
			var _tag_name = match[2].toLowerCase();
			var _type = (match[1] && match[1] == "/") ? "close" : "open";

			if(["strong", "em", "strike"].include(_tag_name)) {
				result_html = result_html.replace(match[0], Bind(this, function() {
					var _tag = "<";
					_tag += match[1] || "";
					_tag += "span";
					if(_type == "open") {
						_tag += {'strong': ' style="font-weight: bold;"', 'em': ' style="font-style: italic;"', 'strike': ' style="text-decoration: line-through;"'}[_tag_name] || "";
						_tag += ' class="' + this.default_class + '" ';
					}
					_tag += ">";
					return _tag;
				}));
			}
		}

		_node.innerHTML = result_html;
		return _node;
	},

	bound_container_when_caret_on_bare_doc: function() {
		if(this.caret_on_root_element()) this.bind_container();
	},

	bound_container_if_disallow_container: function() {
		if(!this.check_allowed_container()) this.peek_at_container_and_bind();
	},

	bind_container: function() {
		if(Browser.msie) {
			this.selection._range().execCommand("formatblock", false, this.default_container) 
			this.selection._range().select();
			//this.selection._event().cancelBubble = true;
		} else {
			this.execCmd("formatblock", this.default_container);
		}
	},

	peek_at_container_and_bind: function() {
		var peeked_node = this.find_top_container();
		if(!peeked_node) this.bind_container();
	},

	find_top_container: function(_node) {
		var current_node = _node || this.selection._node();

		rake_up_parent:
		while(current_node.id != this.doc_root_id && current_node.parentNode) {
			if(this.check_allowed_container(current_node)) {
				return current_node;
			}
			current_node = current_node.parentNode;
		};

		return false;
	},

	patch_containers: function(_cbo) {
		var _root = this.doc();
		for(var i=0; i<_root.childNodes.length; i++) {
			_cbo(_root.childNodes[i]);
		}
	},

	patch_selection: function(_node, _end, _cbo) {
		do {
			_cbo(_node);
			if(_node === _end) break;
			_node = _node.nextSibling;
		} while(_node);
	},

	check_allowed_container: function(_specific_node) {
		var _node = _specific_node || this.selection._node();
		return (_node.tagName.toLowerCase() in this.allow_containers && _node.parentNode && _node.parentNode.id == this.doc_root_id);
	},

	caret_on_root_element: function() {
		return (this.selection._node().id == this.doc_root_id);
	},

	enter_pressed: function() {
		return (this.eventor._keycode() == 13 && !this.eventor._event().shiftKey);
	},

	backspace_pressed: function() {
		return (this.eventor._keycode() == 8 && !this.eventor._event().shiftKey);
	},

	period_inputed: function() {
		return (this.eventor._keycode() == 190 && !this.eventor._event().shiftKey);
	},

	doc: function() {
		if(!this.junkies["doc"]) {
			this.junkies["doc"] = this.iframe.contentWindow.document.getElementById(this.doc_root_id);
		}

		return this.junkies["doc"];
	},

	win: function() {
		if(!this.junkies["win"]) {
			this.junkies["win"] = this.iframe.contentWindow;
		}

		return this.junkies["win"];
	},

	//
	// Toolbar
	//
	create_toolbar_element_with_buttons: function() {
		var _container = document.createElement("div")
		_container.className = "toolbar_container";
		_container.style.overflow = "hidden";
		_container.unselectable = "on";
		for(var i=0; i<this.buttons.length; i++) {
			if(this.buttons[i].name == "space") {
				var _button = document.createElement("img");
				_button.src = "/images/note/btn_space_w9px.gif";
			} else {
				var _button = this.create_toolbar_button(this.buttons[i]);
			}
			_container.appendChild(_button);
		}

		return _container;
	},

	create_toolbar_button: function(_button) {
		var callsign = _button.action || _button.name;

		if(_button.icon) {
			var button = document.createElement("img");

			if (_button.action == "upload_asset" && /^\/connects/.test(location.pathname)) {
				button.src = "/images/note/btn_file.gif";
			}
			else button.src = "/images/note/" + _button.icon;
		} else {
			var button = document.createElement("span");
			button.unselectable = "on";
			button.innerHTML = _button.name;
		}
		button.className = "toolbar_button";

		OnEvent.add(button, "mouseover", BindEvent(this, function(_event) {
			var _e = new Eventor(_event)
			_e._node().style.color = "red"; //randomColor();
		}));

		OnEvent.add(button, "mouseout", BindEvent(this, function(_event) {
			var _e = new Eventor(_event);
			_e._node().style.color = "";
		}));

		OnEvent.add(button, "mousedown", BindEvent(this, function(_event) {
			var _e = new Eventor(_event);
			_e._node().style.color = "red";
		}));

		OnEvent.add(button, "mouseup", BindEvent(this, this.executor, callsign));

		return button;
	},

	execCmd: function(_action, _value) {

		if(Browser.msie && _action in this.extra_case_of_ie) {
			this.ie_only_executor(_action, _value);
			// return;
		}

		else if(Browser.mozilla && _action in this.extra_case_of_mozilla) {
			this.mozilla_only_executor(_action, _value);
			// return;
		}

		else {
			this.iframe.contentWindow.document.execCommand(_action, false, _value);
		}

		var _container = this.find_top_container(this.selection._node());
		if(_container) {
			this.mark_my_node(_container);
			for(var i=0; i<_container.childNodes.length; i++) {
				var _child = _container.childNodes[i];
				if(_child.nodeType == 3) continue;
				if(/br/i.test(_child.tagName)) continue;
				this.mark_my_node(_child);
			}
		}

		this.sync();
		this.focus_on();
	},

	executor: function(_callsign, _event) {
		var _toolbar_eventor = new Eventor(_event);
		_toolbar_eventor._node().style.color = "";
		if(_toolbar_eventor._event().button == 2) return false;

			if(!this.selection) {
				this.focus_on();
				this.selection = new Selection(this.iframe.contentWindow);
				this.selection._focus_to_end();
			}

		switch(_callsign) {

			//////////////////////////////////////////////////////////////////
			// cancel
			case "cancel":
				history.go(-1);
			break;

			case "text":
				if(!this.selection || !this.selection._range() || !this.selection._html()) {
					new Shout().alert("create selection first", 1);
					return;
				}
				this.save_caret();
				this.text_picker = new Pickers(this.iframe.contentWindow, "text", _toolbar_eventor._page(), Bind(this, function() {
					this.focus_on();
					this.restore_caret();
					var _ret = this.text_picker.values["TextPicker"];
					switch(_ret) {
						case "hilite":
							this.execCmd("hilite", "#FFFF30");
						break;
						default:
							this.execCmd(_ret, false);
					}
				}));
			break;

			case "export":
				this.save_caret();
				this.text_picker = new Pickers(this.iframe.contentWindow, "export", _toolbar_eventor._page(), Bind(this, function() {
					this.focus_on();
					this.restore_caret();
					var _ret = this.text_picker.values["ExportPicker"];
				}));
			break;

			//////////////////////////////////////////////////////////////////
			// outer link
			case "external_image":
				this.save_caret();
				this.link_picker = new Pickers(this.iframe.contentWindow, "url", _toolbar_eventor._page(), Bind(this, function() {
					this.focus_on();
					this.restore_caret();
					if(this.link_picker.values["UrlPicker"]) {
						var html = '<p><img src="' + this.link_picker.values["UrlPicker"] + '"><br></p>';
						this.execCmd("inserthtml", html);
					}
				}));
			break;

			//////////////////////////////////////////////////////////////////
			// external web page
			case "external_web":
				this.save_caret();
				this.link_picker = new Pickers(this.iframe.contentWindow, "url", _toolbar_eventor._page(), Bind(this, function() {
					this.focus_on();
					this.restore_caret();
					// FixMe
					if(this.link_picker.values["UrlPicker"]) {
						var link_text = (this.selection._html().length > 0) ? this.selection._html() : this.link_picker.values["UrlPicker"];
						var html = '<a target="_new" class="orange ' + this.default_class + '" href="' + this.link_picker.values["UrlPicker"] + '">' + link_text + '</a>';
						this.execCmd("inserthtml", " " + html);
					}
				}), "Hyper Link");
			break;

			//////////////////////////////////////////////////////////////////
			// email
			case "email":
				this.save_caret();
				this.link_picker = new Pickers(this.iframe.contentWindow, "url", _toolbar_eventor._page(), Bind(this, function() {
					this.focus_on();
					this.restore_caret();
					// FixMe
					if(this.link_picker.values["UrlPicker"]) {
						var email = this.link_picker.values["UrlPicker"].replace(/^http:\/\//, "mailto:");//by dhan
						//var email = this.link_picker.values["UrlPicker"].replace(/^http/, "mailto");
						var link_text = (this.selection._html().length > 0) ? this.selection._html() : email;
						var html = '<a target="_new" class="orange ' + this.default_class + '" href="' + email + '">' + link_text + '</a>';
						this.execCmd("inserthtml", " " + html);
					}
				}), "Email Link");
			break;

			//////////////////////////////////////////////////////////////////
			// Google Map
			case "gmap":
				this.save_caret();

				// May use..?
//				this.focus_on();
//				this.restore_caret();

				if (/^\/connects/.test(location.pathname) || /^\/demo_connect/.test(location.pathname)) {
					BroongLayer.open(_toolbar_eventor._event(), { url: '/maps/new', position_by_event: true });
				}
				else {
					BroongLayer.open(_toolbar_eventor._event(), { url: '/maps/new', has_parent: true });
				}
			break;

			//////////////////////////////////////////////////////////////////
			// embed tag
			case "insert_embed":
				this.save_caret();
				this.embed_picker = new Pickers(this.iframe.contentWindow, "embed", _toolbar_eventor._page(), Bind(this, function() {
					this.focus_on();
					this.restore_caret();
					// FixMe
					if(this.embed_picker.values["EmbedPicker"]) {
						var embed_html = (this.selection._html().length > 0) ? this.selection._html() : this.embed_picker.values["EmbedPicker"];
						this.execCmd("inserthtml", " " + embed_html);
					}
				}));
			break;


			//////////////////////////////////////////////////////////////////
			// upload asset
			case "upload_asset":
				if (/^\/connects/.test(location.pathname) || /^\/demo_connect/.test(location.pathname)) {
					BroongLayer.open(_toolbar_eventor._event(), { url:"/connects/" + this.connect_id + "/connect_note_assets/new", position_by_event: true })
				} else {
					BroongLayer.open(_toolbar_eventor._event(), { url:"/note_assets/new", position_by_dom: _toolbar_eventor._node(), has_parent: true })
				}
			break;

			//////////////////////////////////////////////////////////////////
			// attachment
			case "attachment":
				if(!this.selection || !this.selection._range() || !this.selection._html()) this.focus_on();
				this.save_caret();
				this.attachment.pop(_toolbar_eventor._page(), Bind(this, function(_response) {
					this.focus_on();
					this.restore_caret();
					//this.selection._focus_to_end();
					var _path = /path:([\S\s]*(?=[;]))/i.exec(_response);
					this.execCmd("inserthtml", zImgShow(_path.last()));
				}));
			break;

			//////////////////////////////////////////////////////////////////
			// font color
			case "forecolor":
				this.forecolor_picker = new Pickers(this.iframe.contentWindow, "color", _toolbar_eventor._page(), Bind(this, function() {
					this.execCmd("forecolor", this.forecolor_picker.values["ColorPicker"]);
				}));
			break;

			//////////////////////////////////////////////////////////////////
			// text hilite
			case "hilite":
				if(!this.selection || !this.selection._range() || !this.selection._html()) {
					new Shout().alert("create selection first", 1);
					return;
				}

				this.backcolor_picker = new Pickers(this.iframe.contentWindow, "color", _toolbar_eventor._page(), Bind(this, function() {
					this.execCmd("hilite", this.backcolor_picker.values["ColorPicker"]);
				}));
			break;

			//////////////////////////////////////////////////////////////////
			// quote box
			case "quote": case "code":
				var _h = this.selection._html();
				var _html = '<div class="' + _callsign + '_layer">' + _h + '</div>';
				_html += '<p><br></p>';

				this.execCmd("inserthtml", _html);
			break;

			//////////////////////////////////////////////////////////////////
			// temporary command. show html source.
			case "src":
				alert(this.iframe.contentWindow.document.body.innerHTML);
			break;

			case "insertorderedlist":
				this.execCmd(_callsign, false);
				this.selection = new Selection(this.iframe.contentWindow);
				if(!this.find_top_container()) {
					this.bound_container_when_caret_on_bare_doc();
					this.bound_container_if_disallow_container();
				}
			break;
			
			//////////////////////////////////////////////////////////////////
			// default designMode execCommand.
			default:
				this.execCmd(_callsign, false);
		}
	},

	ie_only_executor: function(_action, _value) {
		switch (_action) {
			case "inserthtml":
				this.selection._range().pasteHTML(_value);
			break;

			case "hilite":
				this.execCmd("backcolor", _value);
			break;
		}
	},

	mozilla_only_executor: function(_action, _value) {
		switch (_action) {
			case "hilite":
				var _r = this.selection._range().getRangeAt(0);
				var _start = _r.startContainer;
				if(_start.nodeType == 3) _start = _start.parentNode;

				var _end = _r.endContainer;
				if(_end.nodeType == 3) _end = _end.parentNode;

				this.execCmd("hilitecolor", _value);

				this.patch_selection(_start, _end, function(_node) {
					var _old_style = _node.getAttribute("style") 
					if(!_old_style) return;
					_node.removeAttribute("style");
					var _html = '<span class="' + this.default_class + '" style="' + _old_style + '">';
					_html += _node.innerHTML;
					_html += '</span>';
					_node.innerHTML = _html;
				});
			break;
		}
	}

} // zEditor prototype





//
// pickers
//
var Pickers = function(_win, _tools, _mouse_xy, _cbo, _title) {
	this.win = _win;
	this.cbo = _cbo;
	this.title = _title;
	this.worker_pool = {};
	this.tools = __A(_tools);
	this.mouse_xy = _mouse_xy;
	this.values = {};
	this.pick_picker();
}

Pickers.prototype = {
	pick_picker: function() {
		for(var i=0; i<this.tools.length; i++) {
			var _worker = WordCaps(this.tools[i]) + "Picker";
			try {
				this.worker_pool[_worker] = new window[_worker](this, this.grep_picked_value, this.title);
			} catch(e) {
				Log(e);
				alert(e.message);
			}
		}
	},

	grep_picked_value: function(_worker, _owner, _event) {
		var _evt = new Eventor(_event);
		_owner.values[_worker] = this.final_value(_evt);
		_owner.cbo();
		if(!this.prevent_auto_close) this.fire();
	}
}

//
// Pick Workers
//

var TextPicker = function(_refer, _cbo) {
	this.picker_id = "_text_picker";
	this.refer = _refer;
	this.cbo = _cbo;
	// this.prevent_auto_close = true;

	this.make_sure_no_same_picker();
	this.worker = this.hire();
}

TextPicker.prototype = {
	make_sure_no_same_picker: function() {
		var _exists_picker = document.getElementById(this.picker_id);
		if(_exists_picker) _exists_picker.parentNode.removeChild(_exists_picker);
	},

	hire: function() {
		var _worker = document.createElement("div");
		_worker.id = this.picker_id
		_worker.className = "toolbar_plate";
		_worker.style.position = "absolute";
		_worker.style.overflow = "auto";
		_worker.style.left = "-1024px";
		_worker.style.top = "-768px";

		var _actions = [
			{ name: "Clear Styles", action: "removeformat" },
			{ name: "bold" },
			{ name: "italic", action: "italic" },
			{ name: "line-through", action: "strikethrough" },
			{ name: "emphasis", action: "hilite" }
		];
		var _buttons = document.createElement("ul");
		_buttons.className = "textpicker";
		_worker.appendChild(_buttons);

		for(var i=0; i<_actions.length; i++) {
			var _button = document.createElement("li");
			_button.className = _actions[i].action || _actions[i].name
			_button.innerHTML = _actions[i].name;
			_buttons.appendChild(_button);

			OnEvent.add(_button, "click", BindEvent(this, this.cbo, [WordCaps(_worker.id), this.refer]));
			OnEvent.add(_button, "mouseover", function() {
				this.style.color = "white";
				this.style.backgroundColor = "#999";
			});

			OnEvent.add(_button, "mouseout", function() {
				this.style.color = "";
				this.style.backgroundColor = "";
			});
		}

		var _footer = document.createElement("div");
		_footer.style.padding = "5px 0px";
		_footer.style.textAlign = "center";
		_worker.appendChild(_footer);

		var _closer = document.createElement("input");
		_closer.type = _closer.className = "button";
		_closer.value = "close";
		_footer.appendChild(_closer);


		document.body.appendChild(_worker);
		MoveToMousePos(_worker, this.refer.mouse_xy);

		OnEvent.add(_closer, "click", Bind(this, this.fire));

		return _worker;
	},

	fire: function() {
		this.worker.parentNode.removeChild(this.worker);
	},

	final_value: function(_event) {
		return _event._node().className;
	}
};


// link picker
var UrlPicker = function(_refer, _cbo, _title) {
	this.picker_id = "_url_picker";
	this.refer = _refer;
	this.cbo = _cbo;
	this.title = _title;

	this.make_sure_no_same_picker();
	this.worker = this.hire();
};

UrlPicker.prototype = {
	make_sure_no_same_picker: function() {
		var _exists_picker = document.getElementById(this.picker_id);
		if(_exists_picker) _exists_picker.parentNode.removeChild(_exists_picker);
	},

	hire: function() {
		var _worker = document.createElement("div");
		_worker.id = this.picker_id
		_worker.style.zIndex = "999"
		_worker.className = "toolbar_plate";
		_worker.style.position = "absolute";
		_worker.style.overflow = "auto";
		_worker.style.left = "-1024px";
		_worker.style.top = "-768px";

		var _closer = document.createElement("div");
		_closer.className = 'picker_closer';
		_closer.innerHTML = '<img src="/images/btn_x_small.gif">';
		// _closer.type = _closer.className = "button";
		// _closer.value = "close";
		_worker.appendChild(_closer);

		var _title = document.createElement("h3");
		_title.className = 'picker_title';
		_title.innerHTML = this.title;
		_worker.appendChild(_title);

		var _content = document.createElement("div");
		_content.className = 'picker_content';

		var _input = document.createElement("input");
		_input.type = "text";
		_input.className = "input";
		_content.appendChild(_input);
		this.input = _input;

		var _button = document.createElement("div");
		_button.className = "picker_button";
		_button.innerHTML = '<img src="/images/btn_apply.gif">';
		_content.appendChild(_button);

		_worker.appendChild(_content);


		document.body.appendChild(_worker);
		MoveToMousePos(_worker, this.refer.mouse_xy);

		//OnEvent.add(_button, "click", this.final_value);
		OnEvent.add(_button, "click", BindEvent(this, this.cbo, [WordCaps(_worker.id), this.refer]));
		OnEvent.add(_closer, "click", Bind(this, this.fire));

		_input.focus();
		return _worker;
	},

	fire: function() {
		this.worker.parentNode.removeChild(this.worker);
	},

	final_value: function() {
		var url = null;
		if(!this.input.value || this.input.value.is_empty()) return null;
		var stdin_url = this.input.value;
		if(/^http(s)?:\/\//i.exec(stdin_url)) url = stdin_url;
		else url = "http://" + stdin_url;

		return url;
	}
};

// insert embed html
var EmbedPicker = function(_refer, _cbo) {
	this.picker_id = "_embed_picker";
	this.refer = _refer;
	this.cbo = _cbo;

	this.make_sure_no_same_picker();
	this.worker = this.hire();
};

EmbedPicker.prototype = {
	make_sure_no_same_picker: function() {
		var _exists_picker = document.getElementById(this.picker_id);
		if(_exists_picker) _exists_picker.parentNode.removeChild(_exists_picker);
	},

	hire: function() {
		var _worker = document.createElement("div");
		_worker.id = this.picker_id
		_worker.className = "toolbar_plate";
		_worker.style.position = "absolute";
		_worker.style.overflow = "auto";
		_worker.style.left = "-1024px";
		_worker.style.top = "-768px";

		var _textarea = document.createElement("textarea");
		_textarea.className = "toolbar_textarea";
		_textarea.cols = 45;
		_textarea.rows = 6;
		_worker.appendChild(_textarea);
		this.textarea = _textarea;

		var _button = document.createElement("button");
		_button.innerHTML = "Insert";
		_worker.appendChild(_button);

		var _closer = document.createElement("button");
		_closer.innerHTML = "close";
		_worker.appendChild(_closer);


		document.body.appendChild(_worker);
		MoveToMousePos(_worker, this.refer.mouse_xy);

		//OnEvent.add(_button, "click", this.final_value);
		OnEvent.add(_button, "click", BindEvent(this, this.cbo, [WordCaps(_worker.id), this.refer]));
		OnEvent.add(_closer, "click", Bind(this, this.fire));

		_textarea.focus();
		return _worker;
	},

	fire: function() {
		this.worker.parentNode.removeChild(this.worker);
	},

	final_value: function() {
		if(!this.textarea.value || this.textarea.value.is_empty()) return null;

		var stdin_html = this.textarea.value;

		var embed = /<embed[^>]+?>/i.exec(stdin_html);
		if(!embed || embed.is_empty()) {
			alert("Only embed tag allowed");
			return;
		}

		var src = /src=['"]?([^\s'"]+)/i.exec(embed).last();
		if(!src || src.is_empty()) {
			alert("Weird embed tag. no valid src");
			return;
		}

		var width = /width=['"]?([0-9]+?)/i.exec(embed).last();
		if(!width || width.is_empty) width = zConf.EMBED_WIDTH;

		var height = /height=['"]?([0-9]+?)/i.exec(embed).last();
		if(!height || height.is_empty) height = zConf.EMBED_HEIGHT;

		var wrapper_img = "<p><img width='" + width + "' height='" + height + "' embed_wrapper' src='/images/insert_area_250.gif' longdesc='" + src + "'></p>";

		return wrapper_img;
	}
};

var ExportPicker = function(_refer, _cbo) {
	this.picker_id = "_export_picker";
	this.refer = _refer;
	this.cbo = _cbo;
	// this.prevent_auto_close = true;

	this.make_sure_no_same_picker();
	this.worker = this.hire();
}

ExportPicker.prototype = {
	make_sure_no_same_picker: function() {
		var _exists_picker = document.getElementById(this.picker_id);
		if(_exists_picker) _exists_picker.parentNode.removeChild(_exists_picker);
	},

	hire: function() {
		var _worker = document.createElement("div");
		_worker.id = this.picker_id
		_worker.className = "toolbar_plate";
		_worker.style.position = "absolute";
		_worker.style.overflow = "auto";
		_worker.style.left = "-1024px";
		_worker.style.top = "-768px";

		var _actions = [
			{ name: "Blogger" },
			{ name: "WordPress" },
			{ name: "Moveable-Type" },
			{ name: "Typo3" },
			{ name: "Tistory" },
			{ name: "Google Docs" }
		];
		var _buttons = document.createElement("ul");
		_buttons.className = "exportpicker";
		_worker.appendChild(_buttons);

		for(var i=0; i<_actions.length; i++) {
			var _button = document.createElement("li");
			_button.className = _actions[i].action || _actions[i].name
			_button.innerHTML = _actions[i].name;
			_buttons.appendChild(_button);

			OnEvent.add(_button, "click", BindEvent(this, this.cbo, [WordCaps(_worker.id), this.refer]));
			OnEvent.add(_button, "mouseover", function() {
				this.style.color = "white";
				this.style.backgroundColor = "#999";
			});

			OnEvent.add(_button, "mouseout", function() {
				this.style.color = "";
				this.style.backgroundColor = "";
			});
		}

		var _footer = document.createElement("div");
		_footer.style.padding = "5px 0px";
		_footer.style.textAlign = "center";
		_worker.appendChild(_footer);

		var _closer = document.createElement("input");
		_closer.type = _closer.className = "button";
		_closer.value = "close";
		_footer.appendChild(_closer);


		document.body.appendChild(_worker);
		MoveToMousePos(_worker, this.refer.mouse_xy);

		OnEvent.add(_closer, "click", Bind(this, this.fire));

		return _worker;
	},

	fire: function() {
		this.worker.parentNode.removeChild(this.worker);
	},

	final_value: function(_event) {
		return _event._node().className;
	}
};
