var filesCache = new Object();

function createXMLHttpRequest()
{
	if(window.XMLHttpRequest) return new XMLHttpRequest();
	else if(window.ActiveXObject)
	{
        try { return new ActiveXObject('Msxml2.XMLHTTP'); }
        catch (e) { return new ActiveXObject('Microsoft.XMLHTTP'); }
	}
}

function createDOMDocument()
{
	if(window.ActiveXObject)
	{
        try { return new ActiveXObject('Msxml2.DOMDocument.3.0'); }
        catch (e) { return new ActiveXObject('Msxml2.DOMDocument'); }
	}
	return document.implementation.createDocument("", "", null);
}

function getFrameDocument(frame)
{
	if(frame.contentWindow.document) return frame.contentWindow.document;
	else if(frame.contentDocument) return frame.contentDocument;
	return frame.document;
}

function getFrameWindow(frame)
{
	if(frame.contentWindow) return frame.contentWindow;
	return frame.window;
}

function transform(doc, stylesheetName)
{
	if(window.XSLTProcessor)
	{
		var xsltProcessor = new XSLTProcessor();
		try { xsltProcessor.importStylesheet(loadXml(stylesheetName)); }
		catch(e) {xsltProcessor.importStylesheet(loadNormalizedXslt(stylesheetName)); } // Opera
		return xsltProcessor.transformToDocument(doc);
	}
	else if(window.ActiveXObject)
	{
		var result = createDOMDocument();
		doc.transformNodeToObject(loadXml(stylesheetName), result);
		return result;
	}
}

// for Opera
function loadNormalizedXslt(fileName)
{
	var cacheKey = 'norm-xslt:' + fileName;
	if(filesCache[cacheKey] !== undefined) return filesCache[cacheKey];

	var doc = loadXml(fileName);
	var node = doc.documentElement.firstChild;
	while(node)
	{
		if(node.nodeType == 1 /* ELEMENT_NODE */ && node.nodeName.match(/(:|^)include$/i) && node.namespaceURI == 'http://www.w3.org/1999/XSL/Transform')
		{
			var includeElement = node;
			var ref = includeElement.getAttribute('href');

			if(fileName.lastIndexOf('/'))
			{
				var base = fileName.substr(0, fileName.lastIndexOf('/'));

				while(ref.indexOf('../') == 0)
				{
					base = base.substr(0, base.lastIndexOf('/'));
					ref = ref.substr(3);
				}

				ref = base + '/' + ref;
			}

			var childDoc = loadNormalizedXslt(ref);
			doc.documentElement.insertBefore(importChilds(childDoc.documentElement, doc), includeElement);
			node = includeElement.nextSibling;
			doc.documentElement.removeChild(includeElement);
		}
		else node = node.nextSibling;
	}

	return doc;
}

function loadXml(fileName)
{
	var cacheKey = 'xml:' + fileName;
	if(filesCache[cacheKey] !== undefined) return filesCache[cacheKey];

	if(window.ActiveXObject)
		try
		{
			 var doc = createDOMDocument();
			 doc.async = false;
			 doc.load(fileName);
			 return filesCache[cacheKey] = doc;
		}
		catch(e) {}

	var request = createXMLHttpRequest();
	request.open('GET', fileName, false);
	request.send(null);

	return filesCache[cacheKey] = request.responseXML;
}

function importChilds(element, doc)
{
	var fragment = doc.createDocumentFragment();
	for(var i = 0; i < element.childNodes.length; i++)
	{
		var childNode = element.childNodes[i];
		switch(childNode.nodeType)
		{
			case 1: // ELEMENT_NODE
				if(childNode.nodeName.indexOf('/') < 0) // IE!
					fragment.appendChild(importElement(childNode, doc));
				break;
 			case 3:  // TEXT_NODE
 				fragment.appendChild(doc.createTextNode(childNode.nodeValue));
 				break;
 			case 4:  // CDATA_SECTION_NODE
 				fragment.appendChild(doc.createCDATASection(childNode.data));
 				break;
 			case 5:  // ENTITY_REFERENCE_NODE
 			 	fragment.appendChild(doc.createComment('ENTITY_REFERENCE_NODE'));
 				break;
 			case 6:  // ENTITY_NODE
 				fragment.appendChild(doc.createComment('ENTITY_NODE'));
 				break;
 			case 7:  // PROCESSING_INSTRUCTION_NODE
 				fragment.appendChild(doc.createComment('PROCESSING_INSTRUCTION_NODE'));
 				break;
 			case 8:  // COMMENT_NODE
 				fragment.appendChild(doc.createComment(childNode.data));
 				break;
		}
	}

	return fragment;
}

