/*
 *  Copyright 2013 Anyware Services
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/**
 * Class to handle basic styles such as bold, italic, supscript, ...
 * @private
 */
Ext.define('Ametys.plugins.cms.editor.BasicStyles', {
	singleton: true,

	/**
	 * @protected
	 * Converts the result of a query command state to a boolean
	 * @param {Boolean} state Can be null or a boolean
	 * @returns {Boolean} Non null value.
	 */
	testQueryCommandState: function(state)
	{
		return state != undefined && state != false;
	},
	
	/**
	 * @protected
	 * Move the current tinymce editor selection to an alignable elements.
	 * Current impl does move selection from an image node to its parent node
	 * @param {Ametys.cms.form.widget.RichText} field The tinymce editor field into
	 */
	changeSelectionToAlignableElement: function(field)
	{
		var n = field.getEditor().selection.getNode();
		if (/^img$/i.test(n.tagName))
		{
            field.getEditor().selection.collapse();
		}
	},
	
	/**
	 * @protected
	 * Determines if the current tinymce editor selection can be manually aligned
	 * Current impl does test for caption elements
	 * @param {Ametys.cms.form.widget.RichText} field The tinymce editor field
	 * @return {boolean} True if #getManuallyAlignement and #setManuallyAlignement can be called
	 */
	isManuallyAlignableElement: function(field)
	{
		return /^caption$/i.test(field.getEditor().selection.getNode().tagName);
	},
	
	/**
	 * @protected
	 * If the current tinymce editor selection #isManuallyAlignableElement, this method get the align value
	 * @param {Ametys.cms.form.widget.RichText} field The tinymce editor field
	 * @return {String} The css align value. (empty, 'left', 'right', 'center', 'justify')
	 */
	getManuallyAlignement: function(field)
	{
		return field.getEditor().selection.getNode().style.textAlign;
	},
	
	/**
	 * @protected
	 * If the current tinymce editor selection #isManuallyAlignableElement, this method change the align value
	 * @param {Ametys.cms.form.widget.RichText} field The tinymce editor field
	 * @param {String} align The css align value. (empty, 'left', 'right', 'center', 'justify')
	 * @param {Boolean} [force=true] When force is true, the align value is set. If not, it is toggled
	 * @param {Boolean} [sendSelect=false] If true, send a mceSelectNode command.
	 */
	setManuallyAlignement: function(field, align, force, sendSelect)
	{
		force = force === true;
		sendSelect = sendSelect !== false;
		
		if (force || this.getManuallyAlignement(field) != align)
		{
			field.getEditor().selection.getNode().style.textAlign = align;
		}
		else
		{
			field.getEditor().selection.getNode().style.textAlign = "";
		}
		field.getEditor().selection.getNode().removeAttribute("data-mce-style");

		if (sendSelect)
		{
			field.getEditor().execCommand('mceSelectNode', false, field.getEditor().selection.getNode());
		}
	}
});
		
/**
 * This singleton class handles bold style
 * @private
 */	
Ext.define('Ametys.plugins.cms.editor.basicstyles.Bold', {
	singleton: true,
	
	/**
	 * Apply or remove bold style
	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
	 */
	apply: function (controller)
	{
		var field = controller.getCurrentField();

		field.getEditor().execCommand("Bold");
		field.getEditor().focus();
	},
	
	/**
	 * Enable/disable and toggle/untoggle controller according the current selection
	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The current selected node. Can be null.
	 */
	selectionListener: function (controller, field, node)
	{
		var off = field == null || node == null || field.getEditor().dom.hasClass(node, "mceNonEditable");
		var state = !off && Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("Bold"));
		
		controller.toggle(state);
		off ? controller.disable() : controller.enable();
	}
});

/**
 * This singleton class handles italic style
 * @private
 */
Ext.define('Ametys.plugins.cms.editor.basicstyles.Italic', {
	singleton: true,
	
	/**
	 * Apply or remove italic style
	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
	 */
	apply: function (controller)
	{
		var field = controller.getCurrentField();

		field.getEditor().execCommand("Italic");
		field.getEditor().focus();
	},
	
	/**
	 * Enable/disable and toggle/untoggle controller according the current selection
	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The current selected node. Can be null.
	 */
	selectionListener: function (controller, field, node)
	{
		var off = field == null || node == null || field.getEditor().dom.hasClass(node, "mceNonEditable");
		var state = !off && Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("Italic"));
		
		controller.toggle(state);
		controller.setDisabled(off);
	}
});

/**
 * This singleton class is used to indent text
 * @private
 */
