wmp.provide("wmp.editor");

wmp.editor = new Class(
{
version: "0.8",
initialize: function(options) {
	this.options = {
		onStop: null,
		onStart: null,
		height: null,
		css: ["/static/content.css", "/static/editor-content.css"],
		spellcheck: true,
		relativeUrlPrefix: "",
		modules: {},
		undolevels: 50,
		dontstart: false,
		pathbar: false,
		warnOnUnsavedChanges: false,
		gui: {
			maxFormatters: 6,
			maxAdd: 6
		},
		helpurl: "/portal/article/editor.html",
		resizable: true,
		unknownElements: false
	}
	this.options.startuphelp = '<p>Mit diesem <a href="'+this.options.helpurl+'" target="_blank">Editor</a> können <b>strukturierte und semantische Dokumente</b> visuell verfasst werden.</p><p>Die <a href="'+this.options.helpurl+'" target="_blank">Anleitung zum Editor</a> hilft bei der Bedienung</p><p>Absätze, Listen, Bilder, Codeblöcke, ... fügen Sie mit den obigen Schaltflächen hinzu.</p>'
	this.modules = {
		//classes
		loaded: [],
		other: ["br","body","tr","td","li", "pi","xmlcomment", "unknown"], //"div", "", "span", "img"
		add: ["p","h","list","figure","blockquote","blockcode", "wmpblock", "table", "compatibility", "pitoc","pinews","piarticles"],
		inline: ["a","strong","em","code","cite","abbr","ins","del","var","samp","sup","sub", "toolname","kbd"],
		classes: wmp.editor.modules,
		//instances
		active: [],
		focused: [],
		typeing: null,
		//pattern
		pattern: []
	}
	this.elements = {};
	this.options = $merge(this.options , options);
	this.modules = $merge(this.modules, this.options.modules);
	this.modules.loaded = this.modules.other.concat(this.modules.add, this.modules.inline);
	
	if (Browser.Engine.trident4) {
		this.addBrowserError();
		return;
	}
	if (Browser.Engine.trident) {
		this.addBrowserError();
		return;
	}
	if (Browser.Engine.webkit419 && !this.browserWarningShown) {
		this.addStartButton();
		this.addBrowserError();
		this.browserWarningShown = true;
		return;
	}
	if (this.options.dontstart) {
		//just add the button and then exit
		this.addStartButton();
		delete(this.options.dontstart);
		return;
	}
	
	this.menus = {};
	this.undoqueue = [];
	
	if (this.active)
		return;
	this.active = true;
	this.createGui();
	this.writeDocument();
},

afterInit: function() {
	this.importXml();
	this.createGuiMenus();
	this.createGuiInsert($(this.options.field));
	if (this.options.onStart)
		this.options.onStart();
},

writeDocument: function() {
	//FF: wenn das teil nicht wo angehängt wird, dann gibt es .contentWindow nicht
	//FF: wenn das teil nochmal wo angehängt wird, dann wird verschwindet der inhalt des iframes
	
	if (!this.iframe || !this.iframe.contentWindow || !this.iframe.contentWindow.document) {
		setTimeout(this.writeDocument.bind(this), 50);
		return;
	}
	this.doc = this.iframe.contentWindow.document;
	
	var cont = '';
	cont += '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' + "\n";
	cont += '<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
	cont += '<title>wmp.editor</title>';
	// FF: wenn styles direkt per link eingebunden werden gibt es nen fehler
	//cont += '<link rel="stylesheet" href="' + cssUrl + '" type="text/css" />';
	cont += '<style type="text/css">';
	for (var c=0; c < this.options.css.length; c++)
		cont += '@import url(' + this.options.css[c] + ');';
	cont += '</style>';
	cont += '</head><body id="content">';
	cont += '</body></html>';
	this.doc.open();
	this.doc.write(cont);
	this.doc.close();
	
	this.init();
},



init: function() {
	this.body = $(this.doc.getElementsByTagName("body")[0]);
	if (!this.body || !this.body.nodeName) {
		setTimeout(this.init.bind(this), 50);
		return;
	}
	this.hasContentEditable = typeof document.getElementsByTagName("body")[0].contentEditable != "undefined";
	if (!this.hasContentEditable) {
		try {
			this.doc.designMode = "on";
		}
		catch (e) {
			setTimeout(this.init.bind(this), 50);
			return;
		}
	}
	this.body.contentEditable = true;
	this.init2();
},

init2: function() {
	try {
		this.body.nodeName;
		if (!this.doc || !this.doc.addEvent) //ff error
			return setTimeout(this.init2.bind(this), 50);
	}
	catch (e) {
		return setTimeout(this.init2.bind(this), 50); //ie error
	}
	this.body.spellcheck = this.options.spellcheck;
	try {this.doc.execCommand("useCSS", false, false);} catch (e) {}
	try {this.doc.execCommand("styleWithCSS", false, false);} catch (e) {}
	try {this.doc.execCommand('enableInlineTableEditing', false, false);} catch (e) {}
	try {this.doc.execCommand('enableObjectResizing', false, false);} catch (e) {}
	try {this.doc.execCommand('LiveResize', false, false);} catch (e) {}
	try {this.doc.execCommand('MultipleSelection', false, "false");} catch (e) {}
	try {this.doc.execCommand('2D-Position', false, false);} catch (e) {}
	try {this.doc.execCommand('SizeToControl', false, false);} catch (e) {}
	//insertBrOnReturn,DisableEditfocus
	if (document.createEvent && Browser.Engine.gecko) {
		//FF bugs verhindern (erste eingabe komisch, br am anfang)
		this.doc.execCommand('inserthtml', false, "a");
	}
	if (Browser.Engine.trident) { //remove borders around elements
		var ns = ["h1","h2","h3","h4","h5","blockquote"], c=0, n;
		while (n=ns[c++])
			this.doc.styleSheets[0].addRule(n, "overflow: visible !important; min-height: auto !important; min-width: auto !important;");
	}
	//Opera braucht keypress um die default action zu verhindern
	//FF2 unter Linux feuert keydown nur einmal
	//IE sendet kein keypress für nicht druckbare zeichen
	this.win = this.iframe.contentWindow;
	this.doc.addEvent("keyup", this.keyup.bind(this));
	this.doc.addEvent("keydown", this.keydown.bind(this));
	this.doc.addEvent("keypress", this.keypress.bind(this));
	this.doc.addEvent("mouseup", this.mouseup.bind(this));
	this.doc.addEvent("mousedown", this.mousedown.bind(this));
	this.doc.addEvent("click", this.click.bind(this));
	this.doc.addEvent("focus", this.onFocus.bind(this));
	
	this.afterInit();
},

stop: function() {
	if (!this.active)
		return;
	this.saveXml();
	clearTimeout(this.checker);
	this.active = false;
	this.doc.removeEvents();
	this.iframe.dispose();
	this.removeGui();
	if (this.options.onStop)
		this.options.onStop();
},

/* events --------------------------------------------------------------------*/
onFocus: function(event) {
},

keyup: function(event) {
	if (!this.isEditable(event.target))
		return event.preventDefault();
	this.periodicCheckDelayed("keyup");
	if (event.control && (event.key == "v" || event.key == "x"))
		this.periodicCheckDelayed("keyuppaste",10);

},

mouseup: function(event) {
	//console.log("mouseup");
	//return;
	if (!this.isEditable(event.target)) {
		this.getModulesAtCursor(event.target);
		//todo: focus only if it is not inside this node
		this.modules.focused[0].focus();
		return event.preventDefault();
	}
	this.periodicCheck("mouseup");
},

mousedown: function(event) {
	//console.log("mousedown");
	//return;
	if (!this.isEditable(event.target)) 
		event.preventDefault();
},

click: function(event) {
	//console.log("click");
	//return;
	if (!this.isEditable(event.target))
		event.preventDefault();
},

keydown: function(event) {
	//ie and safari do not sent keypress for not printable chars
	if (!Browser.Engine.trident && !Browser.Engine.webkit)
		return;
	if (event.key == "tab") {
		//todo: do this for all not printble chars (event.code<40&&event.key!="space")
		this.keypress(event);
	}
},

keypress: function(event) {
	if (!this.sel) this.sel = wmp.selection.getInfo(this.doc);
	//console.log("keypress: ", event.key, event.code);
	this.changed = true;
	var m,x=this.modules.focused.length;
	while(m = this.modules.focused[--x])
		if (!m.handleKeypress(event)) {
			console.log("keypress: module ", m.title, " returned");
			return;
		}
	
	//preventDefault action: browser have actions on this keys in edit mode
	if (event.control && ["i", "b", "u"].contains(event.key))
		event.preventDefault();
	
	if (event.code == 17 || event.code == 16) //only control or shift was pressed;
		return;
	
	if (event.key == "tab") {
		if (event.shift)
			var alt = this.getPrevBlock(this.modules.innerBlock);
		else
			var alt = this.getNextBlock(this.modules.innerBlock);
		if (alt) 
			alt.focus(); // jump to the next/prev block
		else
			return; //no alternative => take the focus out of the editor...
		event.preventDefault();
		return;
	}
	else if (event.meta && (event.key == "left" || event.key == "right")) {
		//mac: stop jumping back/forward
		event.preventDefault();
		return;
	}
	if (!this.isEditable(this.sel.node) && ![33,34,35,36].contains(event.code) && !["up","down","left","right","tab"].contains(event.key)) {
		//[33,34,35,36] pageup,pagedown,end,pos1
		//if (event.key == "up")this.getPrevBlock(this.modules.innerBlock).focus();
		event.preventDefault();
		return;
	}
},

focuschange: function(type) {
	this.oldsel = this.sel;
	this.sel = wmp.selection.getInfo(this.doc);
	//console.log("focuschange", type);
	//return;
	//check if the same node is still selected, todo: geht ned 100%ig
	var n = (this.oldsel && this.oldsel.node),n2=this.sel.node, same=true;
	while (true) {
		if (!n || !n2 || n!=n2 || n.nextSibling!=n2.nextSibling) {
			same=false;
			break;
		}
		if (n.nodeName.toLowerCase()=="body")
			break;
		n=n.parentNode;
		n2=n2.parentNode;
	}
	this.getModulesAtCursor();
	if (!same) {
		if (this.options.pathbar)
			this.showPathBar();
	}
	
	
	//check which format buttons are turned on, todo: this takes up too much power
	
	var get_names_array = function(e,index){
		return e.nodeName.toLowerCase();
	}
	var ns = wmp.xml.getNodesInRange(this.sel.range.startContainer, this.sel.range.endContainer);
	//get the container of the start and endpoint
	var cs_s = wmp.xml.getAncestors(this.sel.range.startContainer);
	var cs_e = wmp.xml.getAncestors(this.sel.range.endContainer);
	ns.extend(cs_s);
	ns.extend(cs_e);
	var cs_s_names = cs_s.map(get_names_array);
	var cs_e_names = cs_e.map(get_names_array);
	var ns_names = ns.map(get_names_array);
	
	var a_s = cs_s_names.contains("a");
	var a_e = cs_e_names.contains("a");
	//if "<a>" is partly selected, disable all
	//todo: das hier ist noch falsch
	if ((a_s && !cs_e.contains(a_s)) || ((a_e && !cs_s.contains(a_s))))
		var a_partly = true;
	
	for (var n in this.inlineFormatters) {
		var nodeInRange = ns_names.contains(this.modules.classes[n].getStatic("nodeName"));
		var menuItem = this.inlineFormatters[n];
		if (nodeInRange) {
			menuItem.activate();
			menuItem.enable();
		}
		else {
			menuItem.deactivate();
			if (!a_partly && this.modules.typeing && this.modules.focused[0].isInlineChildAllowed(n))
				menuItem.enable();
			else
				menuItem.disable();
		}
	}
},

isEditable: function(node) {
	//todo: optimize: look only in topmost block
	var n = node || (this.sel && this.sel.node),u;
	while (n && n.nodeName.toLowerCase()!="body") {
		u = n.contentEditable;
		//console.log("isEditable", n, u);
		if (u == "false" || u===false)
			return false;
		if (u == "true" || u===true)
			return true;
		 n = n.parentNode;
	}
	return true;
},



getModulesAtCursor: function(node) {
	var node = node || this.sel.node;
	var blocksShown = 0;
	var newModules = {
		focused: [],
		active: [],
		typeing: null,
		innerBlock: null
	};
	while (node) {
		if (node.nodeName.toLowerCase() == "html")
			break;
		var module = this.getModule(node);
		if (module) {
			if (module.fix())
				break; //was removed
			newModules.focused.push(module);
			if (!newModules.innerBlock && module.type == "block")
				newModules.innerBlock = module;
			if (blocksShown<2) {
				newModules.active.unshift(module);
				//todo: show only topmost blocks
				if (module.type != "inline")
					blocksShown++;
			}
			
			if (!newModules.typeing && (module.legalChilds.contains("inline") || module.legalChilds.contains("text"))) {
				newModules.typeing = module;
			}
		}
		node = node.parentNode;
	}
	if (!newModules.innerBlock)
		newModules.innerBlock = this.getModule(this.body);
	var c=0,m;
	while(m = this.modules.active[c++]) {
		if (!newModules.active.contains(m))
			m.guideactivate();
	}
	var c=0,m;
	while(m = newModules.active[c++]) {
		m.guiactivate();
	}
				
	/*if (this.modules.typeing != newModules.typeing) {
		if (this.modules.typeing)
			this.modules.typeing.node.removeClass("editor-highlight");
		if (newModules.typeing && newModules.typeing.name!="body")
			newModules.typeing.node.addClass("editor-highlight");
	}*/
	this.modules.focused = newModules.focused;
	this.modules.active = newModules.active;
	this.modules.typeing = newModules.typeing;
	this.modules.innerBlock = newModules.innerBlock;
},

getModule: function(node) {
	if (!node || node.nodeType != 1)
		return null;
	//console.log("getModule", node);
	//already defined
	if ($(node)._module) {
		if (node._module.node == node)
			return node._module;
		else {
			node._module.kill();
			delete(node._module);
			console.warn("getModule: nodenotcorrect");
		}
	}
	//is it part of a parent?
	var pmod;
	if (node.parentNode && (pmod = this.getModule(node.parentNode))) {
		if (pmod.blobContent) {
			return null;
		}
		for (var nn in pmod.nodes) {
			if (pmod.nodes[nn] == node) {
				//console.log("getModule: part of parent", node);
				return null;
			}
		}
	}
	var cm = this.findModule(node);
	if (cm)
		return new cm({node: node, editor: this});
	return null;
},
findModule: function(node) {
	var n, c=0, tag = node.nodeName.toLowerCase(),cm,cs,t,cl;
	var hasClass =  function(className){
		return (node.getAttribute("class")||"").contains(className, ' ');
	}
	while(n = this.modules.loaded[c++]) {
		var m = this.modules.classes[n];
		
		if (!cm || (m.getStatic("specificity")||0)>(cm.getStatic("specificity")||0)) { //is it more specific than a previous match?
			if (t=m.getStatic("testNode")) {
				if (t(node))
					cm = m;
			}
			else if (m.getStatic("nodeName") == tag || (m.getStatic("nodeNames") && m.getStatic("nodeNames").contains(tag))) {
				if (cl=m.getStatic("nodeClass")) {
					if (hasClass(cl))
						cm = m;
				}
				else
					cm = m;
			}
			if (m.getStatic("html5")) {
				if (tag == "div") //todo: ((m.getStatic("type")=="block" ? "div":"span"))
					if (hasClass(m.getStatic("nodeName")))
						cm = m;
			}
		}
	}
	return cm;
},
getModuleConvert: function(node) {
	if (!node || node.nodeType != 1)
		return null;
	var n,c=0;
	while(n = this.modules.loaded[c++]) {
		var prot = this.modules.classes[n].prototype;
		if (prot.convertElement && prot.name != "unknown") {
			var newnode = prot.convertElement($(node), this);
			if (newnode) {
				console.log("converted " + node.get('tag'));
				return this.getModule(newnode);
			}
		}
	}
	if (this.options.unknownElements) {
		var newnode = wmp.editor.modules.unknown.prototype.convertElement($(node), this);
		//console.log('converted unkown tag "' + node.get('tag') + '"');
		return this.getModule(newnode);
	}
},

removeBlock: function() {
	//console.log("removeBlock", this.modules.innerBlock.title);
	if (this.modules.innerBlock && this.modules.innerBlock.type != "body") {
		var alt = this.getPrevBlock(this.modules.innerBlock) || this.getNextBlock(this.modules.innerBlock);
		this.modules.innerBlock.remove();
		if (alt)
			alt.focus();
	}
},

moveBlockUp: function(block) {
	var allowed, mod = (block && !block.event ) ? block : this.modules.innerBlock;
	if (mod && mod.type != "body") {
		this.saveSelection(); 
		var m = this.getModule(mod.node.getPrevious()); //this.getPrevBlock(mod);
		if (m)
			mod.node.inject(m.node, m.isChildAllowed(mod) ? '' : 'before');
		else if (allowed = this.getParentWhereAllowed(mod))
			mod.node.inject(allowed[1].node,'before');
		this.restoreSelection();
	}
},

moveBlockDown: function(block) {
	var allowed, mod = (block && !block.event ) ? block : this.modules.innerBlock;
	if (mod && mod.type != "body") {
		this.saveSelection();
		var m = this.getModule(mod.node.getNext()); //this.getNextBlock(mod);
		if (m)
			mod.node.inject(m.node, m.isChildAllowed(mod) ? 'top' : 'after');
		else if (allowed = this.getParentWhereAllowed(mod))
			mod.node.inject(allowed[1].node,'after');
		this.restoreSelection();
	}
},

getNextBlock: function(module) {
	if (!module) return;
	var node = module.node;
	while (node = wmp.xml.nextNode(node)) {
		var mod = this.getModule(node);
		if (mod && mod.type == "block")
			return mod;
	}
	return;
},
getPrevBlock: function(module) {
	if (!module) return;
	var node = module.node;
	while (node = wmp.xml.prevNode(node)) {
		var mod = this.getModule(node);
		if (mod && mod.type == "block")
			return mod;
	}
	return;
},
getParentWhereAllowed: function(module) {
	var node = module.node.parentNode;
	var c = this.getModule(node);
	while (node = node.parentNode) {
		var mod = this.getModule(node);
		if (mod && mod.isChildAllowed(module))
			return [mod,c];
		var c=mod;
	}
	return null;
},

formatInline: function(event, nodeName, className) {
	this.wrap(nodeName, className);
	event.preventDefault();
},


_importNode: function(node, deep) {
	var d = this.doc;
	switch (node.nodeType) {
		case document.ELEMENT_NODE:
			var cm = this.findModule(node),xml2html; 
			if (cm && (xml2html=cm.getStatic("xml2html"))) {
				return xml2html(node,this);
			}
			var newNode = this.c(node.nodeName);
			var className = newNode.getAttribute("class");
			if (node.attributes && node.attributes.length > 0)
				for (var i = 0, il = node.attributes.length; i < il;)
					newNode.setAttribute(node.attributes[i].nodeName, node.getAttribute(node.attributes[i++].nodeName));
			if (deep && node.childNodes && node.childNodes.length > 0)
				for (var i = 0, il = node.childNodes.length; i < il;)
					newNode.appendChild(this._importNode(node.childNodes[i++], deep));
			if (className)
				newNode.addClass(className);
			return newNode;
			break;
		case document.PROCESSING_INSTRUCTION_NODE:
			return wmp.editor.modules.pi.getStatic("xml2html")(node,this);
			break;
		case document.TEXT_NODE:
		case document.CDATA_SECTION_NODE:
			return d.createTextNode(node.nodeValue);
			break;
		case document.COMMENT_NODE:
			return wmp.editor.modules.xmlcomment.getStatic("xml2html")(node,this);
			break;
	}
},
isHtml5: function(nodeName) {
	return ["figure", "legend", "blockcode", "compatibility", "downloads"].contains(nodeName);
},
c: function(nodeName, args) {
	this.canhtml5=false;
	if(!this.canhtml5 && this.isHtml5(nodeName)) {
		//html5 element
		var n =  $(this.doc.createElement("div"));
		n.className = nodeName;
	}
	else
		var n =  $(this.doc.createElement(nodeName));
	if (args)
		n.set(args);
	return n;
},

ct: function(value) {
	return this.doc.createTextNode(value);
},

focus: function() {
	console.log("focusomat");
	if (!this.iframe || !this.iframe.contentWindow)
		return;
	if (Browser.Engine.presto) {
		//Opera cannot focus the window. So focus the body, but that scrolls an kills the selection
		//var y = this.iframe.contentWindow.pageYOffset;
		try {
			var sel = wmp.selection.save(this.doc);
			var node = (this.sel&&this.sel.range&&this.sel.range.startContainer)||this.body;
			if (node.nodeType!=1)
				node=node.parentNode;
			node.focus()
			wmp.selection.restore(sel);
		}
		catch(e) {}
		//this.iframe.contentWindow.scrollTo(0, y);
	}
	else
		this.iframe.contentWindow.focus();
},

scrollTo: function(node) {
	//console.log('scrollTo', node);
	if (!this.iframe.contentWindow || !this.iframe.contentWindow.getWindow)
		return console.warn("scrollTo: no window");
	var w = this.iframe.contentWindow.getWindow();
	var btop = w.getScroll().y;
	var bheight = this.iframe.getCoordinates().height;
	var bbottom = btop + bheight;
	
	var coords = $(node).getCoordinates();
	var ntop = coords.top;
	var nbottom = ntop + coords.height;
	
	if (ntop<btop)
		var y = ntop - (bheight/4);
	else if (nbottom>bbottom)
		var y = nbottom - bheight + (bheight/4);
	if (y||y===0) {
		//console.log("scrollomat", w, y);
		new Fx.Scroll(w).set(0,y);
		//w.scrollTo(0,y);
		//module.node.scrollIntoView(false);
		//new Fx.Scroll(w).toElement(node);
	}
	//console.log("scroll", node, "bheight:",bheight,"btop:",btop, "bbottom:",bbottom,"ntop:",ntop,"nbottom:",nbottom );
},
selectNode: function(node, options) {
	if (!wmp.xml.isInPage(node))
		return;
	//console.log("selectNode", node, options||"");
	this.scrollTo(node);
	wmp.selection.selectNode(node, options);
	this.focuschange("selectNode");
},

saveSelection: function() {
	//console.log("saveSelection");
	this.savedSelection = wmp.selection.save(this.doc);
},

restoreSelection: function(sel, fallbackNode) {
	//console.log("restoreSelection");
	//the fallbackNode is used when the normal node is no longer in the document
	var sel = sel || this.savedSelection;
	if (!sel.startContainer.parentNode)
		sel.startContainer = fallbackNode;
	if (!sel.endContainer.parentNode)
		sel.endContainer = fallbackNode;
	if (!sel.startContainer || !sel.endContainer)
		return;
	wmp.selection.restore(sel);
	this.focuschange("restoreSelection");
},


saveundo: function() {
	//console.log("saveundo");
	//return;
	//todo: type of undo
	if (this.undoqueue.length>0 && this.doc.body.innerHTML == this.undoqueue.getLast().html)
		return;
	this.undoqueue.push({'cursor':wmp.selection.saveInString(this.sel),'change':'typed','html':this.doc.body.innerHTML});
	//console.log("undo nr. " + this.undoqueue.length + " saved; cursor: " + this.undoqueue.getLast().cursor + "; html: "+ this.undoqueue.getLast().html);
	if (this.undoqueue.length>this.options.undolevels)
		this.undoqueue.shift();
	//idea: this.doc.body.clone(true) instead of innerHTML
},

undo: function() {
	if (this.undoqueue.length <= 2)
		return;
	this.undoqueue.pop();
	var u = this.undoqueue.pop();
	//console.log("undo nr. " + (this.undoqueue.length+1) + " restored; cursor: " + u.cursor + "; html: "+ u.html);
	this.doc.body.empty().innerHTML = u.html;
	var bmod = this.getModule(this.doc.body);
	bmod.fix();
	this.doc.body.normalize();
	wmp.selection.restoreFromString(u.cursor, this.doc);
},

wrap: function(nodeName,className) {
	//console.log("wmp.editor.wrap('"+nodeName+"')");
	var sel = this.sel = wmp.selection.getInfo(this.doc);
	var ns = wmp.xml.getNodesInRange(sel.range.startContainer, sel.range.endContainer);
	ns.extend(wmp.xml.getAncestors(sel.range.startContainer));
	ns.extend(wmp.xml.getAncestors(sel.range.endContainer));
	
	var c=0;
	while (n = ns[c++]) {
		if (n.parentNode && n.nodeName.toLowerCase() == nodeName) {
			var unwrappedNodes = wmp.xml.unwrapNode(n);
		}
	}
	
	if (unwrappedNodes) {
		this.restoreSelection({'startContainer': unwrappedNodes.start, 'startOffset': 0, 'endContainer': unwrappedNodes.end, 'endOffset': unwrappedNodes.end.length});
		//bug: FF: normalize removes the selection right after that
		return;
	}
	
	if (!sel.node || sel.selection.isCollapsed)
		return;
	
	var node = this.c(nodeName);
	if (className)
		node.className = className;
	sel.range.surroundContents(node);
	this.selectNode(node);
	var module = this.getModule(node);
	if (module.onFormatInline)
		module.onFormatInline();
},

addMenu: function(options) {
	options.editor = this;
	if (!options.container)
		options.container = this.elements["menu" + (options.position || "main")];
	if (!this.shortcuts)
		this.shortcuts = $H();
	options.shortcuts = this.shortcuts;
	return new wmp.editor.menu(options);
},

createGui: function() {
	if (this.options.warnOnUnsavedChanges) {
		Element.NativeEvents.beforeunload = 2; //todo:bugfix for mootools 1.2, can it be removed?
		this.warnOnUnsavedChanges_bound = this.warnOnUnsavedChanges.bind(this);
		if (Browser.Engine.webkit)
			window.onbeforeunload = this.warnOnUnsavedChanges_bound;
		else
			window.addEvent("beforeunload", this.warnOnUnsavedChanges_bound);
		//does not work in opera!
	}
	this.elements.container = new Element("div", {'class':'wmpe', 'styles':{'position':'relative', 'left': '-5000px', 'height': '1px'}});
	if (Browser.Engine.trident)
		this.elements.container.addClass("wmpe-ie");
	this.elements.menubar = new Element("div", {"class": "wmpe-menubar wmpe-menutop"}).inject(this.elements.container);
	this.elements.content = new Element("div", {"class": "wmpe-content"}).inject(this.elements.container);
	this.elements.menumain = new Element("div", {"class": "wmpe-menumain wmpe-menuright"}).inject(this.elements.content);
	this.elements.container.inject($(this.options.field), 'before');
	this.iframe = new IFrame({'src':'','class': 'wmpe-iframe','frameBorder': '0', 'styles':{'height':1}}).inject(this.elements.content);
},
createGuiInsert: function(xmlfield) {
	//height
	if (!this.options.height)
		this.options.height = xmlfield.getCoordinates().height;
	if (this.options.height<90)
		this.options.height = 90;
	this.resize();
	//submit event
	this.onformsubmit_bound = this.onFormSubmit.bind(this);
	$(xmlfield.form).addEvent("submit", this.onformsubmit_bound);
	//startbutton
	if (!this.startbutton)
		this.addStartButton();
	this.startbutton.style.display = "none";
	//field
	xmlfield.style.display = "none";
	//show
	//if i apply visibility, display or position, the contentEditable area will get uneditable
	this.elements.container.set('styles',{'height': '', 'left': ''});
},
onFormSubmit: function() {
	this.changed = false;
	this.saveXml()
},

resize: function() {
	this.iframe.setStyle("height", this.options.height-40);
},

createGuiMenus: function() {
	
	//inline formatters
	var menu = this.menus.format = this.addMenu({title:"Formatieren", editor: this, position:"bar",type: "icons", className: "format"});
	this.inlineFormatters = {}; 
	var n,c=0;
	while (n = this.modules.inline[c++]) {
		if (c==this.options.gui.maxFormatters+1)
			var menu = menu.addItem({title:"weitere Formatierungen", icon: '/editor/16x16/list-add.png'});
		var m = this.modules.classes[n];
		var func = this.formatInline.bindWithEvent(this, [m.getStatic("nodeName"),m.getStatic("nodeClass")]);
		var i = menu.addItem({title:m.getStatic("title"), func:func, icon: "editor/22x22/" + m.getStatic("icon"), shortcut: m.getStatic("shortcut")});
		this.inlineFormatters[n] = i;
	}
	
	//add elements
	var menu = this.menus.add = this.addMenu({title:"Elemente", editor: this, position:"bar", type: "icons", className: "add"});
	var n,c=0;
	while (n = this.modules.add[c++]) {
		if (c==this.options.gui.maxAdd+1)
			var menu = menu.addItem({title:"weitere Elemente hinzufügen", icon: '/editor/16x16/list-add.png'});
		var m = this.modules.classes[n];
		menu.addItem({title:m.getStatic("title"), func:this.clickModuleButton.bind(this, n), icon: m.getStatic("icon") || ('/editor/editors/' + (m.getStatic("name")||n) + '.png'), shortcut: m.getStatic("shortcut")});
	}
	
	//misc 
	this.menus.misc = this.addMenu({title:"Editor", editor: this, position:"bar", type: "icons", className: "misc"});
	this.menuitems = {
		del:	this.menus.misc.addItem({title:'Löschen', func:this.removeBlock.bind(this), icon: "editor/16x16/edit-delete.png", shortcut:"delete"}),
		moveup: this.menus.misc.addItem({title:'Nach oben verschieben', func:this.moveBlockUp.bind(this), icon: "editor/16x16/go-up.png", shortcut:"up"}),
		movedown: this.menus.misc.addItem({title:'Nach unten verschieben', func:this.moveBlockDown.bind(this), icon: "editor/16x16/go-down.png", shortcut:"down"}),
		undo:	this.menus.misc.addItem({title:'Rückgängig', func:this.undo.bind(this), icon: "editor/16x16/edit-undo.png", shortcut:"z"})
		//test:	this.menus.misc.addItem({title:'Test', func:this.test.bind(this)})
	}
	
	// exit
	this.menus.exit = this.addMenu({title:"Editor", editor: this, position:"bar", type: "icons", className: "exit"});
	this.menuitems.exit = this.menus.exit.addItem({title:'Quelltext anzeigen', func:this.stop.bind(this), icon: "editor/16x16/document-save.png"});
	
	// help
	if (this.options.startuphelp)
		this.showHelp(this.options.startuphelp);
	
	// resizable
	//todo
	// customizeGui
	if (this.customizeGui)
		this.customizeGui();
},

customizeGui: function() {
	this.elements.content.setStyle("padding-right", 200);
},

showHelp: function(text) {
	this.menus.help = this.addMenu({title:"Einführung", editor: this});
	this.menus.help.addContent({text:text});
},

hideHelp: function() {
	if (this.menus.help)
		this.menus.help.remove();
	delete(this.menus.help);
},

removeGui: function() {
	for(var m in this.menus) {
		this.menus[m].remove();
	}
	this.menus = {}
	
	var c=0,m;
	while(m = this.modules.active[c++]) {
		m.guideactivate();
	}
	this.elements.container.dispose();
	this.options.field.style.display = "";
	if(this.options.field.form)
		$(this.options.field.form).removeEvent("submit", this.onformsubmit_bound);
	
	if (this.options.warnOnUnsavedChanges) {
		if (Browser.Engine.webkit)
			window.onbeforeunload = null;
		else
			window.removeEvent("beforeunload", this.warnOnUnsavedChanges_bound);
	}
	if (this.startbutton)
		this.startbutton.style.display = "";
	
	delete(this.elements);
},


showPathBar: function() {
	this.options.pathbar = true;
	var x=0,m,s;
	if (!this.elements.pathbar) {
		this.elements.pathbar = new Element("div", {"class": "wmpe-pathbar"}).inject(this.elements.container);
	}
	var pathbar = this.elements.pathbar;
	pathbar.innerHTML = "";
	while (m = this.modules.focused[x++]) {
		var a = new Element("a", {'text': m.title}).inject(pathbar, 'top');
		if (this.modules.focused[x])
			s = pathbar.insertBefore(document.createTextNode(" > "), pathbar.firstChild);
	}
},

hidePathBar: function() {
	this.options.pathbar = false;
	this.elements.pathbar.dispose();
	delete(this.elements.pathbar);
},

clickModuleButton: function(moduleName) {
	var mod = this.modules.innerBlock;
	var m = this.modules.classes[moduleName];
	if (!this.sel || this.sel.selection == "" || (Browser.Engine.trident && this.sel.selection.type == "None") || !mod.splittable || !m.getStatic("splittable")) {
		this.createModule(moduleName);
	}
	else {
		var n1 = mod.node;
		var n3 = wmp.xml.splitNode(this.sel.range.endContainer, this.sel.range.endOffset, n1.parentNode);
		var n2 = wmp.xml.splitNode(this.sel.range.startContainer, this.sel.range.startOffset, n1.parentNode);
		if (!wmp.xml.hasRealContent(n1))
			this.getModule(n1).remove();
		if (!wmp.xml.hasRealContent(n3))
			this.getModule(n3).remove();
		var newmod = new m({editor:this, node:n2, convert:true});
		newmod.focus();
	}
},

createModuleOnly: function(moduleName) {
	return new this.modules.classes[moduleName]({editor:this});
},

createModule: function(moduleName) {
	var module = this.createModuleOnly(moduleName);
	var c=0,m;
	while(m = this.modules.focused[c++]) {
		if (m.isChildAllowed(module)) {
			var parentNode = m.node;
			break;
		}
		var before = m.node;
	}
	if (before && parentNode) {
		module.node.inject(before, 'after');
	}
	else if (parentNode) {
		parentNode.appendChild(module.node);
	}
	else {
		this.body.appendChild(module.node);
	}
	module.focus();
	return module;
},

setContent: function(html) {
	/*
	var n,c=0;
	while(n = this.modules.loaded[c++]) {
		var conv = this.modules.classes[n].getStatic("convertOnSetContent");
		if (conv)
			html = conv(html);
	}
	this.body.innerHTML = html;
	*/
	var xml = wmp.xml.docFromString("<body>"+html+"</body>");
	var c,i=0;
	this.body.empty();
	while (c=xml.documentElement.childNodes[i++]) {
		//FF Bug: importNode does not convert nodes to HTML-DOM nodes (e.g. non className)
		//Versuche: document.load,adoptNode() in ff3
		var nc = this._importNode(c,true);
		this.body.appendChild(nc);
	}
	//this.doc.normalizeDocument();
	this.periodicCheck("initial");
},

periodicCheckDelayed: function(name,time) {
	clearTimeout(this.errorscanner);
	this.errorscanner = setTimeout(this.periodicCheck.bind(this,name),time||250);
},

periodicCheck: function(name) {
	if (!this.active)
		return;
	//autostop if editor not in the doc anymore
	if (!$(document.body).hasChild(this.elements.container))
		return this.stop();
	if (!this.doc || (!this.doc.defaultView && document.defaultView))
		return this.stop();
	
	//This should be executed seldom --> periodicCheck for timeout --> this is not called after every keytroke
	clearTimeout(this.checker);
	if (this.body) {
		var bmodule = this.getModule(this.body);
		if (bmodule.old_innerHTML != bmodule.node.innerHTML) {
			bmodule.fix();
			this.saveundo();
			this.saveXml();
		}
	}
	this.focuschange(name||"check");
	this.checker = setTimeout(this.periodicCheck.bind(this),2000);
},

importXml: function() {
	this.setContent(this.options.field.get('value'));
},

exportXml: function() {
	return wmp.xml.xmlizeSource(this.getModule(this.body).exportXml());
},

saveXml: function() {
	var indenter = new wmp.xml.indent();
	this.options.field.value = indenter.format(this.exportXml());
},

submit: function() {
	this.saveXml();
	this.options.field.form.submit();
},

warnOnUnsavedChanges: function(event) {
	var event = new Event(event);
	// Wollen Sie wirklich die Seite verlassen?
	var exitWarningMessage = "Sie haben die Änderungen noch nicht gespeichert!";
	if (this.changed) {
		event.stop();
		this.exportXml();
		return event.event.returnValue = exitWarningMessage;
	}
},

restart: function() {
	this.initialize(this.options);
},

addStartButton: function() {
	var but = this.startbutton = new Element("a", {"class":"wmpe-starteditor"});
	but.set('text', "Editor starten");
	but.addEvent("click", this.restart.bind(this));
	but.inject(this.options.field, 'before');
},

addBrowserError: function() {
	var but = new Element("div", {"class":"wmpe-smallerror"});
	//todo: remove later
	if (Browser.Engine.trident)
		but.set('html', 'Der <a href="'+this.options.helpurl+'" target="_blank">Editor</a> mit dem dieser Inhalt bearbeitet werden sollte, wird im Internet Explorer <b>zwischenzeitlich</b> nicht unterstützt. Bitte verwenden Sie solange Firefox, Opera oder Safari');
	if (Browser.Engine.trident4)
		but.set('html', 'Der <a href="'+this.options.helpurl+'" target="_blank">Editor</a> mit dem dieser Inhalt bearbeitet werden sollte, wird im Internet Explorer 6 nicht unterstützt, bitte holen sie sich einen <b>zeitgemäßen Browser</b> (Firefox, Opera, Safari)');
	else if (Browser.Engine.webkit419)
		but.set('html', 'Der <a href="'+this.options.helpurl+'" target="_blank">Editor</a> mit dem dieser Inhalt bearbeitet werden sollte, wird nicht im Safari2 unterstützt. Bitte verwenden Sie Safari3, Opera oder Firefox');
	else if (Browser.Engine.webkit && !Browser.Engine.webkit420)
		but.set('html', 'Der <a href="'+this.options.helpurl+'" target="_blank">Editor</a> mit dem dieser Inhalt bearbeitet werden sollte, wird nicht im Konqueror unterstützt. Bitte verwenden Sie Opera, Firefox oder Safari3');
	but.inject(this.options.field, 'before');
}


});

	
	
wmp.provide("wmp.editor.customize");
wmp.editor.customize.desc = new Class({
	Extends: wmp.editor,
	customizeGui: function() {
		if (this.menus.misc)
			this.menus.misc.hide();
		if (this.menus.add)
			this.menus.add.hide();
		this.elements.menumain.className = "wmpe-menumain";
		this.elements.menubar.appendChild(this.elements.menumain);
	}
});

wmp.editor.customize.comment = new Class({
	Extends: wmp.editor,
	customizeGui: function() {
		if (this.menus.misc)
			this.menus.misc.hide();
		this.elements.menumain.className = "wmpe-menumain wmpe-menutop";
	}
});

wmp.editor.customize.msg = new Class({
	Extends: wmp.editor,
	customizeGui: function() {
		if (this.menus.misc)
			this.menus.misc.hide();
		if (this.menus.add)
			this.menus.add.hide();
		this.elements.menumain.className = "wmpe-menumain wmpe-menutop";
		this.elements.menumain.setStyle("display", "none");
	}
});
