/*
 * FCKeditor - The text editor for Internet - http://www.fckeditor.net
 * Copyright (C) 2003-2008 Frederico Caldeira Knabben
 *
 * == BEGIN LICENSE ==
 *
 * Licensed under the terms of any of the following licenses at your
 * choice:
 *
 *  - GNU General Public License Version 2 or later (the "GPL")
 *    http://www.gnu.org/licenses/gpl.html
 *
 *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
 *    http://www.gnu.org/licenses/lgpl.html
 *
 *  - Mozilla Public License Version 1.1 or later (the "MPL")
 *    http://www.mozilla.org/MPL/MPL-1.1.html
 *
 * == END LICENSE ==
 *
 * Utility functions.
 */

var FCKTools = new Object() ;

FCKTools.CreateBogusBR = function( targetDocument )
{
	var eBR = targetDocument.createElement( 'br' ) ;
//	eBR.setAttribute( '_moz_editor_bogus_node', 'TRUE' ) ;
	eBR.setAttribute( 'type', '_moz' ) ;
	return eBR ;
}

/**
 * Fixes relative URL entries defined inside CSS styles by appending a prefix
 * to them.
 * @param (String) cssStyles The CSS styles definition possibly containing url()
 *		paths.
 * @param (String) urlFixPrefix The prefix to append to relative URLs.
 */
FCKTools.FixCssUrls = function( urlFixPrefix, cssStyles )
{
	if ( !urlFixPrefix || urlFixPrefix.length == 0 )
		return cssStyles ;

	return cssStyles.replace( /url\s*\(([\s'"]*)(.*?)([\s"']*)\)/g, function( match, opener, path, closer )
		{
			if ( /^\/|^\w?:/.test( path ) )
				return match ;
			else
				return 'url(' + opener + urlFixPrefix + path + closer + ')' ;
		} ) ;
}

FCKTools._GetUrlFixedCss = function( cssStyles, urlFixPrefix )
{
	var match = cssStyles.match( /^([^|]+)\|([\s\S]*)/ ) ;

	if ( match )
		return FCKTools.FixCssUrls( match[1], match[2] ) ;
	else
		return cssStyles ;
}

/**
 * Appends a <link css> or <style> element to the document.
 * @param (Object) documentElement The DOM document object to which append the
 *		stylesheet.
 * @param (Variant) cssFileOrDef A String pointing to the CSS file URL or an
 *		Array with many CSS file URLs or the CSS definitions for the <style>
 *		element.
 * @return {Array} An array containing all elements created in the target
 *		document. It may include <link> or <style> elements, depending on the
 *		value passed with cssFileOrDef.
 */
FCKTools.AppendStyleSheet = function( domDocument, cssFileOrArrayOrDef )
{
	if ( !cssFileOrArrayOrDef )
		return [] ;

	if ( typeof( cssFileOrArrayOrDef ) == 'string' )
	{
		// Test if the passed argument is an URL.
		if ( /[\\\/\.][^{}]*$/.test( cssFileOrArrayOrDef ) )
		{
			// The string may have several URLs separated by comma.
			return this.AppendStyleSheet( domDocument, cssFileOrArrayOrDef.split(',') ) ;
		}
		else
			return [ this.AppendStyleString( domDocument, FCKTools._GetUrlFixedCss( cssFileOrArrayOrDef ) ) ] ;
	}
	else
	{
		var styles = [] ;
		for ( var i = 0 ; i < cssFileOrArrayOrDef.length ; i++ )
			styles.push( this._AppendStyleSheet( domDocument, cssFileOrArrayOrDef[i] ) ) ;
		return styles ;
	}
}

FCKTools.GetStyleHtml = (function()
{
	var getStyle = function( styleDef, markTemp )
	{
		if ( styleDef.length == 0 )
			return '' ;

		var temp = markTemp ? ' _fcktemp="true"' : '' ;
		return '<' + 'style type="text/css"' + temp + '>' + styleDef + '<' + '/style>' ;
	}

	var getLink = function( cssFileUrl, markTemp )
	{
		if ( cssFileUrl.length == 0 )
			return '' ;

		var temp = markTemp ? ' _fcktemp="true"' : '' ;
		return '<' + 'link href="' + cssFileUrl + '" type="text/css" rel="stylesheet" ' + temp + '/>' ;
	}

	return function( cssFileOrArrayOrDef, markTemp )
	{
		if ( !cssFileOrArrayOrDef )
			return '' ;

		if ( typeof( cssFileOrArrayOrDef ) == 'string' )
		{
			// Test if the passed argument is an URL.
			if ( /[\\\/\.][^{}]*$/.test( cssFileOrArrayOrDef ) )
			{
				// The string may have several URLs separated by comma.
				return this.GetStyleHtml( cssFileOrArrayOrDef.split(','), markTemp ) ;
			}
			else
				return getStyle( this._GetUrlFixedCss( cssFileOrArrayOrDef ), markTemp ) ;
		}
		else
		{
			var html = '' ;

			for ( var i = 0 ; i < cssFileOrArrayOrDef.length ; i++ )
				html += getLink( cssFileOrArrayOrDef[i], markTemp ) ;

			return html ;
		}
	}
})() ;