Ext.define('Ametys.plugins.cms.editor.basicstyles.Indent', {
	singleton: true,
	
	/**
	 * Indent text
	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
	 */
	apply: function (controller)
	{
		var field = controller.getCurrentField();

		field.getEditor().execCommand("Indent");
		field.getEditor().focus();
        
        // check that we are in a list (ordered or not)
	    if (Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("InsertUnorderedList")) || Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("InsertOrderedList")))
	    {
	        // get the first parent UL with a non-empty class name
	        var parentUl = field.getEditor().dom.getParent(field.getEditor().selection.getNode(), function(node) { return /^ul|ol$/i.test(node.tagName) && node.className != ''} );
	        if (parentUl != null)
	        {
	            // apply parent class name to indented list
	            var parentClassName = parentUl.className;
	            field.getEditor().selection.getNode().parentNode.setAttribute("class", parentUl.className);
	        }
	    }
	},
	
	/**
	 * Enable/disable controller according the current selection
	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The current selected node. Can be null.
	 */
	selectionListener: function (controller, field, node)
	{
		var off = field == null || node == null || field.getEditor().dom.hasClass(node, "mceNonEditable");
		var on = false;
		
		if (!off && field.getEditor() != null && field.getEditor().dom != null)
		{
			// check that we are in a list (ordered or not)
			on = Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("InsertUnorderedList")) || Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("InsertOrderedList"));
			if (on)
			{
				on = false;
				
				// check that we are in a nested list
				var parentList = field.getEditor().dom.getParent(field.getEditor().selection.getNode(), function(node) { return /^li$/i.test(node.tagName) } );
				if (parentList != null)
				{
					var prev = Ext.get(parentList).prev("li");
					on = prev != null;
				}
			}
		}
		
		controller.setDisabled(off || !on);
	}
});

/**
 * This singleton class is used to outdent text
 * @private
 */
Ext.define('Ametys.plugins.cms.editor.basicstyles.Oudent', {
	singleton: true,
	
	/**
	 * Outdent text
	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
	 */
	apply: function (controller)
	{
		var field = controller.getCurrentField();

		field.getEditor().execCommand("Outdent");
		field.getEditor().focus();
	},
	
	/**
	 * Enable/disable controller according the current selection
	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The current selected node. Can be null.
	 */
	selectionListener: function (controller, field, node)
	{
		var off = field == null || node == null || field.getEditor().dom.hasClass(node, "mceNonEditable");
		var on = false;
		
		if (!off && field.getEditor() != null && field.getEditor().dom != null)
		{
			// check that we are in a list (ordered or not)
			on = Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("InsertUnorderedList")) || Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("InsertOrderedList"));
			if (on)
			{
				// check that we are in a nested list
				var parentList = field.getEditor().dom.getParent(field.getEditor().selection.getNode(), function(node) { return /^ul|ol$/i.test(node.tagName) } );
                if (parentList)
                {
				    on = field.getEditor().dom.getParent(parentList.parentNode, function(node) { return /^ul|ol$/i.test(node.tagName) } ) != null;
                }
			}
		}
		
		controller.setDisabled(off || !on);
	}
});
	
/**
 * This class is used to "supscript" the text
 * @private
 */
Ext.define('Ametys.plugins.cms.editor.basicstyles.Sup', {
	singleton: true,
	
	/**
	 * Apply or remove "super script" style
	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
	 */
	apply: function (controller)
	{
		var field = controller.getCurrentField();

		field.getEditor().execCommand("SuperScript");
		field.getEditor().focus();
	},
	
	/**
	 * Enable/disable and toggle/untoggle controller according the current selection
	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The current selected node. Can be null.
	 */
	selectionListener: function (controller, field, node)
	{
		var off = field == null || node == null || field.getEditor().dom.hasClass(node, "mceNonEditable");
		var state = !off && Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("SuperScript"));
		
		controller.toggle(state);
		controller.setDisabled(off);
	}
});

/**
 * This class is used to "subscript" the text
 * @private
 */
Ext.define('Ametys.plugins.cms.editor.basicstyles.Sub', {
	singleton: true,
	
	/**
	 * Apply or remove "sub script" style
	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
	 */
	apply: function (controller)
	{
		var field = controller.getCurrentField();

		field.getEditor().execCommand("SubScript");
		field.getEditor().focus();
	},
	
	/**
	 * Enable/disable and toggle/untoggle controller according the current selection
	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The current selected node. Can be null.
	 */
	selectionListener: function (controller, field, node)
	{
		var off = field == null || node == null || field.getEditor().dom.hasClass(node, "mceNonEditable");
		var state = !off && Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("SubScript"));
		
		controller.toggle(state);
		controller.setDisabled(off);
	}
});

/**
 * This class is used to align text to the left
 * @private
 */