function importElement(element, doc)
{
	try { var imported = element.namespaceURI ? doc.createElementNS(element.namespaceURI, element.nodeName.toLowerCase()) : doc.createElement(element.nodeName.toLowerCase()); }
	catch(e) { var imported = doc.createElement(element.nodeName.toLowerCase()); }

	for(var i = 0; i < element.attributes.length; i++)
	{
		var attribute = element.attributes[i];
		if(attribute.value != 'null' && attribute.value != '')
			imported.setAttribute(attribute.nodeName, attribute.value);
	}

	var fragment = importChilds(element, doc);
	if(fragment.childNodes.length) imported.appendChild(fragment);

	return imported;
}

function loadXmlFromString(string)
{
	if(window.DOMParser)
	{
		var parser = new DOMParser();
		return parser.parseFromString(string, "text/xml");
	}
	else if(window.ActiveXObject)
		try
		{
			 var doc = createDOMDocument();
			 doc.async = false;
			 doc.loadXML(string);
			 return doc;
		}
		catch(e) {}
}

function saveXml(doc)
{
	if(window.XMLSerializer)
	{
		var serializer = new XMLSerializer();
		try{ return XML(serializer.serializeToString(doc)).toXMLString(); }
		catch(e) { return serializer.serializeToString(doc); }
	}
	else
	{
		//var writer = new ActiveXObject("Msxml2.MXXMLWriter.4.0");
		//writer.indent = true;
		//writer.standalone = true;
		// return writer.output;

		//doc.encoding = 'utf-8';
		return doc.xml;
	}
}

function exportTextareaToFrame(textareaId, frameId)
{
	var textarea = document.getElementById(textareaId);
	var frame = document.getElementById(frameId);
	var frameDoc = getFrameDocument(frame);

	var doc = loadXmlFromString('<document xmlns="http://www.bestyle.ru/standards/aml/">' + textarea.value + '</document>');

	// TO FIX: parsererror
	doc = transform(doc, '/.style/vodoley/xhtml/richedit/aml2html.xslt');
	frameDoc.body.innerHTML = cutTagBody(saveXml(doc), 'html');
}

function cutTagBody(xml, tagName)
{
	var i = xml.indexOf('<' + tagName);
	if(i < 0) return xml;
	i = xml.indexOf('>', i + 1);
	if(xml.charAt(i - 1) == '/') return '';
	return xml.substring(i + 1, xml.lastIndexOf('</' + tagName + '>'));
}

function exportFrameToTextarea(frameId, textareaId)
{
	var frame = document.getElementById(frameId);
	var textarea = document.getElementById(textareaId);
	var frameDoc = getFrameDocument(frame);

	var frame = document.getElementById(frameId);
	var textarea = document.getElementById(textareaId);
	var frameDoc = getFrameDocument(frame);

	var doc = createDOMDocument();
	doc.appendChild(importElement(frameDoc.documentElement, doc));
	doc = transform(doc, '/.style/vodoley/xhtml/richedit/html2aml.xslt');

	textarea.value = cutTagBody(saveXml(doc), 'document');
}

function nodeOffset(node)
{
	return node.previousSibling ? 1 + nodeOffset(node.previousSibling) : 0;
}

function nodeLength(node)
{
	switch(node.nodeType)
	{
		case 3: return  node.nodeValue.length;
		case 4: // CDATA_SECTION_NODE
		case 8: // COMMENT_NODE
		default: return node.childNodes.length;
	}
}

function splitNode(node, offset, rootNode, right)
{
	if(node != rootNode)
	{
		var clone;
		if(!right && offset > 0 || right && offset < nodeLength(node))

		{
			clone = node.cloneNode(false);
			node.parentNode.insertBefore(clone, right ? node.nextSibling : node);

			switch(node.nodeType)
			{
				case 3  : // TEXT_NODE
					node.nodeValue = right ? node.nodeValue.substring(0, offset) : node.nodeValue.substring(offset);
					clone.nodeValue = right ? clone.nodeValue.substring(offset) : clone.nodeValue.substring(0, offset);
					break;
				case 4:  // CDATA_SECTION_NODE
				case 8: // COMMENT_NODE
					break;
				default:
					if(right) while(offset < node.childNodes.length) clone.appendChild(node.childNodes[offset]);
					else while(clone.childNodes.length < offset) clone.appendChild(node.firstChild);
			}
		}
		splitNode(node.parentNode, nodeOffset(node) + (right ? 1 : 0), rootNode, right);
	}
}

