// Chris Pyper 2006
// Functions for determining current state of editor

// Returns the deepest node that contains both endpoints of the selection.
function getParentElement() 
{
	debug("getParentElement()", 'info');

	var sel = getSelection();
	var range = getRange(sel);

	if(!sel || !range)
		return false;

    if (ie) 
	{
        switch (sel.type) 
		{
            case "Text":
            case "None":
            // It seems that even for selection of type "None",
            // there _is_ a parent element and it's value is not
            // only correct, but very important to us.  MSIE is
            // certainly the buggiest browser in the world and I
            // wonder, God, how can Earth stand it?
			  try{
				if (range.parentElement())
          		  return range.parentElement();
			  }
			  catch(e){	return document.body;  }
            case "Control":
			  if ( range.item  )
          		return range.item(0);
			  else
				return range.parentElement();
            default:
            return document.body;
        }
    } 
	else try 
	{
        var p = range.commonAncestorContainer;
        if (!range.collapsed && range.startContainer == range.endContainer &&
            range.startOffset - range.endOffset <= 1 && range.startContainer.hasChildNodes())
            p = range.startContainer.childNodes[range.startOffset];
        /*
        alert(range.startContainer + ":" + range.startOffset + "\n" +
              range.endContainer + ":" + range.endOffset);
        */
        while (p.nodeType == 3) 
            p = p.parentNode;

        return p;
    } 
	catch (e)
	{ 
        return null;
	}
}

// Return highlighted text
function getSelectionText()
{
	debug("getSelectionText()", 'info');

	if(ie)
		return getRange().text;
	else
		return getSelection().toString();
}

// Return cached range if exists, otherwise make new one
function getRange(sel)
{
	debug("getRange()", 'info');

	if(cachedRange)
		return cachedRange;

	return createRange(sel);
}

// Return selection if exists, otherwise new one
function getSelection()
{
	debug("getSelection()", 'info');

	if(cachedSelection)
		return cachedSelection;

	return createSelection();
}

// Returns a range for the current selection
function createRange(sel)
{
	debug("createRange()", 'info');

	if(!docSourceClone)
        return false;

    if (ie)
        return sel.createRange();
    else
	{
        iframe.contentWindow.focus();

        if (typeof sel != "undefined") 
		{
            try
			{
                return sel.getRangeAt(0);
			}
			catch(e) 
			{
                return iframe.contentWindow.document.createRange();
			}
        } 
		else
            return iframe.contentWindow.document.createRange();
    }
}

// Returns the current selection object
function createSelection() 
{
	debug("createSelection()", 'info');	
	
	if(!docSourceClone)
		return false;

    if(ie)
        return document.selection;
    else
        return iframe.contentWindow.getSelection();
}

function highlightSelection()
{
	debug("highlightSelection()", 'info');

	var sel   = getSelection();
	var range = getRange(sel);

	if(ie)
	{
		// ***WARNING EXTREMELY IMPORTANT***
        // We cached a range earlier to maintain hightlighting after selecting text, what happen in IE is if you
        // start typing at the end of a paragraph and then change the focus by pressing on a button elsewhere
        // in the app the selection is cached automatically adjusts to include your new text.  Well, just use
        // a diffrent event then you say for when you exit focus to cache again, well thanks to the contentEditable that is not
        // alway possible.  So the best way is to cache the selection text the first time to see if anything has
        // has changed, if so move the cursor to the end using the the only way possible with the exposed methods
        // take the length of the text range and move there.  Thus you give the appearance that thw whole
        // text range bug thing didn't happen without messing up existing highlight functionality.
        if(cachedInnerText != docSourceClone.innerText && range && range.text.length >= 0)
            range.moveStart("character", range.text.length);

        // Reselect to maintain hightlighting and reposition focus of cursor
        if(range)
            range.select();
	}
	else
	{		
		// ***WARNING EXTREMELY IMPORTANT***
		// This is very limited functionality you can perform with ranges and selection in Gecko based
		// browsers so don't get too excited.  Also, ranges that are dynamically created by javascript
		// are even worse, they have all kinds of issues with certain methods not working, so be
		// careful what you do.
		try
		{
			// Select cached ranges
			sel = createSelection();
			sel.removeAllRanges();
			sel.addRange(cachedRange);		
		}
		catch(e)
		{
			debug("highlightSelection() - EXCEPTION THROWN !!!", "error");	
		}
	}

	return true;
}

// Cache the selection, this is necessary in IE where clicking on a button destorys the range becuase
// of the shift in focus, save the text as well to check later if you have typed something since the last 
// cache, this gets around an issue outlined in my rant above.
function cacheSelection(e)
{
	debug("cacheSelection()", 'info');

	if(!docSourceClone)
		return false;
	var typedTag = getParentElement();
    // If the user typed on a spell check span, clear it
    if(typedTag && typedTag.className && typedTag.className.search(/spellCheck/gi) >= 0)
        removeCorrection(typedTag);

	checkCommandState();

	// ***WARNING EXTREMELY IMPORTANT***
	// This is very limited functionality you can perform with ranges and selection in Gecko based
	// browsers so don't get too excited.  Also, ranges that are dynamically created by javascript
	// are even worse, they have all kinds of issues with certain methods not working, so be
	// careful what you do.
    if(createSelection())
	{
		cachedSelection = createSelection();
        cachedRange		= createRange(createSelection());
		cachedInnerText	= docSourceClone.innerText;
	}	

	return true;
}

// Get the state of the highlighted text or current cursor poistion
function getCommandState()
{
	debug('getCommandState()', 'info');

	// Query for these commands, see online docs for more, this seems to
	// be all we need for know.  State for boolean, Value for values, each 
	// command is different
	var commands = [
		['bold', 'state'],
		['italic', 'state'],
		['underline', 'state'],
		['fontname', 'value'],
		['fontsize', 'value'],
		['forecolor', 'value']
	];

	if(!docSourceClone)
        return false;

	var commandArray = new Array();

	for(var i in commands)
	{
		var command = commands[i][0];
		var type    = commands[i][1];

		// Same function diffrent object
		if(ie)
			var doc = document;
		else
			var doc = iframe.contentWindow.document;

		// Get value or state depending on command
		if(type == 'state')
			commandArray[command] = doc.queryCommandState(command);
		else if(type == 'value')
			commandArray[command] = doc.queryCommandValue(command);

		else
			commandArray[command] = false;

		// Convert color to a nice Hex Representation, IE gives a number, Mozilla gives rgb(etc...	
		if(command.search(/color/) >= 0 && commandArray[command])
			commandArray[command] = convertColor(commandArray[command]);

		 debug('getCommandState() command=' + command + " state/value=" + commandArray[command], 'info');
		
	}
	
	return commandArray;
}

// Check if the command state had changed since last check
function checkCommandState()
{
	debug('checkCommandState()', 'info');

	var commandArray = getCommandState();
	var makeCallback = false;

	// If you have an array of previous command states cached check against it
	if(cachedCommandArray)
	{
		for(var i in cachedCommandArray)
		{
			if(cachedCommandArray[i] != commandArray[i])
			{
				debug('checkCommandState() - Command Change', 'info');
				makeCallback = true;
			}
		}
	}
	else
		makeCallback = true;

	// Signal if there is a change
	if(makeCallback)
	{
		var callBacks = fetchInterface();
		callBacks.triggerCallBack('commandStateChanged', commandArray);
		cachedCommandArray = commandArray;
	}

	return true;
}