Ext.define('Ametys.plugins.cms.editor.basicstyles.AlignLeft', {
	singleton: true,
	
	/**
	 * Align text to the left
	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
	 */
	apply: function (controller)
	{
		var field = controller.getCurrentField();

		if (Ametys.plugins.cms.editor.BasicStyles.isManuallyAlignableElement(field))
		{
			Ametys.plugins.cms.editor.BasicStyles.setManuallyAlignement(field, "left");
		}
		else
		{
			var carretLocation = field.getEditor().selection.getBookmark();
			Ametys.plugins.cms.editor.BasicStyles.changeSelectionToAlignableElement(field);
			field.getEditor().execCommand('JustifyLeft');
			field.getEditor().selection.moveToBookmark(carretLocation);
		}

		field.getEditor().focus();
	},
	
	/**
	 * Enable/disable and toggle/untoggle controller according the current selection
	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The current selected node. Can be null.
	 */
	selectionListener: function (controller, field, node)
	{
		var off = field == null || node == null;
		var state = !off && (
				Ametys.plugins.cms.editor.BasicStyles.isManuallyAlignableElement(field) ? 
						/^left$/.test(Ametys.plugins.cms.editor.BasicStyles.getManuallyAlignement(field)) 
						: Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("JustifyLeft"))
		);
		
		controller.toggle(state);
		controller.setDisabled(off);
	}
});

/**
 * This class is used to center text
 * @private
 */
Ext.define('Ametys.plugins.cms.editor.basicstyles.AlignCenter', {
	singleton: true,
	
	/**
	 * Center the text
	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
	 */
	apply: function (controller)
	{
		var field = controller.getCurrentField();

		if (Ametys.plugins.cms.editor.BasicStyles.isManuallyAlignableElement(field))
		{
			Ametys.plugins.cms.editor.BasicStyles.setManuallyAlignement(field, "center");
		}
		else
		{
			var carretLocation = field.getEditor().selection.getBookmark();
			Ametys.plugins.cms.editor.BasicStyles.changeSelectionToAlignableElement(field);
			field.getEditor().execCommand('JustifyCenter');
			field.getEditor().selection.moveToBookmark(carretLocation);
		}

		field.getEditor().focus();
	},
	
	/**
	 * Enable/disable and toggle/untoggle controller according the current selection
	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The current selected node. Can be null.
	 */
	selectionListener: function (controller, field, node)
	{
		var off = field == null || node == null;
		var state = !off && (
				Ametys.plugins.cms.editor.BasicStyles.isManuallyAlignableElement(field) ? 
						/^center$/.test(Ametys.plugins.cms.editor.BasicStyles.getManuallyAlignement(field)) 
						: Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("JustifyCenter"))
		);
		
		controller.toggle(state);
		controller.setDisabled(off);
	}
});

/**
 * This class is used to align text to the right
 * @private
 */
Ext.define('Ametys.plugins.cms.editor.basicstyles.AlignRight', {
	singleton: true,
	
	/**
	 * Align text to the right
	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
	 */
	apply: function (controller)
	{
		var field = controller.getCurrentField();

		if (Ametys.plugins.cms.editor.BasicStyles.isManuallyAlignableElement(field))
		{
			Ametys.plugins.cms.editor.BasicStyles.setManuallyAlignement(field, "right");
		}
		else
		{
			var carretLocation = field.getEditor().selection.getBookmark();
			Ametys.plugins.cms.editor.BasicStyles.changeSelectionToAlignableElement(field);
			field.getEditor().execCommand('JustifyRight');
			field.getEditor().selection.moveToBookmark(carretLocation);
		}

		field.getEditor().focus();
	},
	
	/**
	 * Enable/disable and toggle/untoggle controller according the current selection
	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The current selected node. Can be null.
	 */
	selectionListener: function (controller, field, node)
	{
		var off = field == null || node == null;
		var state = !off && (
				Ametys.plugins.cms.editor.BasicStyles.isManuallyAlignableElement(field) ? 
						/^right/.test(Ametys.plugins.cms.editor.BasicStyles.getManuallyAlignement(field)) 
						: Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("JustifyRight"))
		);
		
		controller.toggle(state);
		controller.setDisabled(off);
	}
});

/**
 * This class is used to justify text
 * @private
 */