function explodeNode(node)
{
	var fragment = node.ownerDocument.createDocumentFragment();
	while(node.firstChild) fragment.appendChild(node.firstChild);
	node.parentNode.insertBefore(fragment, node);
	node.parentNode.removeChild(node);
}

function explodeChildNodes(node, pattern)
{
	for(var i = 0; i < node.childNodes.length; i++)
	{
		var child = node.childNodes[i];

		if(child.nodeType == 1 /* ELEMENT_NODE */)
			if(child.nodeName.match(pattern))
			{
				explodeNode(child);
				i--;
			}
			else explodeChildNodes(child, pattern);
	}
}

function onlyNodeSelected(range, pattern)
{
	if(range.startContainer.nodeType == 1 && range.startContainer == range.endContainer && range.endOffset - range.startOffset > 0)
	{
		for(var i = range.startOffset; i < range.endOffset; i++)
			if(range.startContainer.childNodes[i].nodeType != 1 || !range.startContainer.childNodes[i].nodeName.match(pattern)) return false;

		return true;
	}

	return false;
}

function onlyNodeSelectedBetween(range, pattern)
{
	if(range.startContainer.parentNode == range.endContainer.parentNode && range.startOffset == range.startContainer.length && range.endOffset == 0)
	{
		node = range.startContainer.nextSibling;
		while(node != range.endContainer)
		{
			if(node.nodeType != 1 || !node.nodeName.match(pattern)) return false;
			node = node.nextSibling;
		}

		return true;
	}
	return false;
}

function rangeRoot(range, pattern)
{
	var node = range.commonAncestorContainer;

	do{ if(node.nodeType == 1 && node.nodeName.match(pattern)) return node; }
	while(node = node.parentNode);

	return null;
}

function getSelectionRange(frame)
{
	// IE
	if(frame.document && frame.document.selection && !frame.contentWindow.getSelection)
	{
		var range = frame.document.selection.createRange();

		if(range.parentElement)
		{
			var object = new Object();

			var r1 = range.duplicate();
			var r2 = range.duplicate();

			r1.collapse(true);
			r2.moveToElementText(r1.parentElement());
			r2.setEndPoint('EndToStart', r1);
			object.startContainer = r1.parentElement();
			object.startOffset = 0; // TEMPORARY

			r1 = range.duplicate();
			r2 = range.duplicate();
			r2.collapse(false);
			r1.moveToElementText(r2.parentElement());
			r1.setEndPoint('StartToEnd', r2);
			object.endContainer = r2.parentElement();
			object.endOffset = 0; // TEMPORARY

			object.commonAncestorContainer = range.parentElement();

			object.setStart = function(startContainer, startOffset) { this.startContainer = container; this.startOffset = offset; }
			object.setEnd = function(container, offset) { this.endContainer = container; this.endOffset = offset; }

			return object;
		}

		return frame.document.selection.getRangeAt(0);
	}

	// Firefox, Opera
	var selection = frame.contentWindow.getSelection();
	if(selection.getRangeAt) return selection.getRangeAt(0);

	// Safari
	var range = document.createRange();
	range.setStart(selection.anchorNode, selection.anchorOffset);
	range.setEnd(selection.focusNode, selection.focusOffset);

	return range;
}

function isSelectionCollapsed(frame)
{
	if(frame.document && frame.document.selection && 0)
	{
		var range = getSelectionRange(frame);
		return range.startContainer == range.endContainer && range.startOffset == range.endOffset;
	}
	return frame.contentWindow.getSelection().isCollapsed;
}

function setSelectionRange(frame, range)
{
	if(frame.document && frame.document.selection && !frame.contentWindow.getSelection) // the test is wrong for Opera
	{
		// fix for IE
	/*
		if(range.parentElement)
		{

		}
		else
		{
			frame.document.selection.removeAllRanges();
			frame.document.selection.addRange(range);
		}
		*/
	}
	else
	{
		frame.contentWindow.getSelection().removeAllRanges();
		frame.contentWindow.getSelection().addRange(range);
	}
}

