/* global ToolbarConfigurator, alert */

'use strict';

( function() {
	var AbstractToolbarModifier = ToolbarConfigurator.AbstractToolbarModifier;

	/**
	 * @class ToolbarConfigurator.ToolbarModifier
	 * @param {String} editorId An id of modified editor
	 * @param {Object} cfg
	 * @extends AbstractToolbarModifier
	 * @constructor
	 */
	function ToolbarModifier( editorId, cfg ) {
		AbstractToolbarModifier.call( this, editorId, cfg );

		this.removedButtons = null;
		this.originalConfig = null;
		this.actualConfig = null;
		this.emptyVisible = false;

		// edit, paste, config
		this.state = 'edit';

		this.toolbarButtons = [
			{
				text: {
					active: 'Hide empty toolbar groups',
					inactive: 'Show empty toolbar groups'
				},
				group: 'edit',
				position: 'left',
				cssClass: 'button-a-soft',
				clickCallback: function( button, buttonDefinition ) {
					var className = 'button-a-background';

					button[ button.hasClass( className ) ? 'removeClass' : 'addClass' ]( className );

					this._toggleVisibilityEmptyElements();

					if ( this.emptyVisible ) {
						button.setText( buttonDefinition.text.active );
					} else {
						button.setText( buttonDefinition.text.inactive );
					}
				}
			},
			{
				text: 'Add row separator',
				group: 'edit',
				position: 'left',
				cssClass: 'button-a-soft',
				clickCallback: function() {
					this._addSeparator();
				}
			},
			/*{
				text: 'Paste config',
				group: 'edit',
				position: 'left',
				clickCallback: function() {
					this.state = 'paste';

					this.modifyContainer.addClass( 'hidden' );
					this.configContainer.removeClass( 'hidden' );
					this.configContainer.setHtml( '<textarea></textarea>' );
					this.showToolbarBtnsByGroupName( 'config' );
				}
			},*/
			{
				text: 'Select config',
				group: 'config',
				position: 'left',
				cssClass: 'button-a-soft',
				clickCallback: function() {
					this.configContainer.findOne( 'textarea' ).$.select();
				}
			},
			{
				text: 'Back to configurator',
				group: 'config',
				position: 'right',
				cssClass: 'button-a-background',
				clickCallback: function() {
					if ( this.state === 'paste' ) {
						var cfg = this.configContainer.findOne( 'textarea' ).getValue();
						cfg = ToolbarModifier.evaluateToolbarGroupsConfig( cfg );

						if ( cfg ) {
							this.setConfig( cfg );
						} else {
							alert( 'Your pasted config is wrong.' );
						}
					}

					this.state = 'edit';
					this._showConfigurationTool();
					this.showToolbarBtnsByGroupName( this.state );
				}
			},
			{
				text: 'Get toolbar <span class="highlight">config</span>',
				group: 'edit',
				position: 'right',
				cssClass: 'button-a-background icon-pos-left icon-download',
				clickCallback: function() {
					this.state = 'config';
					this._showConfig();
					this.showToolbarBtnsByGroupName( this.state );
				}
			}
		];

		this.cachedActiveElement = null;
	}

	// Expose the class.
	ToolbarConfigurator.ToolbarModifier = ToolbarModifier;

	ToolbarModifier.prototype = Object.create( ToolbarConfigurator.AbstractToolbarModifier.prototype );

	/**
	 * @returns {Object}
	 */
	ToolbarModifier.prototype.getActualConfig = function() {
		var copy = AbstractToolbarModifier.prototype.getActualConfig.call( this );

		if ( copy.toolbarGroups ) {

			var max = copy.toolbarGroups.length;
			for ( var i = 0; i < max; i += 1 ) {
				var currentGroup = copy.toolbarGroups[ i ];

				copy.toolbarGroups[ i ] = ToolbarModifier.parseGroupToConfigValue( currentGroup );
			}

		}

		return copy;
	};

	/**
	 * @param {Function} callback
	 * @param {String} [config]
	 * @param {Boolean} [forceKeepRemoveButtons=false]
	 * @private
	 */
	ToolbarModifier.prototype._onInit = function( callback, config, forceKeepRemoveButtons ) {
		forceKeepRemoveButtons = ( forceKeepRemoveButtons === true );
		AbstractToolbarModifier.prototype._onInit.call( this, undefined, config );

		this.removedButtons = [];

		if ( forceKeepRemoveButtons ) {
			if ( this.actualConfig.removeButtons ) {
				this.removedButtons = this.actualConfig.removeButtons.split( ',' );
			} else {
				this.removedButtons = [];
			}
		} else {
			if ( !( 'removeButtons' in this.originalConfig ) ) {
				this.originalConfig.removeButtons = '';
				this.removedButtons = [];
			} else {
				this.removedButtons = this.originalConfig.removeButtons ? this.originalConfig.removeButtons.split( ',' ) : [];
			}
		}

		if ( !this.actualConfig.toolbarGroups )
			this.actualConfig.toolbarGroups = this.fullToolbarEditor.getFullToolbarGroupsConfig();

		this._fixGroups( this.actualConfig );
		this._calculateTotalBtns();

		this._createModifier();
		this._refreshMoveBtnsAvalibility();
		this._refreshBtnTabIndexes();

		if ( typeof callback === 'function' )
			callback( this.mainContainer );
	};

	/**
	 * @private
	 */
	ToolbarModifier.prototype._showConfigurationTool = function() {
		this.configContainer.addClass( 'hidden' );
		this.modifyContainer.removeClass( 'hidden' );
	};

	/**
	 * Show configuration file in tool
	 *
	 * @private
	 */
	ToolbarModifier.prototype._showConfig = function() {
		var that = this,
			actualConfig = this.getActualConfig(),
			cfg = {};
		if ( actualConfig.toolbarGroups ) {
			cfg.toolbarGroups = actualConfig.toolbarGroups;

			var groups = prepareGroups( actualConfig.toolbarGroups, this.cfg.trimEmptyGroups );

			cfg.toolbarGroups = '\n\t\t' + groups.join( ',\n\t\t' );
		}

		function prepareGroups( toolbarGroups, trimEmptyGroups ) {
			var groups = [],
				max = toolbarGroups.length;

			for ( var i = 0; i < max; i++ ) {
				var group = toolbarGroups[ i ];

				if ( group === '/' ) {
					groups.push( '\'/\'' );
					continue;
				}

				if ( trimEmptyGroups ) {
					var max2 = group.groups.length;
					while ( max2-- ) {
						var subgroup = group.groups[ max2 ];

						if ( ToolbarModifier.getTotalSubGroupButtonsNumber( subgroup, that.fullToolbarEditor ) === 0 ) {
							group.groups.splice( max2, 1 );
						}
					}
				}

				if ( !( trimEmptyGroups && group.groups.length === 0 ) ) {
					groups.push( AbstractToolbarModifier.stringifyJSONintoOneLine( group, {
						addSpaces: true,
						noQuotesOnKey: true,
						singleQuotes: true
					} ) );
				}
			}

			return groups;
		}

		if ( actualConfig.removeButtons ) {
			cfg.removeButtons = actualConfig.removeButtons;
		}

		var content = [
			'<textarea class="configCode" readonly>',
			'CKEDITOR.editorConfig = function( config ) {\n',
			( cfg.toolbarGroups ? '\tconfig.toolbarGroups = [' + cfg.toolbarGroups + '\n\t];' : '' ),
			( cfg.removeButtons ? '\n\n' : '' ),
			( cfg.removeButtons ? '\tconfig.removeButtons = \'' + cfg.removeButtons + '\';' : '' ),
			'\n};',
			'</textarea>'
		].join( '' );



		this.modifyContainer.addClass( 'hidden' );
		this.configContainer.removeClass( 'hidden' );

		this.configContainer.setHtml( content );
	};

	/**
	 * Toggle empty groups and subgroups visibility.
	 *
	 * @private
	 */
	ToolbarModifier.prototype._toggleVisibilityEmptyElements = function() {
		if ( this.modifyContainer.hasClass( 'empty-visible' ) ) {
			this.modifyContainer.removeClass( 'empty-visible' );
			this.emptyVisible = false;
		} else {
			this.modifyContainer.addClass( 'empty-visible' );
			this.emptyVisible = true;
		}

		this._refreshMoveBtnsAvalibility();
	};

	/**
	 * Creates HTML main container of modifier.
	 *
	 * @returns {CKEDITOR.dom.element}
	 * @private
	 */
	ToolbarModifier.prototype._createModifier = function() {
		var that = this;

		AbstractToolbarModifier.prototype._createModifier.call( this );

		this.modifyContainer.setHtml( this._toolbarConfigToListString() );

		var groupLi = this.modifyContainer.find( 'li[data-type="group"]' );

		this.modifyContainer.on( 'mouseleave', function() {
			this._dehighlightActiveToolGroup();
		}, this );

		var max = groupLi.count();
		for ( var i = 0; i < max; i += 1 ) {
			groupLi.getItem( i ).on( 'mouseenter', onGroupHover );
		}

		function onGroupHover() {
			that._highlightGroup( this.data( 'name' ) );
		}

		CKEDITOR.document.on( 'keypress', function( e ) {
			var nativeEvent = e.data.$,
				keyCode = nativeEvent.keyCode,
				spaceOrEnter = ( keyCode === 32 || keyCode === 13 ),
				active = new CKEDITOR.dom.element( CKEDITOR.document.$.activeElement );

			var mainContainer = active.getAscendant( function( node ) {
				return node.$ === that.mainContainer.$;
			} );

			if ( !mainContainer || !spaceOrEnter ) {
				return;
			}

			if ( active.data( 'type' ) === 'button' ) {
				active.findOne( 'input' ).$.trigger('click');
			}
		} );

		this.modifyContainer.on( 'click', function( e ) {
			var origEvent = e.data.$,
				target = new CKEDITOR.dom.element( ( origEvent.target || origEvent.srcElement ) ),
				relativeGroupOrSeparatorLi = ToolbarModifier.getGroupOrSeparatorLiAncestor( target );

			if ( !relativeGroupOrSeparatorLi ) {
				return;
			}

			that.cachedActiveElement = document.activeElement;

			// checkbox clicked
			if ( target.$ instanceof HTMLInputElement )
				that._handleCheckboxClicked( target );

			// link clicked
			else if ( target.$ instanceof HTMLButtonElement ) {
				if ( origEvent.preventDefault )
					origEvent.preventDefault();
				else
					origEvent.returnValue = false;

				var result = that._handleAnchorClicked( target.$ );

				if ( result && result.action == 'remove' )
					return;

			}

			var elementType = relativeGroupOrSeparatorLi.data( 'type' ),
				elementName = relativeGroupOrSeparatorLi.data( 'name' );

			that._setActiveElement( elementType, elementName );

			if ( that.cachedActiveElement )
				that.cachedActiveElement.focus();
		} );

		if ( !this.toolbarContainer ) {
			this._createToolbar();
			this.toolbarContainer.insertBefore( this.mainContainer.getChildren().getItem( 0 ) );
		}

		this.showToolbarBtnsByGroupName( 'edit' );

		if ( !this.configContainer ) {
			this.configContainer = new CKEDITOR.dom.element( 'div' );
			this.configContainer.addClass( 'configContainer' );
			this.configContainer.addClass( 'hidden' );

			this.mainContainer.append( this.configContainer );
		}

		return this.mainContainer;
	};

	/**
	 * Show toolbar buttons related to group name provided in argument
	 * and hide other buttons
	 * Please note: this method works on toolbar in tool, which is located
	 * on top of the tool
	 *
	 * @param {String} groupName
	 */
	ToolbarModifier.prototype.showToolbarBtnsByGroupName = function( groupName ) {
		if ( !this.toolbarContainer ) {
			return;
		}

		var allButtons = this.toolbarContainer.find( 'button' );

		var max = allButtons.count();
		for ( var i = 0; i < max; i += 1 ) {
			var currentBtn = allButtons.getItem( i );

			if ( currentBtn.data( 'group' ) == groupName )
				currentBtn.removeClass( 'hidden' );
			else
				currentBtn.addClass( 'hidden' );

		}
	};

	/**
	 * Parse group "model" to configuration value
	 *
	 * @param {Object} group
	 * @returns {Object}
	 * @private
	 */
	ToolbarModifier.parseGroupToConfigValue = function( group ) {
		if ( group.type == 'separator' ) {
			return '/';
		}

		var groups = group.groups,
			max = groups.length;

		delete group.totalBtns;
		for ( var i = 0; i < max; i += 1 ) {
			groups[ i ] = groups[ i ].name;
		}

		return group;
	};

	/**
	 * Find closest Li ancestor in DOM tree which is group or separator element
	 *
	 * @param {CKEDITOR.dom.element} element
	 * @returns {CKEDITOR.dom.element}
	 */
	ToolbarModifier.getGroupOrSeparatorLiAncestor = function( element ) {
		if ( element.$ instanceof HTMLLIElement && element.data( 'type' ) == 'group' )
			return element;
		else {
			return ToolbarModifier.getFirstAncestor( element, function( ancestor ) {
				var type = ancestor.data( 'type' );

				return ( type == 'group' || type == 'separator' );
			} );
		}
	};

	/**
	 * Set active element in tool by provided type and name.
	 *
	 * @param {String} type
	 * @param {String} name
	 */
	ToolbarModifier.prototype._setActiveElement = function( type, name ) {
		// clear current active element
		if ( this.currentActive )
			this.currentActive.elem.removeClass( 'active' );

		if ( type === null ) {
			this._dehighlightActiveToolGroup();
			this.currentActive = null;
			return;
		}

		var liElem = this.mainContainer.findOne( 'ul[data-type=table-body] li[data-type="' + type + '"][data-name="' + name + '"]' );

		liElem.addClass( 'active' );

		// setup model
		this.currentActive = {
			type: type,
			name: name,
			elem: liElem
		};

		// highlight group in toolbar
		if ( type == 'group' )
			this._highlightGroup( name );

		if ( type == 'separator' )
			this._dehighlightActiveToolGroup();
	};

	/**
	 * @returns {CKEDITOR.dom.element|null}
	 */
	ToolbarModifier.prototype.getActiveToolGroup = function() {
		if ( this.editorInstance.container )
			return this.editorInstance.container.findOne( '.cke_toolgroup.active, .cke_toolbar.active' );
		else
			return null;
	};

	/**
	 * @private
	 */
	ToolbarModifier.prototype._dehighlightActiveToolGroup = function() {
		var currentActive = this.getActiveToolGroup();

		if ( currentActive )
			currentActive.removeClass( 'active' );

		// @see ToolbarModifier.prototype._highlightGroup.
		if ( this.editorInstance.container ) {
			this.editorInstance.container.removeClass( 'some-toolbar-active' );
		}
	};

	/**
	 * Highlight group by its name, and dehighlight current group.
	 *
	 * @param {String} name
	 */
	ToolbarModifier.prototype._highlightGroup = function( name ) {
		if ( !this.editorInstance.container )
			return;

		var foundBtnName = this.getFirstEnabledButtonInGroup( name ),
			foundBtn = this.editorInstance.container.findOne( '.cke_button__' + foundBtnName + ', .cke_combo__' + foundBtnName );

		this._dehighlightActiveToolGroup();

		// Helpful to dim other toolbar groups if one is highlighted.
		if ( this.editorInstance.container ) {
			this.editorInstance.container.addClass( 'some-toolbar-active' );
		}

		if ( foundBtn ) {
			var btnToolbar = ToolbarModifier.getFirstAncestor( foundBtn, function( ancestor ) {
				return ancestor.hasClass( 'cke_toolbar' );
			} );

			if ( btnToolbar )
				btnToolbar.addClass( 'active' );
		}
	};

	/**
	 * @param {String} groupName
	 * @return {String|null}
	 */
	ToolbarModifier.prototype.getFirstEnabledButtonInGroup = function( groupName ) {
		var groups = this.actualConfig.toolbarGroups,
			groupIndex = this.getGroupIndex( groupName ),
			group = groups[ groupIndex ];

		if ( groupIndex === -1 ) {
			return null;
		}

		var max = group.groups ? group.groups.length : 0;
		for ( var i = 0; i < max; i += 1 ) {
			var currSubgroupName = group.groups[ i ].name,
				firstEnabled = this.getFirstEnabledButtonInSubgroup( currSubgroupName );

			if ( firstEnabled )
				return firstEnabled;
		}
		return null;
	};

	/**
	 * @param {String} subgroupName
	 * @returns {String|null}
	 */
	ToolbarModifier.prototype.getFirstEnabledButtonInSubgroup = function( subgroupName ) {
		var subgroupBtns = this.fullToolbarEditor.buttonsByGroup[ subgroupName ];

		var max = subgroupBtns ? subgroupBtns.length : 0;
		for ( var i = 0; i < max; i += 1 ) {
			var currBtnName = subgroupBtns[ i ].name;
			if ( !this.isButtonRemoved( currBtnName ) )
				return currBtnName;
		}

		return null;
	};

	/**
	 * Sets up parameters and call adequate action.
	 *
	 * @param {CKEDITOR.dom.element} checkbox
	 * @private
	 */
	ToolbarModifier.prototype._handleCheckboxClicked = function( checkbox ) {
		var closestLi = checkbox.getAscendant( 'li' ),
			elementName = closestLi.data( 'name' ),
			aboutToAddToRemoved = !checkbox.$.checked;

		if ( aboutToAddToRemoved )
			this._addButtonToRemoved( elementName );
		else
			this._removeButtonFromRemoved( elementName );
	};

	/**
	 * Sets up parameters and call adequate action.
	 *
	 * @param {HTMLAnchorElement} anchor
	 * @private
	 */
	ToolbarModifier.prototype._handleAnchorClicked = function( anchor ) {
		var anchorDOM = new CKEDITOR.dom.element( anchor ),
			relativeLi = anchorDOM.getAscendant( 'li' ),
			relativeUl = relativeLi.getAscendant( 'ul' ),
			elementType = relativeLi.data( 'type' ),
			elementName = relativeLi.data( 'name' ),
			direction = anchorDOM.data( 'direction' ),
			nearestLi = ( direction === 'up' ? relativeLi.getPrevious() : relativeLi.getNext() ),
			groupName,
			subgroupName,
			newIndex;

		// nothing to do
		if ( anchorDOM.hasClass( 'disabled' ) )
			return null;

		// remove separator and nothing else
		if ( anchorDOM.hasClass( 'remove' ) ) {
			relativeLi.remove();
			this._removeSeparator( relativeLi.data( 'name' ) );
			this._setActiveElement( null );
			return { action: 'remove' };
		}

		if ( !anchorDOM.hasClass( 'move' ) || !nearestLi )
			return { action: null };

		// move group or separator
		if ( elementType === 'group' || elementType === 'separator' ) {
			groupName = elementName;
			newIndex = this._moveGroup( direction, groupName );
		}

		// move subgroup
		if ( elementType === 'subgroup' ) {
			subgroupName = elementName;
			groupName = relativeLi.getAscendant( 'li' ).data( 'name' );
			newIndex = this._moveSubgroup( direction, groupName, subgroupName );
		}

		// Visual effect
		if ( direction === 'up' )
			relativeLi.insertBefore( relativeUl.getChild( newIndex ) );

		if ( direction === 'down' )
			relativeLi.insertAfter( relativeUl.getChild( newIndex ) );

		// Should know whether there is next li element after modifications.
		var nextLi = relativeLi;

		// We are looking for next li element in list (to check whether current one is the last one)
		var found;
		while ( nextLi = ( direction === 'up' ? nextLi.getPrevious() : nextLi.getNext() ) ) {
			if ( !this.emptyVisible && nextLi.hasClass( 'empty' ) ) {
				continue;
			}

			found = nextLi;
			break;
		}

		// If not found, it means that we reached end.
		if ( !found ) {
			var selector = ( '[data-direction="' + ( direction === 'up' ? 'down' : 'up' ) + '"]' );

			// Shifting direction.
			this.cachedActiveElement = anchorDOM.getParent().findOne( selector );
		}

		this._refreshMoveBtnsAvalibility();
		this._refreshBtnTabIndexes();

		return {
			action: 'move'
		};
	};

	/**
	 * First element can not be moved up, and last element can not be moved down,
	 * so they are disabled.
	 */
	ToolbarModifier.prototype._refreshMoveBtnsAvalibility = function() {
		var that = this,
			disabledBtns = this.mainContainer.find( 'ul[data-type=table-body] li > p > span > button.move.disabled' );

		// enabling all disabled buttons
		var max = disabledBtns.count();
		for ( var i = 0; i < max; i += 1 ) {
			var currentBtn = disabledBtns.getItem( i );
			currentBtn.removeClass( 'disabled' );
		}


		function disableElementsInLists( ulList ) {
			var max = ulList.count();
			for ( i = 0; i < max; i += 1 ) {
				that._disableElementsInList( ulList.getItem( i ) );
			}
		}

		// Disable buttons in toolbars.
		disableElementsInLists( this.mainContainer.find( 'ul[data-type=table-body]' ) );

		// Disable buttons in toolbar groups.
		disableElementsInLists( this.mainContainer.find( 'ul[data-type=table-body] > li > ul' ) );
	};

	/**
	 * @private
	 */
	ToolbarModifier.prototype._refreshBtnTabIndexes = function() {
		var tabindexed = this.mainContainer.find( '[data-tab="true"]' );

		var max = tabindexed.count();
		for ( var i = 0; i < max; i++ ) {
			var item = tabindexed.getItem( i ),
				disabled = item.hasClass( 'disabled' );

			item.setAttribute( 'tabindex', disabled ? -1 : i );
		}
	};

	/**
	 * Disable buttons to move elements up and down which should be disabled.
	 *
	 * @param {CKEDITOR.dom.element} ul
	 * @private
	 */
	ToolbarModifier.prototype._disableElementsInList = function( ul ) {
		var liList = ul.getChildren();

		if ( !liList.count() )
			return;

		var firstDisabled, lastDisabled;
		if ( this.emptyVisible ) {
			firstDisabled = ul.getFirst();
			lastDisabled = ul.getLast();
		} else {
			firstDisabled = ul.getFirst( isNotEmptyChecker );
			lastDisabled = ul.getLast( isNotEmptyChecker );
		}

		function isNotEmptyChecker( element ) {
			return !element.hasClass( 'empty' );
		}

		if ( firstDisabled )
			var firstDisabledBtn = firstDisabled.findOne( 'p button[data-direction="up"]' );

		if ( lastDisabled )
			var lastDisabledBtn = lastDisabled.findOne( 'p button[data-direction="down"]' );

		if ( firstDisabledBtn ) {
			firstDisabledBtn.addClass( 'disabled' );
			firstDisabledBtn.setAttribute( 'tabindex', '-1' );
		}

		if ( lastDisabledBtn ) {
			lastDisabledBtn.addClass( 'disabled' );
			lastDisabledBtn.setAttribute( 'tabindex', '-1' );
		}
	};

	/**
	 * Gets group index in actual config toolbarGroups
	 *
	 * @param {String} name
	 * @returns {Number}
	 */
	ToolbarModifier.prototype.getGroupIndex = function( name ) {
		var groups = this.actualConfig.toolbarGroups;

		var max = groups.length;
		for ( var i = 0; i < max; i += 1 ) {
			if ( groups[ i ].name === name )
				return i;
		}

		return -1;
	};

	/**
	 * Handle adding separator.
	 *
	 * @private
	 */
	ToolbarModifier.prototype._addSeparator = function() {
		var separatorIndex = this._determineSeparatorToAddIndex(),
			separator = ToolbarModifier.createSeparatorLiteral(),
			domSeparator = CKEDITOR.dom.element.createFromHtml( ToolbarModifier.getToolbarSeparatorString( separator ) );

		this.actualConfig.toolbarGroups.splice( separatorIndex, 0, separator );

		domSeparator.insertBefore( this.modifyContainer.findOne( 'ul[data-type=table-body]' ).getChild( separatorIndex ) );

		this._setActiveElement( 'separator', separator.name );
		this._refreshMoveBtnsAvalibility();
		this._refreshBtnTabIndexes();
		this._refreshEditor();
	};

	/**
	 * Handle removing separator.
	 *
	 * @param {String} name
	 */
	ToolbarModifier.prototype._removeSeparator = function( name ) {
		var separatorIndex = CKEDITOR.tools.indexOf( this.actualConfig.toolbarGroups, function( group ) {
			return group.type == 'separator' && group.name == name;
		} );

		this.actualConfig.toolbarGroups.splice( separatorIndex, 1 );

		this._refreshMoveBtnsAvalibility();
		this._refreshBtnTabIndexes();
		this._refreshEditor();
	};

	/**
	 * Determine index where separator should be added, based on currently selected element.
	 *
	 * @returns {Number}
	 * @private
	 */
	ToolbarModifier.prototype._determineSeparatorToAddIndex = function() {
		if ( !this.currentActive )
			return 0;

		var groupLi;
		if ( this.currentActive.elem.data( 'type' ) == 'group' || this.currentActive.elem.data( 'type' ) == 'separator' )
			groupLi = this.currentActive.elem;
		else
			groupLi = this.currentActive.elem.getAscendant( 'li' );

		return groupLi.getIndex();
	};

	/**
	 * @param {Array} elementsArray
	 * @param {Number} elementIndex
	 * @param {String} direction
	 * @returns {Number}
	 * @private
	 */
	ToolbarModifier.prototype._moveElement = function( elementsArray, elementIndex, direction ) {
		var nextIndex;

		if ( this.emptyVisible )
			nextIndex = ( direction == 'down' ? elementIndex + 1 : elementIndex - 1 );
		else {
			// When empty elements are not visible, there is need to skip them.
			nextIndex = ToolbarModifier.getFirstElementIndexWith( elementsArray, elementIndex, direction, isEmptyOrSeparatorChecker );
		}

		function isEmptyOrSeparatorChecker( element ) {
			return element.totalBtns || element.type == 'separator';
		}

		var offset = nextIndex - elementIndex;

		return ToolbarModifier.moveTo( offset, elementsArray, elementIndex );
	};

	/**
	 * Moves group located in config level up or down and refresh editor.
	 *
	 * @param {String} direction
	 * @param {String} groupName
	 * @returns {Number}
	 */
	ToolbarModifier.prototype._moveGroup = function( direction, groupName ) {
		var groupIndex = this.getGroupIndex( groupName ),
			groups = this.actualConfig.toolbarGroups,
			newIndex = this._moveElement( groups, groupIndex, direction );

		this._refreshMoveBtnsAvalibility();
		this._refreshBtnTabIndexes();
		this._refreshEditor();

		return newIndex;
	};

	/**
	 * Moves subgroup located in config level up or down and refresh editor.
	 *
	 * @param {String} direction
	 * @param {String} groupName
	 * @param {String} subgroupName
	 * @private
	 */
	ToolbarModifier.prototype._moveSubgroup = function( direction, groupName, subgroupName ) {
		var groupIndex = this.getGroupIndex( groupName ),
			groups = this.actualConfig.toolbarGroups,
			group = groups[ groupIndex ],
			subgroupIndex = CKEDITOR.tools.indexOf( group.groups, function( subgroup ) {
				return subgroup.name == subgroupName;
			} ),
			newIndex = this._moveElement( group.groups, subgroupIndex, direction );

		this._refreshEditor();

		return newIndex;
	};

	/**
	 * Set `totalBtns` property in `actualConfig.toolbarGroups` elements.
	 *
	 * @private
	 */
	ToolbarModifier.prototype._calculateTotalBtns = function() {
		var groups = this.actualConfig.toolbarGroups;

		var i = groups.length;
		// from the end
		while ( i-- ) {
			var currentGroup = groups[ i ],
				totalBtns = ToolbarModifier.getTotalGroupButtonsNumber( currentGroup, this.fullToolbarEditor );

			if ( currentGroup.type == 'separator' ) {
				// nothing to do with separator
				continue;
			}

			currentGroup.totalBtns = totalBtns;
		}
	};

	/**
	 * Add button to removeButtons field in config and refresh editor.
	 *
	 * @param {String} buttonName
	 * @private
	 */
	ToolbarModifier.prototype._addButtonToRemoved = function( buttonName ) {
		if ( CKEDITOR.tools.indexOf( this.removedButtons, buttonName ) != -1 )
			throw 'Button already added to removed';

		this.removedButtons.push( buttonName );
		this.actualConfig.removeButtons = this.removedButtons.join( ',' );
		this._refreshEditor();
	};

	/**
	 * Remove button from removeButtons field in config and refresh editor.
	 *
	 * @param {String} buttonName
	 * @private
	 */
	ToolbarModifier.prototype._removeButtonFromRemoved = function( buttonName ) {
		var foundAtIndex =  CKEDITOR.tools.indexOf( this.removedButtons, buttonName );

		if ( foundAtIndex === -1 )
			throw 'Trying to remove button from removed, but not found';

		this.removedButtons.splice( foundAtIndex, 1 );
		this.actualConfig.removeButtons = this.removedButtons.join( ',' );
		this._refreshEditor();
	};

	/**
	 * Parse group "model" to configuration value
	 *
	 * @param {Object} group
	 * @returns {Object}
	 * @static
	 */
	ToolbarModifier.parseGroupToConfigValue = function( group ) {
		if ( group.type == 'separator' ) {
			return '/';
		}

		var groups = group.groups,
			max = groups.length;

		delete group.totalBtns;
		for ( var i = 0; i < max; i += 1 ) {
			groups[ i ] = groups[ i ].name;
		}

		return group;
	};

	/**
	 * Find closest Li ancestor in DOM tree which is group or separator element
	 *
	 * @param {CKEDITOR.dom.element} element
	 * @returns {CKEDITOR.dom.element}
	 * @static
	 */
	ToolbarModifier.getGroupOrSeparatorLiAncestor = function( element ) {
		if ( element.$ instanceof HTMLLIElement && element.data( 'type' ) == 'group' )
			return element;
		else {
			return ToolbarModifier.getFirstAncestor( element, function( ancestor ) {
				var type = ancestor.data( 'type' );

				return ( type == 'group' || type == 'separator' );
			} );
		}
	};

	/**
	 * Create separator literal with unique id.
	 *
	 * @public
	 * @static
	 * @return {Object}
	 */
	ToolbarModifier.createSeparatorLiteral = function() {
		return {
			type: 'separator',
			name: ( 'separator' + CKEDITOR.tools.getNextNumber() )
		};
	};

	/**
	 * Creates HTML unordered list string based on toolbarGroups field in config.
	 *
	 * @returns {String}
	 * @static
	 */
	ToolbarModifier.prototype._toolbarConfigToListString = function() {
		var groups = this.actualConfig.toolbarGroups || [],
			listString = '<ul data-type="table-body">';

		var max = groups.length;
		for ( var i = 0; i < max; i += 1 ) {
			var currentGroup = groups[ i ];

			if ( currentGroup.type === 'separator' )
				listString += ToolbarModifier.getToolbarSeparatorString( currentGroup );
			else
				listString += this._getToolbarGroupString( currentGroup );
		}

		listString += '</ul>';

		var headerString = ToolbarModifier.getToolbarHeaderString();

		return headerString + listString;
	};

	/**
	 * Created HTML group list element based on group field in config.
	 *
	 * @param {Object} group
	 * @returns {String}
	 * @private
	 */
	ToolbarModifier.prototype._getToolbarGroupString = function( group ) {
		var subgroups = group.groups,
			groupString = '';

		groupString += [
			'<li ',
				'data-type="group" ',
				'data-name="', group.name, '" ',
				( group.totalBtns ? '' : 'class="empty"' ),
			'>'
		].join( '' );
		groupString += ToolbarModifier.getToolbarElementPreString( group ) + '<ul>';

		var max = subgroups.length;

		for ( var i = 0; i < max; i += 1 ) {
			var currentSubgroup = subgroups[ i ],
				subgroupBtns = this.fullToolbarEditor.buttonsByGroup[ currentSubgroup.name ];

			groupString += this._getToolbarSubgroupString( currentSubgroup, subgroupBtns );
		}
		groupString += '</ul></li>';

		return groupString;
	};

	/**
	 * @param {Object} separator
	 * @returns {String}
	 * @static
	 */
	ToolbarModifier.getToolbarSeparatorString = function( separator ) {
		return [
			'<li ',
			'data-type="', separator.type , '" ',
			'data-name="', separator.name , '"',
			'>',
			ToolbarModifier.getToolbarElementPreString( 'row separator' ),
			'</li>'
		].join( '' );
	};

	/**
	 * @returns {string}
	 */
	ToolbarModifier.getToolbarHeaderString = function() {
		return '<ul data-type="table-header">' +
			'<li data-type="header">' +
				'<p>Toolbars</p>' +
				'<ul>' +
					'<li>' +
						'<p>Toolbar groups</p>' +
						'<p>Toolbar group items</p>' +
					'</li>' +
				'</ul>' +
			'</li>' +
		'</ul>';
	};

	/**
	 * Find and return first ancestor of element provided in first argument
	 * which match the criteria checked in function provided in second argument.
	 *
	 * @param {CKEDITOR.dom.element} element
	 * @param {Function} checker
	 * @returns {CKEDITOR.dom.element|null}
	 */
	ToolbarModifier.getFirstAncestor = function( element, checker ) {
		var ancestors = element.getParents(),
			i = ancestors.length;

		while ( i-- ) {
			if ( checker( ancestors[ i ] ) )
				return ancestors[ i ];
		}

		return null;
	};

	/**
	 * Looking through array elements start from index provided in second argument
	 * and go 'up' or 'down' in array
	 * last argument is condition checker which should return Boolean value
	 *
	 * User cases:
	 *
	 * ToolbarModifier.getFirstElementIndexWith( [3, 4, 8, 1, 4], 2, 'down', function( elem ) { return elem == 4; } ); // 4
	 * ToolbarModifier.getFirstElementIndexWith( [3, 4, 8, 1, 4], 2, 'up', function( elem ) { return elem == 4; } ); // 1
	 *
	 * @param {Array} array
	 * @param {Number} i
	 * @param {String} direction 'up' or 'down'
	 * @param {Function} conditionChecker
	 * @static
	 * @returns {Number} index of found element
	 */
	ToolbarModifier.getFirstElementIndexWith = function( array, i, direction, conditionChecker ) {
		function whileChecker() {
			var result;
			if ( direction === 'up' )
				result = i--;
			else
				result = ( ++i < array.length );

			return result;
		}

		while ( whileChecker() ) {
			if ( conditionChecker( array[ i ] ) )
				return i;

		}

		return -1;
	};

	/**
	 * Moves array element at index level up or down.
	 *
	 * @static
	 * @param {String} direction
	 * @param {Array} array
	 * @param {Number} index
	 * @returns {Number}
	 */
	ToolbarModifier.moveTo = function( offset, array, index ) {
		var element, newIndex;

		if ( index !== -1 )
			element = array.splice( index, 1 )[ 0 ];

		newIndex = index + offset;

		array.splice( newIndex, 0, element );

		return newIndex;
	};

	/**
	 * @static
	 * @param {Object} subgroup
	 * @returns {Number}
	 */
	ToolbarModifier.getTotalSubGroupButtonsNumber = function( subgroup, fullToolbarEditor ) {
		var subgroupName = ( typeof subgroup == 'string' ? subgroup : subgroup.name ),
			subgroupBtns = fullToolbarEditor.buttonsByGroup[ subgroupName ];

		return ( subgroupBtns ? subgroupBtns.length : 0 );
	};

	/**
	 * Returns all buttons number in group which are nested in subgroups also.
	 *
	 * @param {Object} group
	 * @param {ToolbarModifier.FullToolbarEditor}
	 * @static
	 * @returns {Number}
	 */
	ToolbarModifier.getTotalGroupButtonsNumber = function( group, fullToolbarEditor ) {
		var total = 0,
			subgroups = group.groups;

		var max = subgroups ? subgroups.length : 0;
		for ( var i = 0; i < max; i += 1 )
			total += ToolbarModifier.getTotalSubGroupButtonsNumber( subgroups[ i ], fullToolbarEditor );

		return total;
	};

	/**
	 * Creates HTML subgroup list element based on subgroup field in config.
	 *
	 * @param {Object} subgroup
	 * @param {Array} groupBtns
	 * @returns {String}
	 * @private
	 */
	ToolbarModifier.prototype._getToolbarSubgroupString = function( subgroup, groupBtns ) {
		var subgroupString = '';

		subgroupString += [
			'<li ',
				'data-type="subgroup" ',
				'data-name="', subgroup.name, '" ',
				( subgroup.totalBtns ? '' : 'class="empty" ' ),
			'>'
		].join( '' );
		subgroupString += ToolbarModifier.getToolbarElementPreString( subgroup.name );
		subgroupString += '<ul>';

		var max = groupBtns ? groupBtns.length : 0;
		for ( var i = 0; i < max; i += 1 )
			subgroupString += this.getButtonString( groupBtns[ i ] );

		subgroupString += '</ul>';

		subgroupString += '</li>';

		return subgroupString;
	};

	/**
	 * @param {String} buttonName
	 * @returns {String|null}
	 * @private
	 */
	ToolbarModifier.prototype._getConfigButtonName = function( buttonName ) {
		var items = this.fullToolbarEditor.editorInstance.ui.items;

		var name;
		for ( name in items ) {
			if ( items[ name ].name == buttonName )
				return name;
		}

		return null;
	};

	/**
	 * @param {String} buttonName
	 * @returns {Boolean}
	 */
	ToolbarModifier.prototype.isButtonRemoved = function( buttonName ) {
		return CKEDITOR.tools.indexOf( this.removedButtons, this._getConfigButtonName( buttonName ) ) != -1;
	};

	/**
	 * @param {CKEDITOR.ui.button/CKEDITOR.ui.richCombo} button
	 * @returns {String}
	 * @public
	 */
	ToolbarModifier.prototype.getButtonString = function( button ) {
		var checked = ( this.isButtonRemoved( button.name ) ? '' : 'checked="checked"' );

		return [
			'<li data-tab="true" data-type="button" data-name="', this._getConfigButtonName( button.name ), '">',
				'<label title="', button.label, '" >',
					'<input ',
						'tabindex="-1"',
						'type="checkbox"',
						checked,
					'/>',
					button.$.getOuterHtml(),
				'</label>',
			'</li>'
		].join( '' );
	};

	/**
	 * Creates group header string.
	 *
	 * @param {Object|String} group
	 * @returns {String}
	 * @static
	 */
	ToolbarModifier.getToolbarElementPreString = function( group ) {
		var name = ( group.name ? group.name : group );

		return [
			'<p>',
				'<span>',
					'<button title="Move element upward" data-tab="true" data-direction="up" class="move icon-up-big"></button>',
					'<button title="Move element downward" data-tab="true" data-direction="down" class="move icon-down-big"></button>',
					( name == 'row separator' ? '<button title="Remove element" data-tab="true" class="remove icon-trash"></button>' : '' ),
					name,
				'</span>',
			'</p>'
		].join( '' );
	};

	/**
	 * @static
	 * @param {String} cfg
	 * @returns {String}
	 */
	ToolbarModifier.evaluateToolbarGroupsConfig = function( cfg ) {
		cfg = ( function( cfg ) {
			var config = {}, result;

			/*jshint -W002 */
			try {
				result = eval( '(' + cfg + ')' );
			} catch ( e ) {
				try {
					result = eval( cfg );
				} catch ( e ) {
					return null;
				}
			}
			/*jshint +W002 */

			if ( config.toolbarGroups && typeof config.toolbarGroups.length === 'number' ) {
				return JSON.stringify( config );
			} else if ( result && typeof result.length === 'number' ) {
				return JSON.stringify( { toolbarGroups: result } );
			} else if ( result && result.toolbarGroups ) {
				return JSON.stringify( result );
			} else {
				return null;
			}

		}( cfg ) );

		return cfg;
	};

	return ToolbarModifier;
} )();