FCKTools.GetElementDocument = function ( element )
{
	return element.ownerDocument || element.document ;
}

// Get the window object where the element is placed in.
FCKTools.GetElementWindow = function( element )
{
	return this.GetDocumentWindow( this.GetElementDocument( element ) ) ;
}

FCKTools.GetDocumentWindow = function( document )
{
	// With Safari, there is not way to retrieve the window from the document, so we must fix it.
	if ( FCKBrowserInfo.IsSafari && !document.parentWindow )
		this.FixDocumentParentWindow( window.top ) ;

	return document.parentWindow || document.defaultView ;
}

/*
	This is a Safari specific function that fix the reference to the parent
	window from the document object.
*/
FCKTools.FixDocumentParentWindow = function( targetWindow )
{
	if ( targetWindow.document )
		targetWindow.document.parentWindow = targetWindow ;

	for ( var i = 0 ; i < targetWindow.frames.length ; i++ )
		FCKTools.FixDocumentParentWindow( targetWindow.frames[i] ) ;
}

FCKTools.HTMLEncode = function( text )
{
	if ( !text )
		return '' ;

	text = text.replace( /&/g, '&amp;' ) ;
	text = text.replace( /</g, '&lt;' ) ;
	text = text.replace( />/g, '&gt;' ) ;

	return text ;
}

FCKTools.HTMLDecode = function( text )
{
	if ( !text )
		return '' ;

	text = text.replace( /&gt;/g, '>' ) ;
	text = text.replace( /&lt;/g, '<' ) ;
	text = text.replace( /&amp;/g, '&' ) ;

	return text ;
}

FCKTools._ProcessLineBreaksForPMode = function( oEditor, text, liState, node, strArray )
{
	var closeState = 0 ;
	var blockStartTag = "<p>" ;
	var blockEndTag = "</p>" ;
	var lineBreakTag = "<br />" ;
	if ( liState )
	{
		blockStartTag = "<li>" ;
		blockEndTag = "</li>" ;
		closeState = 1 ;
	}

	// Are we currently inside a <p> tag now?
	// If yes, close it at the next double line break.
	while ( node && node != oEditor.FCK.EditorDocument.body )
	{
		if ( node.tagName.toLowerCase() == 'p' )
		{
			closeState = 1 ;
			break;
		}
		node = node.parentNode ;
	}

	for ( var i = 0 ; i < text.length ; i++ )
	{
		var c = text.charAt( i ) ;
		if ( c == '\r' )
			continue ;

		if ( c != '\n' )
		{
			strArray.push( c ) ;
			continue ;
		}

		// Now we have encountered a line break.
		// Check if the next character is also a line break.
		var n = text.charAt( i + 1 ) ;
		if ( n == '\r' )
		{
			i++ ;
			n = text.charAt( i + 1 ) ;
		}
		if ( n == '\n' )
		{
			i++ ;	// ignore next character - we have already processed it.
			if ( closeState )
				strArray.push( blockEndTag ) ;
			strArray.push( blockStartTag ) ;
			closeState = 1 ;
		}
		else
			strArray.push( lineBreakTag ) ;
	}
}

FCKTools._ProcessLineBreaksForDivMode = function( oEditor, text, liState, node, strArray )
{
	var closeState = 0 ;
	var blockStartTag = "<div>" ;
	var blockEndTag = "</div>" ;
	if ( liState )
	{
		blockStartTag = "<li>" ;
		blockEndTag = "</li>" ;
		closeState = 1 ;
	}

	// Are we currently inside a <div> tag now?
	// If yes, close it at the next double line break.
	while ( node && node != oEditor.FCK.EditorDocument.body )
	{
		if ( node.tagName.toLowerCase() == 'div' )
		{
			closeState = 1 ;
			break ;
		}
		node = node.parentNode ;
	}

	for ( var i = 0 ; i < text.length ; i++ )
	{
		var c = text.charAt( i ) ;
		if ( c == '\r' )
			continue ;

		if ( c != '\n' )
		{
			strArray.push( c ) ;
			continue ;
		}

		if ( closeState )
		{
			if ( strArray[ strArray.length - 1 ] == blockStartTag )
			{
				// A div tag must have some contents inside for it to be visible.
				strArray.push( "&nbsp;" ) ;
			}
			strArray.push( blockEndTag ) ;
		}
		strArray.push( blockStartTag ) ;
		closeState = 1 ;
	}
	if ( closeState )
		strArray.push( blockEndTag ) ;
}