function format(frame, name, pattern, conflictPattern)
{
	var frameDoc = getFrameDocument(frame);
	var range = getSelectionRange(frame);

	var endContainer = range.endContainer, endOffset = range.endOffset;
	var startContainer = range.startContainer, startOffset = range.startOffset;

	//alert(startContainer.nodeName + ':' + startOffset + ' | ' + endContainer.nodeName + ':' + endOffset);

	if(onlyNodeSelected(range, pattern))
	{
		var length = startContainer.childNodes.length;

		explodeChildNodes(startContainer, pattern)

		range.setStart(startContainer, startOffset);
		range.setEnd(endContainer, endOffset + startContainer.childNodes.length - length);
	}
	else if(onlyNodeSelectedBetween(range, pattern))
	{
		var node = startContainer.nextSibling;
		while(node != endContainer)
		{
			if(node.nodeType == 1 && node.nodeName.match(pattern)) explodeNode(node);
			node = node.nextSibling;
		}
		range.setStart(startContainer, startContainer.length);
		range.setEnd(endContainer, 0);
	}
	else if(rootNode = rangeRoot(range, pattern))
	{
		splitNode(endContainer, endOffset, rootNode.parentNode, true);
		splitNode(startContainer, startOffset, rootNode.parentNode, false);
		//if(rootNode == startContainer || rootNode == endContainer)
		{
			var parentNode = rootNode.parentNode;
			var offset = nodeOffset(rootNode);
			var length = parentNode.childNodes.length;

			explodeNode(rootNode);

			range.setStart(parentNode, offset);
			range.setEnd(parentNode, offset + 1 + parentNode.childNodes.length - length);
		}
		/*
		else
		{
			explodeNode(rootNode);

			range.setStart(startContainer, 0);
			range.setEnd(endContainer, endContainer.length);
		}
		*/
	}
	else
	{
		var node = frameDoc.createElement(name);
		range.surroundContents(node); // fix for IE
		if(conflictPattern !== undefined) explodeChildNodes(node, conflictPattern);
		range.selectNodeContents(node);
		if(startOffset == endOffset) range.setEnd(range.endContainer, range.startOffset);
	}

	setSelectionRange(frame, range);

//	alert(range.startContainer.nodeName + ':' + range.startOffset + ' | ' + range.endContainer.nodeName + ':' + range.endOffset);
}

function richeditKeydown(e, frameId, textareaId)
{
	e = e || event;
	var code = e.which || e.keyCode;
	var frame = document.getElementById(frameId);

	if(code == 13) // [Enter] is pressed
	{
		richeditCommand(frameId, textareaId, 'newParagraph');
		e.preventDefault();
	}
}

function richeditSelect(e, frameId, textareaId)
{

}

function richeditCommand(frameId, textareaId, command)
{
	var frame = document.getElementById(frameId);
	var textarea = document.getElementById(textareaId);
	var frameDoc = getFrameDocument(frame);

	try{ frameDoc.execCommand('styleWithCSS', false, false); } catch(e) {}

	frame.contentWindow.focus();

	switch(command)
	{
		case 'heading':		format(frame, 'h1', 	/^h[1-6]$/i, 		/^p$|^h[1-6]$|^div$/i); break;
		case 'paragraph':	format(frame, 'p', 		/^p$/i, 			/^p$|^h[1-6]$|^div/i); break;
		case 'newParagraph':
			if(!isSelectionCollapsed(frame)) frameDoc.execCommand('delete', false, true);
			format(frame, 'p', 		/^p$/i, 			/^p$|^h[1-6]$|^div/i);
			break;
		case 'section':		format(frame, 'div', 	/^div$/i); break;
		case 'bold':		format(frame, 'b', 		/^b$|^strong$/i, 	/^b$|^strong$/i); break;
		case 'italic':		format(frame, 'i', 		/^i$|^em$/i, 		/^i$|^em$/i); break;
		case 'underline':	format(frame, 'u', 		/^u$/i, 			/^u$/i); break;
		case 'strike':		format(frame, 's', 		/^s$/i, 			/^s$/i); break;
		case 'ol': 			frameDoc.execCommand('InsertOrderedList', false, true); break;
		case 'ul': 			frameDoc.execCommand('InsertUnorderedList', false, true); break;
		case 'link':
			if(url = window.prompt('Введите адрес ссылки', 'http://'))
				frameDoc.execCommand('createLink', false, url);
			break;
		case 'unlink': frameDoc.execCommand('Unlink', false, true); break;
		case 'image':
			if(uri = window.prompt('Введите адрес изображения', ''))
				frameDoc.execCommand('InsertImage', false, uri);
			break;
		case 'video':
			if(uri = window.prompt('Введите адрес видеофайла', ''))
				frameDoc.execCommand('InsertVideo', false, uri);
			break;
		case 'removeFormat':
			frameDoc.execCommand('removeFormat', false, true)
			break;

		case 'code':
			textarea.parentNode.style.display = (textarea.parentNode.style.display == 'none') ? '' : 'none';
			break;
	}
	frame.contentWindow.focus();
}

