﻿function TEditorElement()
{
	//private
		var self = this;
		var htmlHelper = null; //THTMLHelper
		var object = null;
		var objectDocument = null;
		var range = null;
		var type = ""; //selection type e.g. "Control"
		var objectEnclosingTableCell = null;
		var valid = false;
		var editor = null;

		function OnChanged()
		{
			if (editor)
			{
				editor.raiseHTMLCodeChanged();
			}
		}

		//returns parent object 
		function getParentObject(obj)
		{
			if (object.tagName) 
			{
				//if tagName defined then DHTML object like TD,DIV,...
				return object.parentNode;
			}
			else
			{
				//if tagName not defined then object is a Range
				if (object.parentElement || object.startContainer)
				{
					return (document.all)?range.parentElement():range.startContainer.parentNode;
				}
				else
				{
					return false;
				}
			}	
		}

		//if necessary creates tag and gets tag object enclosing current object
		function getEnclosedTag(tagName,forceCreate)
		{
			var parentObject = getParentObject(object);
			if (parentObject)
			{
				var c = 0;
				var found = false;
				while (c <= tagName.length-1 && !found)
				{
					if (parentObject.tagName.toLowerCase() == tagName[c].toLowerCase())
					{
						found = true;
					}
					else
					{
						c++;
					}
				}

				if (!found && forceCreate)
				{
					c--;

					if (object.insertAdjacentHTML)
					{
						object.insertAdjacentHTML('beforeBegin', '<'+tagName[c].toLowerCase()+'></'+tagName[c].toLowerCase()+'>');	
						parentObject = object.previousSibling;
						parentObject.appendChild(object);
					}
					else
					{	
						object.execCommand('FormatBlock',false,'<'+tagName[c].toLowerCase()+'>');
						parentObject = object.parentElement();
					}			
				}
			}
		
			return parentObject;
		}

		function getEnclosedStyle(htmlAttribute)
		{
			parentObject = getEnclosedTag(new Array('div','p','font'),false);
			if (parentObject) { return parentObject.getAttribute(htmlAttribute,0); } else { return ''; }
		}

		function setEnclosedStyle(tag,htmlAttribute,value)
		{
			parentObject = getEnclosedTag(new Array(tag),true);
			if (parentObject) 
			{
				parentObject.setAttribute(htmlAttribute,value,0);
				parentObject.outerHTML = parentObject.outerHTML; //to ensure that dom object refreshes itself after value assigned
			}
		}

		function setFormat(styleID,styleValue)
		{
			if (object)
			{
				var execObject = (object.execCommand)?object:objectDocument;

				//second process properties depending on object type
				if (type == "Text")
				{
					if (styleID == "Class" || styleID == "Style") 
					{ 		
						var collapsed = (document.all)?range.text=='':range.collapsed;

						//we have to first check common anchestor in fuckin firefox because it returns parent node as common anchestor
						//when selection over multiple format tags else it returns it in parentNode

						var outerObject = (document.all)?range.parentElement():(range.commonAncestorContainer.tagName?range.commonAncestorContainer:range.commonAncestorContainer.parentNode);
						var outerObjectText = (document.all)?outerObject.innerText:outerObject.textContent;
						var rangeText = (document.all)?range.text:range.toString();

						//first we check wether we should assign stuff directly to element enclosing selection
						if (collapsed || outerObjectText == rangeText) 
						{
							//do nothing, we process outer object directly
						}
						else
						{
							execObject.execCommand('RemoveFormat',false,'');
							execObject.execCommand('FontName',false,'selected');
							
							var generatedTagName = (document.all)?'font':'span';
							var createdObject = null;

							//todo: we have to search node with recursion or complete dokument to ensure processing of elements that were not inserted in the same level (e.g. when formating a string inside an existing format tag, browser splits tags, not same level!)
							var i = 0;
							while (i <= outerObject.childNodes.length-1 && !createdObject)
							{
								var childNode = outerObject.childNodes[i];
								if (childNode.tagName)
								{
									if (childNode.tagName.toLowerCase() == generatedTagName.toLowerCase())
									{
										if (childNode.style.fontFamily == 'selected' || childNode.face == 'selected')
										{
											createdObject = childNode;
										}
									}
								}
								i++;
							}

							createdObject.removeAttribute('style',0);
							createdObject.removeAttribute('face',0);

							outerObject = createdObject;
						}

						switch (styleID)
						{
							case "Class" : outerObject.className = styleValue; break;
							case "Style" : outerObject.style.cssText = styleValue; break;
						}

						OnChanged();
					} else
					if (styleID == "FontSize") { execObject.execCommand('FontSize',false,styleValue); OnChanged(); } else
					if (styleID == "FontName") { execObject.execCommand('FontName',false,styleValue); OnChanged(); } else
					if (styleID == "ForeColor") { execObject.execCommand('ForeColor',false,styleValue); OnChanged(); } else
					if (styleID == "BackColor") { execObject.execCommand((document.all && !window.opera)?'BackColor':'hilitecolor',false,styleValue); OnChanged(); } else
					if (styleID == "Bold") { execObject.execCommand('Bold',false,null); OnChanged(); } else
					if (styleID == "Italic") { execObject.execCommand('Italic',false,null); OnChanged(); } else
					if (styleID == "Underline") { execObject.execCommand('Underline',false,null); OnChanged(); } else
					if (styleID == "Sub") { execObject.execCommand('Superscript',false,null); OnChanged(); } else
					if (styleID == "Sup") { execObject.execCommand('Subscript',false,null); OnChanged(); } else
					if (styleID == "Alignment") { execObject.execCommand('Justify'+styleValue.substr(0,1).toUpperCase()+styleValue.substr(1,styleValue.length),false,null); OnChanged(); }
				
				}
				else if (type == "Control")
				{
				//http://msdn.microsoft.com/en-us/library/ms531169(VS.85).aspx - Command 
					if (styleID == "Class") { object.className = styleValue; OnChanged(); } else
					if (styleID == "FontSize") { object.style.fontSize = styleValue; OnChanged(); } else
					if (styleID == "FontName") { object.style.fontFamily = styleValue; OnChanged(); } else
					if (styleID == "ForeColor") { object.style.color = styleValue; OnChanged(); } else
					if (styleID == "BackColor") { object.style.background = styleValue; OnChanged(); } else
					if (styleID == "Bold") { object.style.fontWeight = styleValue==1?'bold':'normal'; OnChanged(); } else 
					if (styleID == "Italic") { object.style.fontStyle = styleValue==1?'italic':'normal'; OnChanged(); } else 
					if (styleID == "Underline") { object.style.textDecorationUnderline = (styleValue == 1); OnChanged(); } else
					if (styleID == "Sub") { object.style.textDecorationUnderline = (styleValue == 1); OnChanged(); } else
					if (styleID == "Sup") { object.style.textDecorationUnderline = (styleValue == 1); OnChanged(); } else
					if (styleID == "Alignment") { setEnclosedStyle("div","align",styleValue); }
				}			
			}
		}

		function getFormat(styleID)
		{
			result = null;
			result = '';

			//second process properties depending on object type
			if (type == "Text")
			{
				if (styleID == "Class" || styleID == "Style") {
					parentObject = (document.all)?range.parentElement():(range.commonAncestorContainer.tagName?range.commonAncestorContainer:range.commonAncestorContainer.parentNode);
						
					switch (styleID)
					{
						case "Class" : result = parentObject.className; break;
						case "Style" : result = parentObject.style.cssText; break;
					}

				} else
				if (styleID == "FontSize") { 
					result = object.queryCommandValue('FontSize');
					//if (!result) { result = object.queryCommandValue('FontSize'); } 
				} else
				if (styleID == "FontName") { 
					result = getEnclosedStyle('face');
					if (!result) { result = object.queryCommandValue('FontName'); } 
				} else
				if (styleID == "ForeColor") { 
					//object returns value where bytes order is inverted, using function to invert order and shift 8 bits right to get the hex color code
					//e.g. returns #EFCDAB
					//     -> bigEndianToLittleEndian = #ABCDEF00
					//     -> #ABCDEF00 >> 8 = #FFABCDEF
					//     -> #FFABCDEF & #00FFFFFF = #ABCDEF
					result = int2Hex (((bigEndianToLittleEndian(object.queryCommandValue('ForeColor'))>> 8 )&0x00FFFFFF));
				} else
				if (styleID == "BackColor") { 
					result = int2Hex (((bigEndianToLittleEndian(object.queryCommandValue('BackColor'))>> 8 )&0x00FFFFFF));
				} else
				if (styleID == "Bold") { result =(object.queryCommandValue)?object.queryCommandValue('Bold'):false; } else
				if (styleID == "Italic") { result =(object.queryCommandValue)?object.queryCommandValue('Italic'):false; } else
				if (styleID == "Underline") { result =(object.queryCommandValue)?object.queryCommandValue('Underline'):false; } else
				if (styleID == "Alignment") 
				{	
					if (object.queryCommandValue)
					{	
						if (object.queryCommandValue('JustifyLeft')) { result = 'left'; } else
						if (object.queryCommandValue('JustifyCenter')) { result = 'center'; } else
						if (object.queryCommandValue('JustifyRight')) { result = 'right'; }
					}
					else
					{
						result = 'left';	
					}
				}
			}
			else if (type == "Control")
			{
				if (styleID == "Class") { result = object.className; } else
				if (styleID == "FontSize") { result = object.style.fontSize; } else
				if (styleID == "FontName") { result = object.style.fontFamily; } else
				if (styleID == "ForeColor") { result = object.style.color; } else
				if (styleID == "BackColor") { result = object.style.bgcolor; } else
				if (styleID == "Bold") { result = object.style.fontWeight == 'bold'; } else
				if (styleID == "Italic") { result = object.style.fontStyle == 'italic'; } else
				if (styleID == "Underline") { result = object.style.textDecorationUnderline; } else
				if (styleID == "Alignment") { result = getEnclosedStyle("align"); }
			}

			return result;
		}

		function int2Hex(value)
		{
			result = '';
			digits = '0123456789ABCDEF';
			
			//groesstes vielfache ermitteln
			factor = 16;
			while (factor < value)
			{
				factor *= 16;
			}

			//solange zahl mit vielfachen dividieren bis wert < 16
			while (value > 15)		
			{
				naturalpart = Math.floor(value / factor);
				decimalpart = (value % factor);

				result += digits.charAt(naturalpart);
				value  -= naturalpart*factor;
		
				//vielfache um basis 16 verringern um an nächste hex stelle zu kommen
				factor = factor / 16;
			}

			if (result.substr(0,1) == '0') { result = result.substr(1,result.length); }

			while (factor >= 16)
			{
				result += "0";
				factor = factor / 16;
			}
		
			result += digits.charAt(value);

			while ((result.length % 2 > 0) || (result.length < 6))
			{
				result = '0'+result;	
			}

			return '#'+result;
		}

		function bigEndianToLittleEndian(value)
		{
			byte1 = (value >> 24) & 0x000000FF;
			byte2 = (((value >> 16) & 0x000000FF) << 8) & 0x0000FF00;
			byte3 = (((value >> 8) & 0x000000FF) << 16) & 0x00FF0000;
			byte4 = ((value & 0x000000FF) << 24) & 0xFF000000;

			return byte1+byte2+byte3+byte4;
		}

	//public
		this.isValid = function()
		{
			return valid;
		}

		this.getAttributeValue = function(attributeName)
		{ 
			var result = '';
			if(object) 
			{ 
				if (object.getAttribute)
				{
					result = object.getAttribute(attributeName,0);
					if (result == null) { result = ''; }
				}
			}

			//avoid DOM bug, dom returns "on" when no value attribute is set
			if(attributeName.toLowerCase() == 'value' && result.toLowerCase() == 'on') { result = ''; }

			return result;
		}

		this.setAttributeValue = function(attributeName,value)
		{
			var result = false;
			if(object) 
			{ 
				if (object.getAttribute) 
				{
					object.setAttribute(attributeName,value,0); 
					OnChanged();

					result = true;
				}
			}

			return result;
		}

		this.getType = function()
		{
			result = type;
			//if (objectEnclosingTableCell) { result = 'TableCellText'; }
			
			return result;
		}

		//returns the value of the type= attribute of an input tag
		this.getInputType = function()
		{
			var result = '';

			if (object.type)
			{
				result = object.type;
			}

			return result;
		}

		this.getObjectType = function()
		{
			result = '';
			if (object.nodeName) { result = object.nodeName; }

			return result;
		}

		this.executeCommand = function(commandID,commandValue)
		{
			value = commandValue == '' ? 'selected' : commandValue;

			var execObject = (object.execCommand)?object:objectDocument;

			execObject.execCommand(commandID,false,value);

			if (commandID == 'FontName' && commandValue == '')
			{
				parentObject = getEnclosedTag(new Array('font'),true);
				if (parentObject)
				{
					if (parentObject.ownerDocument)
					{
						fontArray = parentObject.ownerDocument.all.tags("FONT");
						for (i=0; i<fontArray.length; i++)
						{
							if (fontArray[i].face == value)
							{
								fontArray[i].outerHTML = fontArray[i].outerHTML.replace(/ face=selected/i,"");
							}
						}
					}
				}
			}
		}

		//functions to manipulate shape of editor elements
		this.addTableCell = function(position)
		{ 
			if (this.canModifyTable())
			{	
				tdArray = htmlHelper.addTableCell(objectEnclosingTableCell,position);
				if (tdArray)
				{
					for (c=0;c<=tdArray.length-1;c++) { editor.setGuideLines(tdArray[c],editor.getGuideLines(null)); }
					OnChanged();
				}
			}
		}

		//deletes the current active table cell
		this.deleteTableCell = function()
		{		
			if (this.canModifyTable())
			{	
				if (htmlHelper.deleteTableCell(objectEnclosingTableCell,objectEnclosingTableCell.cellIndex))
				{
					editor.refreshSelection();
					OnChanged();
				}
				else
				{
					alert('Die Spalte konnte nicht gelöscht werden. \n\nMöglicherweise befinden sich Formularelemente in der aktuellen Spalte. Bitte entfernen Sie diese vorher.');
				}
			}	
		}

		this.addTableRow = function(position) 
		{ 
			if (this.canModifyTable())
			{
				tdArray = htmlHelper.addTableRow(objectEnclosingTableCell,position);
				if (tdArray)
				{
					for (c=0;c<=tdArray.length-1;c++) { editor.setGuideLines(tdArray[c],editor.getGuideLines(null)); }
					OnChanged();
				}
			}
		}

		//deletes the current active table cell
		this.deleteTableRow = function()
		{		
			if (this.canModifyTable())
			{	
				errorDescription = '';
				if (htmlHelper.deleteTableRow(objectEnclosingTableCell,objectEnclosingTableCell.parentElement.rowIndex,errorDescription))
				{
					editor.refreshSelection();
					OnChanged();
				}
				else
				{
					alert('Die Zeile konnte nicht gelöscht werden. \n\nMöglicherweise befinden sich Formularelemente in der aktuellen Zeile. Bitte entfernen Sie diese vorher.');
				}
			}	
		}

		this.removeFormat = function() 
		{ 
			if (object)
			{
				var execObject = (object.execCommand)?object:objectDocument;
			
				if (type == "Text")
				{	
					execObject.execCommand('removeFormat',null,null);
				}
				else if (type == "Control")
				{
					object.removeAttribute('style',0);
					object.removeAttribute('className',0);
				}

				OnChanged();
			}
		}

		this.addOption = function(value,text) { if (htmlHelper.addOption(object,value,text)) { OnChanged(); } }
		this.deleteOption = function() { if (htmlHelper.deleteOption(object)) { OnChanged(); } }
		this.updateOption = function(value,text) { if (htmlHelper.updateOption(object,object.selectedIndex,value,text)) { OnChanged(); } }
		this.moveOption = function(offset) { if (htmlHelper.moveOption(object,object.selectedIndex,offset)) { OnChanged(); } }
		this.getSelectedIndex = function() { if (object.selectedIndex >= 0) { return object.selectedIndex; } else { return -1; } }
		this.getOption = function(index) { if (object.selectedIndex >= 0) { return object.options(index); } else { return null; } }

		this.getFontName = function() { return getFormat("FontName"); }
		this.setFontName = function(value) { setFormat("FontName",value); }
		this.getFontSize = function() { return getFormat("FontSize"); }
		this.setFontSize = function(value) { setFormat("FontSize",value); }
		this.getForeColor = function() { return getFormat("ForeColor"); }
		this.setForeColor = function(value) { setFormat("ForeColor",value); }
		this.getBackColor = function() { return getFormat("BackColor"); }
		this.setBackColor = function(value) { setFormat("BackColor",value); }
		this.getClass = function() { return getFormat("Class"); }
		this.setClass = function(value) { setFormat("Class",value); }
		this.getStyle = function() { return getFormat("Style"); }
		this.setStyle = function(value) { setFormat("Style",value); }
		this.getAlignment = function() { return getFormat("Alignment"); }
		this.setAlignment = function(value) { setFormat("Alignment",value); }
		this.setBold = function(value) { setFormat("Bold",value); }
		this.getBold = function() { return getFormat("Bold"); }
		this.setItalic = function(value) { setFormat("Italic",value); }
		this.getItalic = function() { return getFormat("Italic"); }
		this.setUnderline = function(value) { setFormat("Underline",value); }
		this.getUnderline = function() { return getFormat("Underline"); }
		
		this.setSub = function(value) { setFormat("Sub",value); }
		this.getSub = function() { return getFormat("Sub"); }
		
		this.setSup = function(value) { setFormat("Sup",value); }
		this.getSup = function() { return getFormat("Sup"); }
		
		this.setEditable = function(value) { if (object) { object.contentEditable = value; OnChanged(); } } 
		this.select = function() 
		{ 
			if (object) { 
				if (this.isControl()) 
				{
					object.setActive();
				}
				if (object.focus) object.focus(); 
			}
		}
		
		this.getID = function() { return this.getAttributeValue('id'); }
		this.setID = function(value) { return this.setAttributeValue('id',value); }
		this.getValue = function() { return this.getAttributeValue('value'); }
		this.setValue = function(value) { return this.setAttributeValue('value',value); }
		this.getSource = function() { return this.getAttributeValue('src'); }
		this.setSource = function(value) { return this.setAttributeValue('src',value); }

		this.isControl = function() { return (type == "Control"); }
		this.canModifyTable = function() { return (objectEnclosingTableCell != null); }
		
		//call this method first and pass a selected object and the appropriate selection type
		this.initialize = function(domDocument, domObject,selectionType,editorObject,selectionRange)
		{
		   	editor = editorObject;
			htmlHelper = new THTMLHelper;
			object = domObject;
			objectDocument = domDocument;
			range = selectionRange;

			type = selectionType; 	
			if (type == "None") { type = "Text"; } 

			objectEnclosingTableCell = null;
			if (type == "Text")
			{
				var obj = getParentObject(object);
				if (obj)
				{
					var tableFound = (obj.tagName == 'TD');

					while (!tableFound && obj.tagName != 'BODY' && obj.parentNode)
					{
						obj = obj.parentNode;
						tableFound = (obj.tagName == 'TD');			
					}

					if (tableFound) { objectEnclosingTableCell = obj; }
				}
			}

			valid = true;
		}

		this.assign = function(source)
		{
			this.initialize(source.getDocument(), source.getObject(),source.getType(),source.getEditor(),source.getRange());	
		}

		this.reset = function()
		{
			valid = false;
		}	

		this.getObject = function()
		{
			return object;
		}

		this.getDocument = function()
		{
			return objectDocument;
		}

		this.getEditor = function()
		{
			return editor;
		}

		this.getRange = function()
		{
			return range;
		}
}