FCKTools._ProcessLineBreaksForBrMode = function( oEditor, text, liState, node, strArray )
{
	var closeState = 0 ;
	var blockStartTag = "<br />" ;
	var blockEndTag = "" ;
	if ( liState )
	{
		blockStartTag = "<li>" ;
		blockEndTag = "</li>" ;
		closeState = 1 ;
	}

	for ( var i = 0 ; i < text.length ; i++ )
	{
		var c = text.charAt( i ) ;
		if ( c == '\r' )
			continue ;

		if ( c != '\n' )
		{
			strArray.push( c ) ;
			continue ;
		}

		if ( closeState && blockEndTag.length )
			strArray.push ( blockEndTag ) ;
		strArray.push( blockStartTag ) ;
		closeState = 1 ;
	}
}

FCKTools.ProcessLineBreaks = function( oEditor, oConfig, text )
{
	var enterMode = oConfig.EnterMode.toLowerCase() ;
	var strArray = [] ;

	// Is the caret or selection inside an <li> tag now?
	var liState = 0 ;
	var range = new oEditor.FCKDomRange( oEditor.FCK.EditorWindow ) ;
	range.MoveToSelection() ;
	var node = range._Range.startContainer ;
	while ( node && node.nodeType != 1 )
		node = node.parentNode ;
	if ( node && node.tagName.toLowerCase() == 'li' )
		liState = 1 ;

	if ( enterMode == 'p' )
		this._ProcessLineBreaksForPMode( oEditor, text, liState, node, strArray ) ;
	else if ( enterMode == 'div' )
		this._ProcessLineBreaksForDivMode( oEditor, text, liState, node, strArray ) ;
	else if ( enterMode == 'br' )
		this._ProcessLineBreaksForBrMode( oEditor, text, liState, node, strArray ) ;
	return strArray.join( "" ) ;
}

/**
 * Adds an option to a SELECT element.
 */
FCKTools.AddSelectOption = function( selectElement, optionText, optionValue )
{
	var oOption = FCKTools.GetElementDocument( selectElement ).createElement( "OPTION" ) ;

	oOption.text	= optionText ;
	oOption.value	= optionValue ;

	selectElement.options.add(oOption) ;

	return oOption ;
}

FCKTools.RunFunction = function( func, thisObject, paramsArray, timerWindow )
{
	if ( func )
		this.SetTimeout( func, 0, thisObject, paramsArray, timerWindow ) ;
}

FCKTools.SetTimeout = function( func, milliseconds, thisObject, paramsArray, timerWindow )
{
	return ( timerWindow || window ).setTimeout(
		function()
		{
			if ( paramsArray )
				func.apply( thisObject, [].concat( paramsArray ) ) ;
			else
				func.apply( thisObject ) ;
		},
		milliseconds ) ;
}

FCKTools.SetInterval = function( func, milliseconds, thisObject, paramsArray, timerWindow )
{
	return ( timerWindow || window ).setInterval(
		function()
		{
			func.apply( thisObject, paramsArray || [] ) ;
		},
		milliseconds ) ;
}

FCKTools.ConvertStyleSizeToHtml = function( size )
{
	return size.EndsWith( '%' ) ? size : parseInt( size, 10 ) ;
}

FCKTools.ConvertHtmlSizeToStyle = function( size )
{
	return size.EndsWith( '%' ) ? size : ( size + 'px' ) ;
}

// START iCM MODIFICATIONS
// Amended to accept a list of one or more ascensor tag names
// Amended to check the element itself before working back up through the parent hierarchy
FCKTools.GetElementAscensor = function( element, ascensorTagNames )
{
//	var e = element.parentNode ;
	var e = element ;
	var lstTags = "," + ascensorTagNames.toUpperCase() + "," ;

	while ( e )
	{
		if ( lstTags.indexOf( "," + e.nodeName.toUpperCase() + "," ) != -1 )
			return e ;

		e = e.parentNode ;
	}
	return null ;
}
// END iCM MODIFICATIONS