function richeditEnableTools(toolsDivId)
{
	var toolsDiv = document.getElementById(toolsDivId);
	toolsDiv.className = toolsDiv.className.replace(/disabled/i, 'enabled');
}

function richeditDisableTools(toolsDivId)
{
	var toolsDiv = document.getElementById(toolsDivId);
	toolsDiv.className = toolsDiv.className.replace(/enabled/i, 'disabled');
}

function richeditInit(textareaId, frameId, editorDivId, toolsDivId)
{
	var textarea = document.getElementById(textareaId);
	var frame = document.getElementById(frameId);
	var editorDiv = document.getElementById(editorDivId);
	var toolsDiv = document.getElementById(toolsDivId);

	// CONTENT

	var frameDoc = getFrameDocument(frame);
	if(!frameDoc.designMode || frameDoc.designMode.toLowerCase() == 'on') return;

	textarea.parentNode.style.display = 'none';
	editorDiv.style.display = '';

	var doc = loadXmlFromString('<document xmlns="http://www.bestyle.ru/standards/aml/">' + textarea.value + '</document>');
	doc = transform(doc, '/.style/vodoley/xhtml/richedit/aml2html.xslt')
	var content = cutTagBody(saveXml(doc), 'html');

	frameDoc.designMode = 'On';

	frameDoc.open();
	frameDoc.write('<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head><body class="editable">'+ content +'</body></html>');
	frameDoc.close();

	var links = document.getElementsByTagName('head')[0].getElementsByTagName('link');
	for(var i = 0; i < links.length; i++)
		frameDoc.getElementsByTagName('head')[0].appendChild(importElement(links[i], frameDoc));

	// EVENTS

	textarea.onblur = function(e) { exportTextareaToFrame(textareaId, frameId); }

	var focusFunction = function(e) { richeditEnableTools(toolsDivId); }
	var blurFunction = function(e) { exportFrameToTextarea(frameId, textareaId); richeditDisableTools(toolsDivId); }

	//var keydownFunction = function(e) { richeditKeydown(e, frameId, textareaId); }
	//var selectFunction = function(e) { richeditSelect(e, frameId, textareaId); }

	if(frame.contentDocument)
	{
		frame.contentDocument.addEventListener('blur', blurFunction, false);
		frame.contentDocument.addEventListener('focus', focusFunction, false);
		//frame.contentDocument.addEventListener("keydown", keydownFunction, false);
		//frame.contentDocument.addEventListener("mousemove", selectFunction, false);
	}
	else
	{
		frame.onblur = blurFunction;
		frame.onfocus = focusFunction;
		//frame.onkeydown = keydownFunction;
	}

	for(var i = 0; i < toolsDiv.childNodes.length; i++)
	{
		var buttonDiv = toolsDiv.childNodes[i];
		var buttonA = buttonDiv.firstChild;
		buttonA.onfocus = function(e) { getFrameWindow(document.getElementById(frameId)).focus(); }
		buttonDiv.onmousedown = function(e) { this.className = this.className.replace(/passive/i, 'active'); }
		buttonDiv.onmouseup = function(e) { this.className = this.className.replace(/active/i, 'passive'); }

	}
}

	//textarea.onblur = new Function("exportTextareaToFrame('"+ textareaId +"', '"+ frameId +"')");

	//var focusFunction = new Function("richeditEnableTools(\""+toolsDivId+"\")");
	//var blurFunction = new Function("exportFrameToTextarea('"+ frameId +"', '"+ textareaId +"'); richeditDisableTools(\""+toolsDivId+"\")");
		//buttonA.onfocus = new Function("document.getElementById('"+ frameId +"').contentWindow.focus()");
		//buttonDiv.onmousedown = new Function("this.className = this.className.replace(/passive/i, 'active')");
		//buttonDiv.onmouseup = new Function("this.className = this.className.replace(/active/i, 'passive')");

