var highLightArray = phpHighLighting;
var hashSynheObjects = [];
function getSynheObject(id) { // Получаем объект Synhe
	if (typeof(hashSynheObjects[id]) == "undefined") {
		hashSynheObjects[id] = new synhe(id);
	}
	return hashSynheObjects[id];
}
function synhe(id) { // Конструктор объекта Synhe
	// Префиксы для ID
	this._TEXT_ = "textarea_";
	this._DIV_ = "chckdiv_";
	this._CHCB_ = "checkbox_";
	this._NUMLIST_ = "numList_";
	
	// Переменные
	this.frameId = id;
	this.textContent = "";
	this.htmlContent = "<P> </P>";
	this.htmlCharLength = 0;
	this.display = false;
	this.checkTimeOut = 300; // Временной интервал, для проверки изменений (мс)
	this.frameHeight = 0;
	this.rowHeight = 0;
	this.frameScrollTop = 0;
	this.firstVisibleRow = 0;
	this.lastVisibleRow = 0;
	this.visiblePArray = [];
	this.isDelRow = false;
	
	// Переменные выделения
	this.sCollapse = true;
	this.sNode = null;
	this.eNode = null;
	this.sChar = 0;
	
	this.enableDesignMode = function() { // Инициализируем iframe для редактирования
		var fWin = (isGecko) ? gebi(this.frameId).contentWindow : frames[this.frameId].window;
		var fDoc = (isGecko) ? gebi(this.frameId).contentDocument : frames[this.frameId].document;
		this.htmlEdit = "<html><head>\n\
			<style>\n\
			/*@import url('');*/\n\
			body, div, p, td {font: normal 14px \"Courier New\", Courier, monospace; margin:0px; padding:0px;}\
			body {padding:3px;}\
			p{white-space:nowrap;}";
		// Формируем список стилей для подсветки
		for (var i = 0; i < highLightArray.length; i++) {
			this.htmlEdit += "." + highLightArray[i][0] + "{" + highLightArray[i][2] + "}\n";
		}
		this.htmlEdit += "</style>\n\
			<body></body>\
			</html>";
		fDoc.open();
		fDoc.write(this.htmlEdit);
		fDoc.close();
		this.changeFormat();
		if (!fDoc.designMode) {this.designModeOff(); return;}
		fDoc.designMode = (isGecko) ? "on" : "On";
		if (fWin.HTMLElement) { // Добавляем метод removeNode для Гекко
			if (!fWin.HTMLElement.prototype.removeNode) {
				fWin.HTMLElement.prototype.removeNode = function(bool) {
					var _p_ = this.parentNode;
					if (!bool) while(this.childNodes.length > 0) _p_.insertBefore(this.firstChild, this);
					_p_.removeChild(this);
				}
			}
		}
		this.addContent(); // Фикс для ИЕ
		this.includeHandlers(); // Добавляем обработчкики событий
	}
}
synhe.prototype = {
	drawNumLines : function() { // Рисуем линейку нумерации строк
		var td, lineH, frameH, tScroll, html, count;
		td = gebi(this._NUMLIST_ + this.frameId);
		lineH = (this.rowHeight) ? this.rowHeight : false;
		frameH = (this.frameHeight) ? this.frameHeight : false;
		tScroll = (this.frameScrollTop) ? this.frameScrollTop : "0";
		if (this.display && lineH && frameH && tScroll) {
			count = parseInt((frameH + parseInt(tScroll))/lineH) + 1;
			html = "<div style=\"width:40px; overflow:hidden; height: " + frameH + "px; background-color:#F3F3F3;\">";
			html += "<ol style=\"margin-top:4px; position:relative; top:-" + tScroll + "px\">";
			for (var i = 1; i <= count; i++) {
				html += "<li style=\"padding:0;\">&nbsp;";
			}
			html += "</ol>";
			html += "</div>";
			td.innerHTML = html;
			td.style.display = "";
		} else td.style.display = "none";
	},
	changeFormat : function () { // Переход "простой текст"-"хайлайтер"
		this.display = !this.display;
		gebi(this.frameId).style.display = (this.display) ? "" : "none";
		gebi(this._CHCB_ + this.frameId).checked = (this.display) ? false : true;
		gebi(this._TEXT_ + this.frameId).style.display = (this.display) ? "none" : "";
		this.drawNumLines();
		this.addContent();
	},
	designModeOff : function() { // Не поддерживается св-во designMode
		this.display = false;
		gebi(this.frameId).style.display = "none";
		gebi(this._DIV_ + this.frameId).style.display = "none";
		gebi(this._TEXT_ + this.frameId).style.display = "";
	},
	addContent : function() { // Переход HTML <--> PlainText
		var fDoc = (isGecko) ? gebi(this.frameId).contentDocument : frames[this.frameId].document;
		if (this.display) {
			try {
				//fDoc.body.innerHTML = this.textToHtml((gebi(this._TEXT_ + this.frameId).value != "" ? gebi(this._TEXT_ + this.frameId).value : "\n"));
				fDoc.body.innerHTML = this.textToHtml(gebi(this._TEXT_ + this.frameId).value);
			} catch(e) { // Фикс для ИЕ
				setTimeout("getSynheObject('" + this.frameId +"').addContent()", 10);
			}
		}
		else gebi(this._TEXT_ + this.frameId).value = this.htmlToText(fDoc.body.innerHTML);
	},
	textToHtml : function(text) { // PlainText -> HTML
		if (!(text && text.length)) return "<p>&nbsp;&nbsp;</p>";
		text = text.replace(/&/g, "&amp;");
		text = text.replace(/</g, "&lt;");
		text = text.replace(/>/g, "&gt;");
		var subStArr = text.split("\n");
		text = "";
		for (var i in subStArr) {
			text += "<P>" + subStArr[i] + "</P>";
		}
		return text;
	},
	htmlToText : function(html) { // HTML -> PlainText
		if (!(html && html.length)) return "";
		var bufer = document.createElement("SPAN");
		bufer.innerHTML = html;
		for (var arr = [], length = (node = bufer.childNodes).length, name, i = 0; i < length; i++) {
			arr[arr.length] = 
				('p' == (name = node[i].nodeName.toLowerCase())) ? (node[i].textContent ? node[i].textContent : node[i].innerText) :
				'br' == name ? '\n' : (node[i].textContent ? node[i].textContent : node[i].innerText);
		}
		for (var i in arr) {
			if (typeof(arr[i]) == "undefined") arr[i] = "";
			arr[i] = arr[i] + (i == (arr.length - 1) ? "" : "\n");
		}
		return arr.join('');
	},
	getContent : function() { // Сохранием текст кода в переменной
		var fDoc = (isGecko) ? gebi(this.frameId).contentDocument : frames[this.frameId].document;
		this.htmlContent = fDoc.body.innerHTML;
		this.textContent = this.htmlToText(fDoc.body.innerHTML);
	},
	includeHandlers : function() {
		var fDoc = (isGecko) ? gebi(this.frameId).contentDocument : frames[this.frameId].document;
		fDoc.carret = this;
		try {
			// Обработчик события keyUp
			if (isGecko) fDoc.addEventListener("keyup", this.keyUpHandler, false);
			else fDoc.onkeyup = this.keyUpHandler;
			// Обработчик события keyDown
			if (isGecko) fDoc.addEventListener("keydown", this.keyDownHandler, false);
			else fDoc.onkeydown = this.keyDownHandler;
			// Обработчик события click
			if (isGecko) fDoc.addEventListener("click", this.clickHandler, false);
			else fDoc.onclick = this.clickHandler;
			// Обработчик события contextmenu
			if (isGecko) fDoc.addEventListener("contextmenu", this.contextmenuHandler, false);
			else fDoc.oncontextmenu = this.contextmenuHandler;
			// Обработчик события TimeOut :)
			this.timeoutHandler();
		} catch(e) {
			alert("~error~");
		}
	},
	keyUpHandler : function(event) {
		this.carret.saveState();
		var event = event || eval(this.carret.frameId + ".event");
		var key = getKeyObject(this.carret.frameId).showKeyCode(event);
		var re = new RegExp("33|34|35|36|37|38|39|40");
		if (re.test(key)) return;
	},
	keyDownHandler : function(event) {
		this.carret.saveState();
		var event = event || eval(this.carret.frameId + ".event");
		var key = getKeyObject(this.carret.frameId).showKeyCode(event);
		// !NEW
		this.carret.isDelRow = (key == 46 || key == 8) ? true : false;
		// NEW!
		return getKeyObject(this.carret.frameId).catchSomeKeys(event); // Блокируем дефолтные обработчики событий нажатия некоторых клавиш
	},
	contextmenuHandler : function(event) {
		this.carret.saveState();
		return false;
	},
	clickHandler : function() {
		this.carret.saveState();
	},
	timeoutHandler : function() {
		this.saveState();
		window.setTimeout("getSynheObject('" + this.frameId +"').timeoutHandler()", this.checkTimeOut);
	},
	mainParser : function() {
		var fDoc = (isGecko) ? gebi(this.frameId).contentDocument : frames[this.frameId].document;
		this.fix_BR_to_P();
		this.index_P_nodes();
		this.drawNumLines();
		this.highLight();
	},
	highLight : function() { // Заменяем содержимое строк на HTML с подсветкой
		var fWin = (isGecko) ? gebi(this.frameId).contentWindow : frames[this.frameId].window;
		var fDoc = (isGecko) ? gebi(this.frameId).contentDocument : frames[this.frameId].document;
		var fBody = fDoc.body;
		var node, rows, rNew, rOld, text;
		sChar = this.sChar; // Сохраняем фокус до его перехода в позицию 0 (фикс для гекко)
		eChar = this.eChar;
		rows = this.visiblePArray;
		for (var i = this.firstVisibleRow; i <= this.lastVisibleRow; i++) {
			if (typeof(rows[i]) == "undefined") continue; // В области редактирования отсутствует такая строка
			node = fBody.childNodes;
			nOld = node[rows[i]];
			nNew = document.createElement("P");
			text = nOld.innerText ? nOld.innerText : nOld.textContent;
			if (!(text && text.length > 0)) continue;
			nNew.innerHTML = this.rowParser(text); // Парсим каждую строку
			if (nOld.innerHTML == nNew.innerHTML) continue;
			nOld.innerHTML = nNew.innerHTML;
		}
		this.setFocus(sChar, eChar);
	},
	setFocus : function(sChar, eChar) { // Ставим выделение после замены содержимого строк
		var fWin = (isGecko) ? gebi(this.frameId).contentWindow : frames[this.frameId].window;
		var fDoc = (isGecko) ? gebi(this.frameId).contentDocument : frames[this.frameId].document;
		var fBody = fDoc.body;
		var _node, node, rng, sel, tNode, rows, count = 0, selOpen = false;
		rows = this.visiblePArray;
		node = fBody.childNodes;
		for (var i = 0; i <= this.lastVisibleRow; i++) {
			if (typeof(rows[i]) == "undefined") continue;
			nOld = node[rows[i]];
			if (nOld == this.sNode) { // Узел содержит начало выделения
				selOpen = true;
				if (document.createRange) { // Гекко
					sel = fWin.getSelection();
					for (var j = 0, length = (_node = nOld.childNodes).length; j < length; j++) {
						// !NEW
						if (_node[j].nodeType != 1)  tNode = _node[j] 
						else {
							if (_node[j].firstChild) tNode = _node[j].firstChild;
							else continue;
						}
						// NEW!
						if (sChar <= count + tNode.nodeValue.length) {
							rng = fDoc.createRange();
							rng.setStart(tNode, (sChar - count));
							if (this.sCollapse) { // Выделение длины 0
								rng.setEnd(tNode, (sChar - count));
								sel.addRange(rng);
								sel.collapseToEnd();
								break;
							}
						}
						if (this.sNode == this.eNode && eChar <= count + tNode.nodeValue.length) { // Начало и окончание выделения на одной строке
							rng.setEnd(tNode, (eChar - count));
							sel.addRange(rng);
							break;
						}
						count += tNode.nodeValue.length;
					}
				} else { // ИЕ
					rng = fBody.createTextRange();
					rng.moveToElementText(nOld);
					rng.moveStart("character", this.sChar);
					rng.collapse(true);
					if (this.sCollapse) { // Выделение длины 0
						rng.moveEnd("character", 0);
						selOpen = false;
					} else if (this.sNode == this.eNode) { // Начало и окончание выделения на одной строке
						rng.moveEnd("character", (this.eChar - this.sChar - 1));
						selOpen = false;
					} else {
						rng.moveEnd("character", (nOld.innerText.length - this.sChar));
					}
				}
			} else if (selOpen) { // Конец выделения не установлен
				if (document.createRange) { // Гекко
					// -------- do nothing -----------
				} else { // ИЕ
					if (nOld == this.eNode) { // Узел, содержащий конец выделения
						rng.moveEnd("character", this.eChar);
						selOpen = false;
					} else {  // Узел целиком лежит в выделении
						rng.moveEnd("character", (nOld.innerText.length + 1));
					}
				}
			}
		}
		if (rng && fBody.createTextRange) rng.select(); // ИЕ
	},
	rowParser : function(text) {
		var html, re, pattern, inRe;
		text = text.replace(/&/g, "&amp;");
		text = text.replace(/</g, "&lt;");
		text = text.replace(/>/g, "&gt;");
		pattern = /(?:^|\>)(.*?)(?:\<|$)/g;
		html = text.replace(pattern, function($1) {
			return $1.replace(/\s/g, "&nbsp;");
		});
		for (var i = 0; i < highLightArray.length; i++) {
			re = new RegExp(highLightArray[i][1], "g");
			html = html.replace(re, "<span class=" + highLightArray[i][0] + ">$&</span>");
		}
		return html;
	},
	index_P_nodes : function() {
		var fDoc = (isGecko) ? gebi(this.frameId).contentDocument : frames[this.frameId].document;
		var count = 0;
		this.visiblePArray = clearArray(this.visiblePArray);
		for (var i = 0, length = (node = fDoc.body.childNodes).length; i < length; i++) {
			if (node[i].nodeType != 1) continue;
			if (node[i].nodeName.toLowerCase() == "p") {
				this.visiblePArray[count] = i;
				count++;
			} else { // TEMP
				alert("Внимание! В БОДИ обнаружен не P-тег!");
			}
		}
	},
	fix_BR_to_P : function() {
		var fDoc = (isGecko) ? gebi(this.frameId).contentDocument : frames[this.frameId].document;
		for (var i = 0, length = (node = fDoc.body.childNodes).length, name; i < length; i++) {
			// !NEW
			if (typeof(node[i]) == "undefined") continue;
			// NEW!
			if (node[i].nodeType != 1) continue;
			if ("br" == (name = node[i].nodeName.toLowerCase())) { // Поймали BR в BODY
				this.brSplit("body", i);
			} else {
				for (var j = 0; j < node[i].childNodes.length; j++) {
					if (node[i].childNodes[j].nodeType != 1) continue;
					if ("br" == node[i].childNodes[j].nodeName.toLowerCase()) { // Поймали BR в P
						if (!(node[i].childNodes[j].previousSibling || node[i].childNodes[j].nextSibling)) continue;
						this.brSplit(i, j);
					}
				}
			}
		}
	},
	brSplit : function(i, j) {
		var fDoc, parent, br, left, right, s, rng;
		fDoc = (isGecko) ? gebi(this.frameId).contentDocument : frames[this.frameId].document;
		fWin = (isGecko) ? gebi(this.frameId).contentWindow : frames[this.frameId];
		parent = (i == "body") ? fDoc.body : fDoc.body.childNodes[i];
		br = parent.childNodes[j];
		left  = (br.previousSibling) ? br.previousSibling : null;
		right  = (br.nextSibling) ? br.nextSibling : null;
		if (left || right) {
			if (left && right && left.nodeType == 3 && right.nodeType == 3 && !this.isDelRow) { // <br> в середине текстовой строки, превращаем строку в две с помощью <p>, если не были нажаты delete или backspace
				var p1 = fDoc.createElement("P");
				p1.appendChild(fDoc.createTextNode(left.nodeValue));
				parent.replaceChild(p1, left);
				
				var p2 = fDoc.createElement("P");
				for (var k = j + 1; k < parent.childNodes.length; k++) p2.appendChild(parent.childNodes[k].cloneNode(true));
				while (parent.childNodes[j + 1]) parent.removeChild(parent.childNodes[j + 1]);
				parent.appendChild(p2);
				
				br.removeNode(true);
				parent.removeNode(false);
				fWin.focus(1);
			} else { // <br> в начале или конце текстовой строки или в качетсве отдельной строки, возможно только в Гекко, удаляем <br>, устанавливаем курсор
				br.removeNode(true);
				s = fWin.getSelection();
				rng = fDoc.createRange();
				rng.selectNode(this.sNode);
				rng.setStart(this.sNode, 0);
				rng.collapse(true);
				s.addRange(rng);
				fWin.focus(0);
			}
		} else {} // Случай пустой строки в Гекко {code: <p><br></p>}. Не делаем ничего
		this.fix_BR_to_P();
	},
	saveState : function() {
		var fDoc, fWin, frame, change;
		var sel, rng, text;
		fDoc = (isGecko) ? gebi(this.frameId).contentDocument : frames[this.frameId].document;
		fWin = (isGecko) ? gebi(this.frameId).contentWindow : frames[this.frameId].window;
		frame = gebi(this.frameId);
		change = false; // Флаг, фиксирующий наличие изменений
		try {
			change = change || (this.htmlCharLength != fDoc.body.innerHTML.length); // Изменение количества символов
			this.htmlCharLength = fDoc.body.innerHTML.length;
			change = change || (this.frameHeight != frame.offsetHeight); // Изменения высоты фрейма
			this.frameHeight = frame.offsetHeight;
			change = change || (this.rowHeight != fDoc.body.firstChild.offsetHeight); // Изменения высоти строки
			this.rowHeight = (fDoc.body.firstChild) ? fDoc.body.firstChild.offsetHeight : 0;
			change = change || (this.frameScrollTop != fDoc.body.scrollTop); // Изменения скролла
			this.frameScrollTop = fDoc.body.scrollTop;
			this.firstVisibleRow = parseInt(this.frameScrollTop / this.rowHeight);
			this.lastVisibleRow = parseInt((this.frameScrollTop + this.frameHeight) / this.rowHeight);
		} catch(e) { // Фикс для ИЕ
			setTimeout("getSynheObject('" + this.frameId +"').saveState()", 10);
		}
		// Запоминаем состоние выделения
		var sel, rng, sRng, eRng, node, count, temp;
		sel = (fDoc.selection) ? fDoc.selection : fWin.getSelection();
		if (sel) rng = (sel.createRange) ? sel.createRange() : sel.getRangeAt(sel.rangeCount - 1);
		if (document.selection) { // ИЕ
			this.sCollapse = (rng.text.length > 0) ? 0 : 1;
			sRng = rng.duplicate();
			sRng.collapse(true);
			node = sRng.parentElement();
			while (node.nodeName.toLowerCase() != "p" && node.nodeName.toLowerCase() != "body") {
				node = node.parentNode;
			}
			this.sNode = node;
			count = -1;
			while (sRng.parentElement().nodeName.toLowerCase() != "body") {
				temp = sRng.text.length;
				sRng.moveStart("character", -1);
				if (temp == sRng.text.length) {if (count != -1) count++;break;}
				count++;
			}
			if (count == -1) count = 0;
			this.sChar = count;
			this.eNode = null;
			this.eChar = 0;
			if (!this.sCollapse) {
				eRng = rng.duplicate();
				eRng.collapse(false);
				node = eRng.parentElement();
				while (node.nodeName.toLowerCase() != "p" && node.nodeName.toLowerCase() != "body") node = node.parentNode;
				this.eNode = node;
				count = 0;
				while (eRng.parentElement().nodeName.toLowerCase() != "body") {
					temp = eRng.text.length;
					eRng.moveStart("character", -1);
					if (temp == eRng.text.length) break;
					count++;
				}
				this.eChar = count;
			}
		} else if (rng && rng.startContainer) { // Гекко
			this.sCollapse = rng.collapsed;
			node = rng.startContainer;
			count = rng.startOffset;
			this.sNode = this.getSelectionData(node, count, "p").node;
			this.sChar = this.getSelectionData(node, count, "p").count;
			this.eNode = null;
			this.eChar = 0;
			if (!this.sCollapse) {
				node = rng.endContainer;
				count = rng.endOffset;
				this.eNode = this.getSelectionData(node, count, "p").node;
				this.eChar = this.getSelectionData(node, count, "p").count;
	}
}
		if (change) this.mainParser();
	},
	getSelectionData : function(node, count, limitNode) {
		while (node.nodeName.toLowerCase() != limitNode && node.nodeName.toLowerCase() != "body") {
			while (node.previousSibling) {
				node = node.previousSibling;
				count += node.textContent.length;
			}
			node = node.parentNode;
		}
		return {node:node, count:count};
	}
}