FCKTools.CreateEventListener = function( func, params )
{
	var f = function()
	{
		var aAllParams = [] ;

		for ( var i = 0 ; i < arguments.length ; i++ )
			aAllParams.push( arguments[i] ) ;

		func.apply( this, aAllParams.concat( params ) ) ;
	}

	return f ;
}

FCKTools.IsStrictMode = function( document )
{
	// There is no compatMode in Safari, but it seams that it always behave as
	// CSS1Compat, so let's assume it as the default for that browser.
	return ( 'CSS1Compat' == ( document.compatMode || ( FCKBrowserInfo.IsSafari ? 'CSS1Compat' : null ) ) ) ;
}

// Transforms a "arguments" object to an array.
FCKTools.ArgumentsToArray = function( args, startIndex, maxLength )
{
	startIndex = startIndex || 0 ;
	maxLength = maxLength || args.length ;

	var argsArray = new Array() ;

	for ( var i = startIndex ; i < startIndex + maxLength && i < args.length ; i++ )
		argsArray.push( args[i] ) ;

	return argsArray ;
}

FCKTools.CloneObject = function( sourceObject )
{
	var fCloneCreator = function() {} ;
	fCloneCreator.prototype = sourceObject ;
	return new fCloneCreator ;
}

// Appends a bogus <br> at the end of the element, if not yet available.
FCKTools.AppendBogusBr = function( element )
{
	if ( !element )
		return ;

	var eLastChild = this.GetLastItem( element.getElementsByTagName('br') ) ;

	if ( !eLastChild || ( eLastChild.getAttribute( 'type', 2 ) != '_moz' && eLastChild.getAttribute( '_moz_dirty' ) == null ) )
	{
		var doc = this.GetElementDocument( element ) ;

		if ( FCKBrowserInfo.IsOpera )
			element.appendChild( doc.createTextNode('') ) ;
		else
			element.appendChild( this.CreateBogusBR( doc ) ) ;
	}
}

FCKTools.GetLastItem = function( list )
{
	if ( list.length > 0 )
		return list[ list.length - 1 ] ;

	return null ;
}

FCKTools.GetDocumentPosition = function( w, node )
{
	var x = 0 ;
	var y = 0 ;
	var curNode = node ;
	var prevNode = null ;
	var curWindow = FCKTools.GetElementWindow( curNode ) ;
	while ( curNode && !( curWindow == w && ( curNode == w.document.body || curNode == w.document.documentElement ) ) )
	{
		x += curNode.offsetLeft - curNode.scrollLeft ;
		y += curNode.offsetTop - curNode.scrollTop ;

		if ( ! FCKBrowserInfo.IsOpera )
		{
			var scrollNode = prevNode ;
			while ( scrollNode && scrollNode != curNode )
			{
				x -= scrollNode.scrollLeft ;
				y -= scrollNode.scrollTop ;
				scrollNode = scrollNode.parentNode ;
			}
		}

		prevNode = curNode ;
		if ( curNode.offsetParent )
			curNode = curNode.offsetParent ;
		else
		{
			if ( curWindow != w )
			{
				curNode = curWindow.frameElement ;
				prevNode = null ;
				if ( curNode )
					curWindow = curNode.contentWindow.parent ;
			}
			else
				curNode = null ;
		}
	}

	// document.body is a special case when it comes to offsetTop and offsetLeft values.
	// 1. It matters if document.body itself is a positioned element;
	// 2. It matters is when we're in IE and the element has no positioned ancestor.
	// Otherwise the values should be ignored.
	if ( FCKDomTools.GetCurrentElementStyle( w.document.body, 'position') != 'static'
			|| ( FCKBrowserInfo.IsIE && FCKDomTools.GetPositionedAncestor( node ) == null ) )
	{
		x += w.document.body.offsetLeft ;
		y += w.document.body.offsetTop ;
	}

	return { "x" : x, "y" : y } ;
}

FCKTools.GetWindowPosition = function( w, node )
{
	var pos = this.GetDocumentPosition( w, node ) ;
	var scroll = FCKTools.GetScrollPosition( w ) ;
	pos.x -= scroll.X ;
	pos.y -= scroll.Y ;
	return pos ;
}

FCKTools.ProtectFormStyles = function( formNode )
{
	if ( !formNode || formNode.nodeType != 1 || formNode.tagName.toLowerCase() != 'form' )
		return [] ;
	var hijackRecord = [] ;
	var hijackNames = [ 'style', 'className' ] ;
	for ( var i = 0 ; i < hijackNames.length ; i++ )
	{
		var name = hijackNames[i] ;
		if ( formNode.elements.namedItem( name ) )
		{
			var hijackNode = formNode.elements.namedItem( name ) ;
			hijackRecord.push( [ hijackNode, hijackNode.nextSibling ] ) ;
			formNode.removeChild( hijackNode ) ;
		}
	}
	return hijackRecord ;
}