Ext.define('Ametys.plugins.cms.editor.basicstyles.AlignJustify', {
	singleton: true,
	
	/**
	 * Justify the text
	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
	 */
	apply: function (controller)
	{
		var field = controller.getCurrentField();

		if (Ametys.plugins.cms.editor.BasicStyles.isManuallyAlignableElement(field))
		{
			Ametys.plugins.cms.editor.BasicStyles.setManuallyAlignement(field, "justify");
		}
		else
		{
			var carretLocation = field.getEditor().selection.getBookmark();
			Ametys.plugins.cms.editor.BasicStyles.changeSelectionToAlignableElement(field);
			field.getEditor().execCommand('JustifyFull');
			field.getEditor().selection.moveToBookmark(carretLocation);
		}

		field.getEditor().focus();
	},
	
	/**
	 * Enable/disable and toggle/untoggle controller according the current selection
	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The current selected node. Can be null.
	 */
	selectionListener: function (controller, field, node)
	{
		var off = field == null || node == null;
		var state = !off && (
				Ametys.plugins.cms.editor.BasicStyles.isManuallyAlignableElement(field) ? 
						/^justify/.test(Ametys.plugins.cms.editor.BasicStyles.getManuallyAlignement(field)) 
						: Ametys.plugins.cms.editor.BasicStyles.testQueryCommandState(field.getEditor().queryCommandState("JustifyFull"))
		);
		
		controller.toggle(state);
		controller.setDisabled(off);
	}
});

/**
 * This class is used to justify text
 * @private
 */
Ext.define('Ametys.plugins.cms.editor.basicstyles.List', {
	singleton: true,
	
	/**
	 * Insert an ordered or unordered list to the cursor position. The list will be removed if already existing and if there is no "css-class" configuration.
	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
	 */
	applyStyle: function (controller)
	{
		var field = controller.getCurrentField();
        field.focus(null, 1);

		var tagName = controller.getInitialConfig('tagname');
		var node = field.getEditor().dom.getParent(field.getEditor().selection.getNode(), tagName);

		if (node == null || !controller.getInitialConfig('css-class')) // when there is no "css-class" we want to remove
		{
			field.getEditor().execCommand(/ul/i.test(tagName) ? 'InsertUnorderedList' : 'InsertOrderedList');
			node = field.getEditor().dom.getParent(field.getEditor().selection.getNode(), tagName);
			
			if (node == null)
			{
				// may happen when the selection is around the text
				// CMS-2483 Problem when creating square list on pasted text
				field.getEditor().selection.collapse();
				var isThatNode = field.getEditor().selection.getNode().previousSibling;
				node = isThatNode && /^ul|ol$/i.test(isThatNode.tagName) ? isThatNode : null;
			}
		}
		
		if (node != null)
		{
            var cls = controller.getInitialConfig('css-class') || Ametys.form.widget.RichText.RichTextConfiguration.getTag(tagName, field.getEditor().getParam('category')).attributes['class'].defaultValue;
            
			node.setAttribute("class", cls);
		}
        
	    field.getEditor().execCommand('mceAddUndoLevel');
	},
	
	/**
	 * Remove the ordered or unordered list
	 */
	remove: function (controller)
	{
		var field = controller.getCurrentField();

		if (field.getEditor() != null && field.getEditor().dom != null)
		{
			var n = field.getEditor().dom.getParent(field.getEditor().selection.getNode(), function(node) { return /^ul|ol$/i.test(node.tagName); });
			if (n != null)
			{
				field.getEditor().execCommand(/ul/i.test(n.tagName) ? 'InsertUnorderedList' : 'InsertOrderedList');
			}
			field.getEditor().focus();
		}
	},
	
	/**
	 * Enable/disable and toggle/untoggle controller according the current selection
	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The current selected node. Can be null.
	 */
	selectionListener: function (controller, field, node)
	{
		if (field != null && node != null && field.getEditor() != null && field.getEditor().dom != null)
		{
			controller.enable();

			var specificNode = field.getEditor().dom.getParent(node, controller.getInitialConfig('tagname'));
			if (specificNode != null)
			{
				controller.toggle(!controller.getInitialConfig('css-class') || specificNode.className.split(" ").indexOf(controller.getInitialConfig('css-class')) >= 0);
				return;
			}
		}
		else
		{
			controller.disable();
		}
		controller.toggle(false);
	},
	
	/**
	 * Enable/disable and toggle/untoggle controller according the empty selection
	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The current selected node. Can be null.
	 */
	noSelectionListener: function (controller, field, node)
	{
		if (field == null || node == null || field.getEditor() == null || field.getEditor().dom == null)
		{
			controller.disable();
			controller.toggle(false);
		}
		else
		{
			var li = field.getEditor().dom.getParent(field.getEditor().selection.getNode(), function(node) { return /^ul|ol$/i.test(node.tagName); });
			
			controller.enable();
			controller.toggle(li == null || li.tagName.toLowerCase() != controller.getInitialConfig('tagname').toLowerCase());
		}
	}
	
});