/*
Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
For licensing, see LICENSE.html or http://ckeditor.com/license
*/

(function()
{
	var guardElements = { table:1, ul:1, ol:1, blockquote:1, div:1 };
	var directSelectionGuardElements = {};
	CKEDITOR.tools.extend( directSelectionGuardElements, guardElements, { tr:1, p:1, div:1, li:1 } );

	function onSelectionChange( evt )
	{
		evt.editor.getCommand( 'bidirtl' ).setState( getState( evt.editor, evt.data.path, 'rtl' ) );
		evt.editor.getCommand( 'bidiltr' ).setState( getState( evt.editor, evt.data.path, 'ltr' ) );
	}

	function getState( editor, path, dir )
	{
		var useComputedState = editor.config.useComputedState,
			selectedElement;

		useComputedState = useComputedState === undefined || useComputedState;

		if ( useComputedState )
		{
			var selection = editor.getSelection(),
				ranges = selection.getRanges();

			selectedElement = ranges && ranges[ 0 ].getEnclosedNode();

			// If this is not our element of interest, apply to fully selected elements from guardElements.
			if ( !selectedElement || selectedElement
					&& !( selectedElement.type == CKEDITOR.NODE_ELEMENT && selectedElement.getName() in directSelectionGuardElements )
				)
				selectedElement = getFullySelected( selection, guardElements );
		}

		selectedElement = selectedElement || path.block || path.blockLimit;

		if ( !selectedElement || selectedElement.getName() == 'body' )
			return CKEDITOR.TRISTATE_OFF;

		selectedElement = useComputedState ?
			selectedElement.getComputedStyle( 'direction' ) :
			selectedElement.getStyle( 'direction' ) || selectedElement.getAttribute( 'dir' );

		return ( selectedElement == dir ) ?
			CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF;
	}

	function switchDir( element, dir, editor )
	{
		var dirBefore = element.getComputedStyle( 'direction' ),
			currentDir = element.getStyle( 'direction' ) || element.getAttribute( 'dir' ) || '';

		element.removeStyle( 'direction' );

		if ( currentDir.toLowerCase() == dir )
			element.removeAttribute( 'dir' );
		else
			element.setAttribute( 'dir', dir );

		// If the element direction changed, we need to switch the margins of
		// the element and all its children, so it will get really reflected
		// like a mirror. (#5910)
		var dirAfter = element.getComputedStyle( 'direction' );
		if ( dirAfter != dirBefore )
		{
			var range = new CKEDITOR.dom.range( element.getDocument() );
			range.setStartBefore( element );
			range.setEndAfter( element );

			var walker = new CKEDITOR.dom.walker( range );

			var node;
			while ( ( node = walker.next() ) )
			{
				if ( node.type == CKEDITOR.NODE_ELEMENT )
				{
					// A child with dir defined is to be ignored.
					if ( !node.equals( element ) && node.hasAttribute( 'dir' ) )
					{
						range.setStartAfter( node );
						walker = new CKEDITOR.dom.walker( range );
						continue;
					}

					// Switch the margins.
					var marginLeft = node.getStyle( 'margin-right' ),
						marginRight = node.getStyle( 'margin-left' );

					marginLeft ? node.setStyle( 'margin-left', marginLeft ) : node.removeStyle( 'margin-left' );
					marginRight ? node.setStyle( 'margin-right', marginRight ) : node.removeStyle( 'margin-right' );
				}
			}
		}

		editor.forceNextSelectionCheck();
	}

	function getFullySelected( selection, elements )
	{
		var selectedElement = selection.getCommonAncestor();
		while( selectedElement.type == CKEDITOR.NODE_ELEMENT
				&& !( selectedElement.getName() in elements )
				&& selectedElement.getParent().getChildCount() == 1
			)
			selectedElement = selectedElement.getParent();

		return selectedElement.type == CKEDITOR.NODE_ELEMENT
			&& ( selectedElement.getName() in elements )
			&& selectedElement;
	}

	function bidiCommand( dir )
	{
		return function( editor )
		{
			var selection = editor.getSelection(),
				enterMode = editor.config.enterMode,
				ranges = selection.getRanges();

			if ( ranges && ranges.length )
			{
				// Apply do directly selected elements from guardElements.
				var selectedElement = ranges[ 0 ].getEnclosedNode();

				// If this is not our element of interest, apply to fully selected elements from guardElements.
				if ( !selectedElement || selectedElement
						&& !( selectedElement.type == CKEDITOR.NODE_ELEMENT && selectedElement.getName() in directSelectionGuardElements )
					)
					selectedElement = getFullySelected( selection, guardElements );

				if ( selectedElement )
				{
					if ( !selectedElement.isReadOnly() )
						switchDir( selectedElement, dir, editor );
				}
				else
				{
					// Creates bookmarks for selection, as we may split some blocks.
					var bookmarks = selection.createBookmarks();

					var iterator,
						block;

					for ( var i = ranges.length - 1 ; i >= 0 ; i-- )
					{
						// Array of elements processed as guardElements.
						var processedElements = [];
						// Walker searching for guardElements.
						var walker = new CKEDITOR.dom.walker( ranges[ i ] );
						walker.evaluator = function( node ){
							return node.type == CKEDITOR.NODE_ELEMENT
								&& node.getName() in guardElements
								&& !( node.getName() == ( enterMode == CKEDITOR.ENTER_P ) ? 'p' : 'div'
									&& node.getParent().type == CKEDITOR.NODE_ELEMENT
									&& node.getParent().getName() == 'blockquote'
								);
						};

						while ( ( block = walker.next() ) )
						{
							switchDir( block, dir, editor );
							processedElements.push( block );
						}

						iterator = ranges[ i ].createIterator();
						iterator.enlargeBr = enterMode != CKEDITOR.ENTER_BR;

						while ( ( block = iterator.getNextParagraph( enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' ) ) )
						{
							if ( block.isReadOnly() )
								continue;

							var _break = 0;

							// Check if block have been already processed by the walker above.
							for ( var ii = 0; ii < processedElements.length; ii++ )
							{
								var parent = block.getParent();

								while( parent && parent.getName() != 'body' )
								{
									if ( ( parent.$.isSameNode && parent.$.isSameNode( processedElements[ ii ].$ ) )
											|| parent.$ == processedElements[ ii ].$ )
									{
										_break = 1;
										break;
									}
									parent = parent.getParent();
								}

								if ( _break )
									break;
							}

							if ( !_break )
							{
								switchDir( block, dir, editor );
							}
						}
					}

					editor.forceNextSelectionCheck();
					// Restore selection position.
					selection.selectBookmarks( bookmarks );
				}

				editor.focus();
			}
		};
	}

	CKEDITOR.plugins.add( 'bidi',
	{
		requires : [ 'styles', 'button' ],

		init : function( editor )
		{
			// All buttons use the same code to register. So, to avoid
			// duplications, let's use this tool function.
			var addButtonCommand = function( buttonName, buttonLabel, commandName, commandExec )
			{
				editor.addCommand( commandName, new CKEDITOR.command( editor, { exec : commandExec }) );

				editor.ui.addButton( buttonName,
					{
						label : buttonLabel,
						command : commandName
					});
			};

			var lang = editor.lang.bidi;

			addButtonCommand( 'BidiLtr', lang.ltr, 'bidiltr', bidiCommand( 'ltr' ) );
			addButtonCommand( 'BidiRtl', lang.rtl, 'bidirtl', bidiCommand( 'rtl' ) );

			editor.on( 'selectionChange', onSelectionChange );
		}
	});

})();