FCKTools.RestoreFormStyles = function( formNode, hijackRecord )
{
	if ( !formNode || formNode.nodeType != 1 || formNode.tagName.toLowerCase() != 'form' )
		return ;
	if ( hijackRecord.length > 0 )
	{
		for ( var i = hijackRecord.length - 1 ; i >= 0 ; i-- )
		{
			var node = hijackRecord[i][0] ;
			var sibling = hijackRecord[i][1] ;
			if ( sibling )
				formNode.insertBefore( node, sibling ) ;
			else
				formNode.appendChild( node ) ;
		}
	}
}

// Perform a one-step DFS walk.
FCKTools.GetNextNode = function( node, limitNode )
{
	if ( node.firstChild )
		return node.firstChild ;
	else if ( node.nextSibling )
		return node.nextSibling ;
	else
	{
		var ancestor = node.parentNode ;
		while ( ancestor )
		{
			if ( ancestor == limitNode )
				return null ;
			if ( ancestor.nextSibling )
				return ancestor.nextSibling ;
			else
				ancestor = ancestor.parentNode ;
		}
	}
	return null ;
}

FCKTools.GetNextTextNode = function( textnode, limitNode, checkStop )
{
	node = this.GetNextNode( textnode, limitNode ) ;
	if ( checkStop && node && checkStop( node ) )
		return null ;
	while ( node && node.nodeType != 3 )
	{
		node = this.GetNextNode( node, limitNode ) ;
		if ( checkStop && node && checkStop( node ) )
			return null ;
	}
	return node ;
}

/**
 * Merge all objects passed by argument into a single object.
 */
FCKTools.Merge = function()
{
	var args = arguments ;
	var o = args[0] ;

	for ( var i = 1 ; i < args.length ; i++ )
	{
		var arg = args[i] ;
		for ( var p in arg )
			o[p] = arg[p] ;
	}

	return o ;
}

/**
 * Check if the passed argument is a real Array. It may not working when
 * calling it cross windows.
 */
FCKTools.IsArray = function( it )
{
	return ( it instanceof Array ) ;
}

/**
 * Appends a "length" property to an object, containing the number of
 * properties available on it, excluded the append property itself.
 */
FCKTools.AppendLengthProperty = function( targetObject, propertyName )
{
	var counter = 0 ;

	for ( var n in targetObject )
		counter++ ;

	return targetObject[ propertyName || 'length' ] = counter ;
}

/**
 * Gets the browser parsed version of a css text (style attribute value). On
 * some cases, the browser makes changes to the css text, returning a different
 * value. For example, hexadecimal colors get transformed to rgb().
 */
FCKTools.NormalizeCssText = function( unparsedCssText )
{
	// Injects the style in a temporary span object, so the browser parses it,
	// retrieving its final format.
	var tempSpan = document.createElement( 'span' ) ;
	tempSpan.style.cssText = unparsedCssText ;
	return tempSpan.style.cssText ;
}

/**
 * Binding the "this" reference to an object for a function.
 */
FCKTools.Bind = function( subject, func )
{
  return function(){ return func.apply( subject, arguments ) ; } ;
}

/**
 * Retrieve the correct "empty iframe" URL for the current browser, which
 * causes the minimum fuzz (e.g. security warnings in HTTPS, DNS error in
 * IE5.5, etc.) for that browser, making the iframe ready to DOM use whithout
 * having to loading an external file.
 */
FCKTools.GetVoidUrl = function()
{
	if ( FCK_IS_CUSTOM_DOMAIN )
		return "javascript: void( function(){" +
			"document.open();" +
			"document.write('<html><head><title></title></head><body></body></html>');" +
			"document.domain = '" + FCK_RUNTIME_DOMAIN + "';" +
			"document.close();" +
			"}() ) ;";

	if ( FCKBrowserInfo.IsIE )
	{
		if ( FCKBrowserInfo.IsIE7 || !FCKBrowserInfo.IsIE6 )
			return "" ;					// IE7+ / IE5.5
		else
			return "javascript: '';" ;	// IE6+
	}

	return "javascript: void(0);" ;		// All other browsers.
}

FCKTools.ResetStyles = function( element )
{
	element.style.cssText = 'margin:0;' +
		'padding:0;' +
		'border:0;' +
		'background-color:transparent;' +
		'background-image:none;' ;
}
