/*
 *  Copyright 2015 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 handling the forms of a rich text
 */
Ext.define('Ametys.plugins.forms.content.Forms', {
	
	singleton: true,
	
	/**
	 * @private
	 * @property {HTMLElement} _currentNode the node that is currently selected
	 */
	
	/**
	 * @private
	 * @property {HTMLElement} _currentFormNameNode the form node that is currently selected
	 */
	
	/**
     * @private
     * @property {String} _currentWorkflowValue the current value of the workflow
	 */
	
	/**
     * @private
     * @property {String} _currentWorkflowNode the current node for the workflow
	 */
	
	/**
	 * @private
	 * @property {HTMLElement} _currentFormProcessingEmailsNode the form node processing emails that is currently selected
	 */
	
	/**
	 * @private
	 * @property {HTMLElement} _currentFormOINWNode the form node for the open in new window feature
	 */
	 
	/**
	 * @private
	 * @property {HTMLElement} _currentURLNode the current node for the external form's url
	 */ 
	
	/**
	 * @private
	 * @property {HTMLElement} _currentFormReceiptNode the current node for the email processing
	 */
	 
	/**
	 * @private
	 * @property {HTMLElement}	_currentFormRedirectNode the current node for the page redirection
	 */
	
	/**
	 * @private
	 * @property {HTMLElement}	_currentFormConvertToCMSNode the current node for conversion from cms to external
	 */
	
	/**
	 * @private
	 * @property {HTMLElement}	_currentFormConvertToExternalNode the current node for conversion from external to cms  
	 */
	
	/**
	 * @private
	 * @property {HTMLElement}	_currentRemovableForm the removable form currently selected  
	 */

    /**
     * @private
     * @property {HTMLElement} _currentFormLimitNode the current node for the entry limit processing
     */
	
	/**
	 * Enable/disable the button according to the selection
	 * @param {Ametys.cms.editor.EditorButtonController} controller The controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	insertListener: function(controller, field, node)
	{
		var form = (field != null && node != null) ? this._getForm(field.getEditor(), node) : null;
		controller.setDisabled(field == null || node == null || form != null);
	},
	
	/**
	 * Insert a CMS form
	 */
	insertCMS: function()
	{
        var layoutCSSClassName = Ametys.plugins.forms.content.Layout._handledLayoutClass[Ext.Object.getKeys(Ametys.plugins.forms.content.Layout._layoutActions).indexOf(Ametys.form.widget.RichText.RichTextConfiguration.getTag("div", tinyMCE.activeEditor.getParam('category')).getAttribute("layout").defaultValue)];
		this._insertForm(' class="form cmsForm ' + layoutCSSClassName + '" type="cms" _mce_ribbon_select="1" form_name="' + this._findName() + '" ');
	},
	
	/**
	 * Insert an external form
	 */
	insertExternal: function()
	{
        var layoutCSSClassName = Ametys.plugins.forms.content.Layout._handledLayoutClass[Ext.Object.getKeys(Ametys.plugins.forms.content.Layout._layoutActions).indexOf(Ametys.form.widget.RichText.RichTextConfiguration.getTag("div", tinyMCE.activeEditor.getParam('category')).getAttribute("layout").defaultValue)];
		this._insertForm(' class="form ' + layoutCSSClassName + '" target="_blank" ');
	},
	
	/**
	 * @private
	 * Insert a form with the given attributes
	 * @param {String} attrs the attributes of the form
	 */
	_insertForm: function(attrs)
	{
		// Generate 15-digit random number
		var id = 'form_' + Math.round((Math.random() * 0.9 + 0.1) * Math.pow(10, 15));
		
		var html = '<div form="form"';
		html += ' id="' + id + '"';
		html += ' layout="' + Ametys.form.widget.RichText.RichTextConfiguration.getTag("div", tinyMCE.activeEditor.getParam('category')).getAttribute("layout").defaultValue + '" ';
		html += attrs;
		html += '>';
		html += "</div>";
		
		// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
		tinyMCE.activeEditor.focus();
		tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
		tinyMCE.insertHTMLAtRoot(html);
		
		this._currentNode = tinyMCE.activeEditor.dom.doc.getElementById(id);
		tinyMCE.activeEditor.execCommand('mceSelectNode', false, this._currentNode);

		
		var layout = Ametys.plugins.forms.content.Layout._getCurrentLayout(this._currentNode);
		
		var layoutFunctionName = Ametys.plugins.forms.content.Layout._layoutActions[layout];
		eval(layoutFunctionName + "();");
		
		tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
		
		var element = this._currentNode;
		while (element.childNodes && element.childNodes.length > 0)
		{
			element = element.childNodes[0];
		}
		
		tinyMCE.activeEditor.execCommand('mceSelectNode', false, element);
	},
	
	/**
	 * Listener function invoked when the form name text input changes
	 * @param {Ext.form.Field} input the input field
	 */
	setFormNameOnBlur: function (input)
	{
		if (this._setFormName(input.getValue()) === false)
		{
			input.setValue(this._getFormName());
		}
	},
	
	/**
	 * Listener function invoked when a special key was stroke in the form name input field
	 * @param {Ext.form.Field} input the input field
	 * @param {Ext.event.Event} event the specialkey event
	 */
	setFormNameOnSpecialKey: function (input, event)
	{
		if (event.getKey() == event.ENTER) 
		{
			event.preventDefault();
			event.stopPropagation();
			if (this._setFormName(input.getValue()) === false)
			{
				input.setValue(this._getFormName());
			}
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.focus();
		}
		else if (event.getKey() == event.ESC) 
		{
			event.preventDefault();
			event.stopPropagation();
			
			input.setValue(this._getFormName());
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.focus();
		}	
	},
	
	/**
	 * Listener for the form name input field
	 * @param {Ametys.cms.editor.EditorFieldController} controller The controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	formNameListener: function (controller, field, node)
	{
		var form = (field != null && node != null && field.getEditor() != null) ? this._getForm(field.getEditor(), field.getEditor().selection.getNode()) : null;
		if (form != null && form.getAttribute("type") != "cms")
		{
			form = null;
		}
		
		this._currentFormNameNode =  form;
		if (!form)
		{
			controller.disable();
			controller.setValue("");
		}
		else
		{
			controller.enable();
			controller.setValue(this._getFormName());
		}
	},
	
	/**
	 * @private
	 * Get the name of the form
	 * @return the name of the form
	 */
	_getFormName: function ()
	{
		var formName = this._currentFormNameNode.getAttribute("form_name");
		return (this._currentFormNameNode == null || formName == null) ? "" : decodeURIComponent(formName);
	},
	
	/**
	 * @private
	 * Set the name of the form with the provided value
	 * @param value the name to set to the form
	 * @return false if an error occurred
	 */
	_setFormName: function (value)
	{
		if (this._getFormName() != value)
		{
			if (value == '' && this._getFormProcessingEmails() == '')
			{
				Ametys.Msg.show({
					title: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_CMS_FORMNAME_EMPTYERROR_TITLE}}",
					msg: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_CMS_FORMNAME_EMPTYERROR_DESCRIPTION}} <br />" 
						   + "{{i18n PLUGINS_FORMS_FORMS_EDITOR_CMS_FORMNAME_EMPTYERROR_DETAILS}}",
				    buttons: Ext.Msg.OK,
					icon: Ext.MessageBox.ERROR
				});
	
				return false;
			}
			
			var form = this._currentFormNameNode;
			if (form != null)
			{
				if ((value != '') && (!this._isFreeCMSFormName(form.id, value)))
				{
					Ametys.Msg.show({
						title: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_CMS_FORMNAME_ERROR_TITLE}}",
						msg: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_CMS_FORMNAME_ERROR_DESCRIPTION}} <br />" 
							   + "{{i18n PLUGINS_FORMS_FORMS_EDITOR_CMS_FORMNAME_ERROR_DETAILS}}",
					    buttons: Ext.Msg.OK,
						icon: Ext.MessageBox.ERROR
					});
					return false;
				}
				
				form.setAttribute("form_name", encodeURIComponent(value));
				// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
				tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
			}
		}
	},
	
	/**
	 * Listener when the selection changes in the editor
	 * @param {Ametys.cms.editor.EditorFieldController} controller the button's controller
	 * @param {Ext.form.Field} field the target field
	 * @param {HTMLElement} node the node 
	 */
	workflowSelectionListener: function(controller, field, node)
	{
		var off = field == null || node == null;
		if (off)
		{
			controller.disable();
			return;
		}
		
		var formNode = Ametys.plugins.forms.content.Forms._getForm(field.getEditor(), node);
		if (formNode != null)
		{
			this._currentWorkflowNode = formNode;
			controller.enable();
			
			var formWorkflow = formNode.getAttribute("form_workflow");
			var value = formWorkflow != null ? formWorkflow : controller.getStore().getRange()[0].data.value;
			formNode.setAttribute("form_workflow", value);
			
			controller.setValue(value);
			
			this._currentWorkflowValue = value;
		}
	},
	
	/**
	 * Listener when the selection changes in the editor
	 * @param {Ametys.cms.editor.EditorFieldController} controller the button's controller
	 * @param {Ext.data.Record} record the selected record
	 */
	applyWorkflow: function (controller, record)
	{
		var newValue = record.data.value;
		var oldValue = this._currentWorkflowValue;
		
		// Never display the warning message when there initially was no workflow on the form, 
		// nor when going back to the form's initial workflow 
		var initialValue = this._currentWorkflowNode.getAttribute("initial_form_workflow");
		
		this._currentWorkflowNode.setAttribute("form_workflow", newValue);
		this._currentWorkflowValue = newValue;
		
		if (oldValue != newValue && initialValue != "" && initialValue != null && newValue != initialValue)
		{
			Ametys.Msg.confirm(
					"{{i18n PLUGINS_FORMS_FORMS_EDITOR_WORKFLOW_CHANGE_DIALOG_TITLE}}", 
					newValue == "" ? "{{i18n PLUGINS_FORMS_FORMS_EDITOR_WORKFLOW_DELETE_DIALOG_TEXT}}" 
								   : "{{i18n PLUGINS_FORMS_FORMS_EDITOR_WORKFLOW_CHANGE_DIALOG_TEXT}}",
					function (answer) {
						if (answer != 'yes')
						{
							controller.setValue(oldValue);
							this._currentWorkflowValue = oldValue;
							this._currentWorkflowNode.setAttribute("form_workflow", oldValue);
						}
					}, 
					this
			);
		}
		
		tinyMCE.activeEditor.focus();
	},
	
	/**
	 * Listener for the open in new window checkbox field
	 * @param {Ametys.cms.editor.EditorFieldController} controller The controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	openInNewWindowListener: function(controller, field, node)
	{
		var form = (field != null && node != null) ? this._getForm(field.getEditor(), node) : null;
		this._currentFormOINWNode = form;
		if (!form)
		{
			controller.disable();
		}
		else
		{
			controller.enable();
			controller.setValue(this._getOpenInNewWindow());
		}
	},
	
	/**
	 * Function invoked when the open in new window field changes
	 * @param {Ext.form.Field} input the checkbox field
	 */
	setOpenInNewWindow: function(input)
	{
		var form = this._currentFormOINWNode;
		var newValue = input.getValue();
		if (form && newValue != this._getOpenInNewWindow())
		{
			form.setAttribute("target", input.getValue() ? "_blank" : "");
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
		}
		tinyMCE.activeEditor.focus();
	},
	
	/**
	 * @private
	 * Is the open in new window option activated ?
	 */
	_getOpenInNewWindow: function()
	{
		var form = this._currentFormOINWNode;
		if (form)
		{
			return form.getAttribute("target") != null && form.getAttribute("target") == "_blank"; 
		}
	},
	
	/**
	 * Function invoked when a special key is stroke in the form's url field
	 * @param {Ext.form.Field} input the field
	 * @param {Ext.event.Event} event the event
	 */
	setURLOnSpecialKey: function (input, event)
	{
		if (event.getKey() == event.ENTER) 
		{
			event.preventDefault();
			event.stopPropagation();
			
			if (this._setURL(input.getValue()) === false)
			{
				input.setValue(this._getURL());
			}
		}
		else if (event.getKey() == event.ESC) 
		{
			event.preventDefault();
			event.stopPropagation();
			
			input.setValue(this._getURL());
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.focus();
		}	
	},
	
	/**
	 * Function invoked when the url input field changes
	 * @param {Ext.form.Field} input the url input field
	 */
	setURLOnBlur: function (input)
	{
		if (this._setURL(input.getValue()) === false)
		{
			input.setValue(this._getURL());
		}
	},
	
	/**
	 * Listener controlling the state of the url field
	 * @param {Ametys.cms.editor.EditorFieldController} controller The controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	urlListener: function (controller, field, node)
	{
		var form = (field != null && node != null && field.getEditor() != null) ? this._getForm(field.getEditor(), field.getEditor().selection.getNode()) : null;
		if (form != null && form.getAttribute("type") == "cms")
		{
			form = null;
		}
		
		this._currentURLNode = form;
		if (!form)
		{
			controller.setValue("");
			controller.disable();
		}
		else
		{
			controller.setValue(this._getURL());
			controller.enable();
		}
	},
	
	/**
	 * @private
	 * Get the url of the form
	 */
	_getURL: function()
	{
		if (this._currentURLNode == null || this._currentURLNode.getAttribute("form_action") == null)
		{
			return "";
		}
		else
		{
			return decodeURIComponent(this._currentURLNode.getAttribute("form_action"));
		}
	},
	
	/**
	 * @private
	 * Set the url of the form
	 * @param {String} value the value to set on the url field
	 */
	_setURL: function (value)
	{
		if (this._getURL() != value)
		{
			if (/^https?:\/\/.+$/.test(value) == false)
			{
				Ametys.Msg.show({
					title: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_EXTERNAL_URL_ERROR_TITLE}}",
					msg: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_EXTERNAL_URL_ERROR_DESCRIPTION}} <br />" 
						   + "{{i18n PLUGINS_FORMS_FORMS_EDITOR_EXTERNAL_URL_ERROR_DETAILS}}<br/>^https?://.+$",
				    buttons: Ext.Msg.OK,
					icon: Ext.MessageBox.ERROR,
					fn: function () { tinyMCE.activeEditor.focus(); }
				});
				return false;
			}
			
			var form = this._currentURLNode;
			if (form != null)
			{
				form.setAttribute("form_action", encodeURIComponent(value));
				// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
				tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
			}
		}

		tinyMCE.activeEditor.focus();
	},
	
	/**
	 * Function invoked when the email processing field has changed
	 * @param {Ext.form.Field} input the email processing field
	 */
	setFormProcessingEmailsOnBlur: function (input)
	{
		if (this._setFormProcessingEmails(input.getValue()) === false)
		{
			input.setValue(this._getFormProcessingEmails());
		}
	},
	
	/**
	 * @private
	 * Set the form processing emails value
	 * @param value the value 
	 * @return false if an error occurred
	 */
	_setFormProcessingEmails: function (value)
	{
		if (this._getFormProcessingEmails() != value)
		{
			if (value == '' && this._getFormName() == '')
			{
				Ametys.Msg.show({
					title: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_CMS_EMAILPROCESSING_EMPTYERROR_TITLE}}",
					msg: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_CMS_EMAILPROCESSING_EMPTYERROR_DESCRIPTION}} <br />" 
						   + "{{i18n PLUGINS_FORMS_FORMS_EDITOR_CMS_EMAILPROCESSING_EMPTYERROR_DETAILS}}",
				    buttons: Ext.Msg.OK,
					icon: Ext.MessageBox.ERROR
				});
				return false;
			}
	
			var form = this._currentFormProcessingEmailsNode;
			if (form != null)
			{
				if (value == '' || /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,})([ ,;\r\n]*([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,}))*$/.test(value))
				{
    				form.setAttribute("form_processing_emails", encodeURIComponent(value));
    				// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
    				tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
                }
                else
                {
					Ametys.Msg.show({
						title: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_CMS_EMAILPROCESSING_ERROR_TITLE}}",
						msg: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_CMS_EMAILPROCESSING_ERROR_DESCRIPTION}} <br />" 
							   + "{{i18n PLUGINS_FORMS_FORMS_EDITOR_CMS_EMAILPROCESSING_ERROR_DETAILS}}",
					    buttons: Ext.Msg.OK,
						icon: Ext.MessageBox.ERROR
					});
					return false;
				}
			}
		}
	},
	
	/**
	 * @private
	 * Get the form processing emails
	 */
	_getFormProcessingEmails: function ()
	{
		if (this._currentFormProcessingEmailsNode == null || this._currentFormProcessingEmailsNode.getAttribute("form_processing_emails") == null)
		{
			return "";
		}
		else
		{
			return decodeURIComponent(this._currentFormProcessingEmailsNode.getAttribute("form_processing_emails"));
		}
	},
	
	/**
	 * Listener controlling the state of the form processing field
	 * @param {Ametys.cms.editor.EditorButtonController} controller The controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	formProcessingEmailsListener: function (controller, field, node)
	{
		var form = (field != null && node != null && field.getEditor() != null) ? this._getForm(field.getEditor(), field.getEditor().selection.getNode()) : null;
		if (form != null && form.getAttribute("type") != "cms")
		{
			form = null;
		}
		
		this._currentFormProcessingEmailsNode =  form;
		if (!form)
		{
			controller.setValue('');
			controller.disable();
		}
		else
		{
			controller.setValue(this._getFormProcessingEmails());
			controller.enable();
		}
	},
	
	/**
	 * Send to the visitor an email when its request is received
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button controller
	 */
	receiptAction: function(controller)
	{
		var form = this._currentFormReceiptNode;
		
		var emailIds = this._receiptGetEmailFields();
		var atLeastOne = emailIds.length > 0
		
		// test if there is at least a field
		if (!atLeastOne)
		{
			// no one
			Ametys.Msg.show({
				title: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_MAILRECEIPT_NO_TITLE}}",
				msg: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_MAILRECEIPT_NO_DESCRIPTION}}",
			    buttons: Ext.Msg.OK,
				icon: Ext.MessageBox.ERROR,
				fn: function () { tinyMCE.activeEditor.focus(); }
			});

			return;
		}
		
		// display receipt dialog box
		var from = form.getAttribute("form_receipt_from");
		var to = form.getAttribute("form_receipt_to");
		var subject = form.getAttribute("form_receipt_subject");
		var body = form.getAttribute("form_receipt_body");
		
		Ametys.plugins.forms.content.ReceiptBox.create(from, 
											   to, 
											   subject != null ? decodeURIComponent(subject) : null, 
											   body != null ? decodeURIComponent(body) : null, 
											   emailIds,  
											   Ext.bind(this._setFormReceiptAttributes, this, [controller], 5));
	},
    
	/**
	 * @private
	 * Set the form attributes for the receipt
	 * @param {String} from the sender
	 * @param {String} to the receiver
	 * @param {String} subject the subject of the email
	 * @param {String} body the body of the email
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button's controller
	 * @return true
	 */
	_setFormReceiptAttributes: function(from, to, subject, body, controller)
	{
		var form = this._currentFormReceiptNode;
		if (from != null)
		{
			form.setAttribute("form_receipt_from", from);
			form.setAttribute("form_receipt_to", to);
			form.setAttribute("form_receipt_subject", subject != null ? encodeURIComponent(subject) : null);
			form.setAttribute("form_receipt_body", body != null ? encodeURIComponent(body) : null);
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
			
			
			controller.toggle(true);
			controller.enable();
		}
		else
		{
			form.removeAttribute("form_receipt_from");
			form.removeAttribute("form_receipt_to");
			form.removeAttribute("form_receipt_subject");
			form.removeAttribute("form_receipt_body");
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
			
			controller.toggle(false);
			controller.enable();
		}		
		tinyMCE.activeEditor.focus();
		
		return true;
	},
	
	/**
	 * Get the email ids form the form
	 */
	_receiptGetEmailFields: function()
	{
		var emailIds = [];
		function trimee(s)
		{
			if (s.substring(s.length - 1) == ":")
			{
				return s.substring(0, s.length - 1).trim();
			}
			else
			{
				return s;
			}
		}
		
		var currentForm = this._currentFormReceiptNode;
		var imgs = currentForm.getElementsByTagName("img");
		for (var i = 0; i < imgs.length; i++)
		{
			if (imgs[i].getAttribute("marker") == "marker" 
				&& imgs[i].getAttribute("form") == "form"
				&& imgs[i].getAttribute("input_text") == "input_text"
				&& imgs[i].getAttribute("form_regexptype") == "email")
			{
				// found one
				var label = Ametys.plugins.forms.content.Components._getLabel(imgs[i]);
				
				emailIds.push([imgs[i].id, label != null ? trimee(label.innerHTML) : imgs[i].id]);
			}
		}
		
		return emailIds;
	},
	
	/**
	 * Listener controlling the state of the receipt button
	 * @param {Ametys.cms.editor.EditorButtonController} controller The controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	receiptListener: function(controller, field, node)
	{
		var form = (field != null && node != null && field.getEditor() != null) ? this._getForm(field.getEditor(), field.getEditor().selection.getNode()) : null;
		if (form != null && form.getAttribute("type") != "cms")
		{
			form = null;
		}
		
		this._currentFormReceiptNode =  form;
		if (!form)
		{
			controller.toggle(false);
			controller.disable();
		}
		else
		{
			var to = this._currentFormReceiptNode.getAttribute("form_receipt_to") || "";
			if (to != "")
			{
				// repairing
				var input = field.getEditor().dom.doc.getElementById(to);
				if (input == null || input.getAttribute("input_text") != "input_text" || input.getAttribute("form_regexptype") != "email")
				{
					input.setAttribute("form_receipt_to", null);
					to = "";
				}
			}
			
			controller.toggle(to != '');
			controller.enable();
		}
	},

    /**
     * Limit the entries of a form
     * @param {Ametys.cms.editor.EditorButtonController} controller the button controller
     */
    limitAction: function(controller)
    {
        var form = this._currentFormLimitNode;
        
        // display receipt dialog box
        var limit = form.getAttribute("form_limit");
        var places = form.getAttribute("form_places");
        var noPlaces = form.getAttribute("form_no_places");
        
        Ametys.plugins.forms.content.LimitBox.create(limit, places, noPlaces,  
                                               Ext.bind(this._setFormLimitAttributes, this, [controller, controller.getCurrentField()], 4));
    },
    
    /**
     * @private
     * Set the form attributes for the limit of entries
     * @param {Number} limit the limit of entries
     * @param {String} places the message to display if there are remaining places.
     * @param {String} noPlaces the message to display if there are no remaining places left.
     * @param {Ametys.cms.editor.EditorButtonController} controller the button's controller
     * @return true
     */
    _setFormLimitAttributes: function(limit, places, noPlaces, controller, field)
    {
        var form = this._currentFormLimitNode;
        if (limit != null)
        {
            form.setAttribute("form_limit", limit);
            form.setAttribute("form_places", places);
            form.setAttribute("form_no_places", noPlaces);

            field.getEditor().execCommand('mceAddUndoLevel');
            
            
            controller.toggle(true);
            controller.enable();
        }
        else
        {
            form.removeAttribute("form_limit");
            form.removeAttribute("form_places");
            form.removeAttribute("form_no_places");
            field.getEditor().execCommand('mceAddUndoLevel');
            
            controller.toggle(false);
            controller.enable();
        }       
        tinyMCE.activeEditor.focus();
        
        return true;
    },

    /**
     * Listener controlling the state of the limit button
     * @param {Ametys.cms.editor.EditorButtonController} controller The controller
     * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
     * @param {HTMLElement} node The currently selected node. Can be null.
     */
    limitListener: function(controller, field, node)
    {
        var form = (field != null && node != null && field.getEditor() != null) ? this._getForm(field.getEditor(), field.getEditor().selection.getNode()) : null;
        if (form != null && form.getAttribute("type") != "cms")
        {
            form = null;
        }
        
        this._currentFormLimitNode =  form;
        if (!form)
        {
            controller.toggle(false);
            controller.disable();
        }
        else
        {
            var places = this._currentFormReceiptNode.getAttribute("form_places") || "";
            controller.toggle(places != '');
            controller.enable();
        }
    },

	/**
	 * Choose a page to redirect the user to when the form is submitted
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button's controller 
	 */
	redirectionAction: function(controller)
	{
		var value = this._currentFormRedirectNode.getAttribute("form_redirect");
		
		Ametys.web.helper.ChoosePage.open({
        	iconCls: 'ametysicon-website38 decorator-ametysicon-check34',
            title: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_REDIRECTION_DIALOG_TITLE}}",
            helpMessage: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_REDIRECTION_DIALOG_DESCRIPTION}}",
            values: Ext.Array.from(value),
            defaultSitemapName: Ametys.cms.language.LanguageDAO.getCurrentLanguage(), 
            allowCreation: true,
            allowDeletion: true,
            callback: Ext.bind(this._redirectionAction, this, [controller], 1)
        });
	},
	
	/**
	 * @private
	 * Set the redirection attribute on the form
	 * @param {String} pageId the id of the selected page. Can be null if no page is selected
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button's controller
	 */
	_redirectionAction: function(pageId, controller)
	{
        if (pageId !== undefined)
        {
    		if (pageId != null)
    		{
    			this._currentFormRedirectNode.setAttribute("form_redirect", pageId);
    			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
    			tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
    			controller.toggle(true);
    		}
    		else
    		{
    			this._currentFormRedirectNode.removeAttribute("form_redirect");
    			tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
    			controller.toggle(false);
    		}

    		controller.enable();
    		tinyMCE.activeEditor.focus();
        }
	},
	
	/**
	 * Listener controlling the state of the redirection button
	 * @param {Ametys.cms.editor.EditorButtonController} controller The controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	redirectionListener: function(controller, field, node)
	{
		var form = (field != null && node != null && field.getEditor() != null) ? this._getForm(field.getEditor(), field.getEditor().selection.getNode()) : null;
		if (form != null && form.getAttribute("type") != "cms")
		{
			form = null;
		}
		
		this._currentFormRedirectNode =  form;
		if (!form)
		{
			controller.toggle(false);
			controller.disable();
			controller.setDescription(controller.getInitialConfig("description"));
		}
		else
		{
			var to = this._currentFormRedirectNode.getAttribute("form_redirect") || "";
			controller.toggle(to != '');
			controller.enable();
			
			if (to != '')
			{
				Ametys.web.page.PageDAO.getPage(to, Ext.bind(this._getPageCb, this, [controller], 1));
			}
			else
			{
				var description = controller.getInitialConfig("description");
				description += controller.getInitialConfig("redirection-description-no") || '';
				controller.setDescription(description);
			}
		}	
	},
	
	/**
	 * @private
	 * Callback invoked after getting the redirection page. Update the tooltip of controller.
	 * @param {Ametys.web.page.Page} page the page
	 * @param {Ametys.cms.editor.EditorButtonController} controller The controller
	 */
	_getPageCb: function (page, controller)
	{
		if (page)
		{
			var description = controller.getInitialConfig("description");
			description += Ext.String.format("{{i18n PLUGINS_FORMS_FORMS_EDITOR_REDIRECTION_PAGE_DESCRIPTION}}", page.getTitle());
			
			controller.setDescription(description);
		}
	},
	
	/**
	 * Convert the selected form to an external form 
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button controller
	 */
	convertFormToExternal: function(controller)
	{
	    // FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
		tinyMCE.activeEditor.dom.removeClass(this._currentFormConvertToExternalNode, 'cmsForm');
		this._currentFormConvertToExternalNode.removeAttribute("type");
		this._currentFormConvertToExternalNode.removeAttribute("form_name");
		this._currentFormConvertToExternalNode.removeAttribute("form_processing_emails");
		this._currentFormConvertToExternalNode.removeAttribute("form_receipt_to");
		this._currentFormConvertToExternalNode.removeAttribute("form_receipt_from");
		this._currentFormConvertToExternalNode.removeAttribute("form_receipt_subject");
		this._currentFormConvertToExternalNode.removeAttribute("form_receipt_body");
        this._currentFormConvertToExternalNode.removeAttribute("form_redirect");
        this._currentFormConvertToExternalNode.removeAttribute("form_limit");
        this._currentFormConvertToExternalNode.removeAttribute("form_places");
        this._currentFormConvertToExternalNode.removeAttribute("form_no_places");
		tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
		
		tinyMCE.activeEditor.execCommand('mceSelectNode', false, this._currentFormConvertToExternalNode);

		tinyMCE.activeEditor.focus();
	},
	
	/**
	 * Listener controlling the state of the conversion button from a CMS form to an external form
	 * @param {Ametys.cms.editor.EditorButtonController} controller The controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	convertFormToExternalListener: function(controller, field, node)
	{
		var form = (field != null && node != null && field.getEditor() != null) ? this._getForm(field.getEditor(), field.getEditor().selection.getNode()) : null;
		if (form == null || form.getAttribute("type") == null)
		{
			form = null;
		}
		
		this._currentFormConvertToExternalNode =  form;
		controller.setDisabled(form == null);
	},
	
	/**
	 * Convert the selected form to a cms form 
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button controller
	 */
	convertFormToCMS: function(controller)
	{
	    // FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
		tinyMCE.activeEditor.dom.addClass(this._currentFormConvertToCMSNode, 'cmsForm');
		this._currentFormConvertToCMSNode.setAttribute("type", "cms");
		this._currentFormConvertToCMSNode.setAttribute("form_name", this._findName());
		tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
		
		tinyMCE.activeEditor.execCommand('mceSelectNode', false, this._currentFormConvertToCMSNode);

		tinyMCE.activeEditor.focus();
	},
	
	/**
	 * Listener controlling the state of the conversion button from a CMS form to an external form
	 * @param {Ametys.cms.editor.EditorButtonController} controller The controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	convertFormToCMSListener: function(controller, field, node)
	{
		var form = (field != null && node != null && tinyMCE.activeEditor != null) ? this._getForm(field.getEditor(), field.getEditor().selection.getNode()) : null;
		if (form == null || form.getAttribute("type") == "cms")
		{
			form = null;
		}
		
		this._currentFormConvertToCMSNode =  form;
		controller.setDisabled(form == null);
	},

	/**
	 * Remove form
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button controller
	 */
	removeForm: function(controller)
	{
		Ext.MessageBox.confirm("{{i18n PLUGINS_FORMS_FORMS_EDITOR_REMOVEFORM_CONFIRM_TITLE}}",
								"{{i18n PLUGINS_FORMS_FORMS_EDITOR_REMOVEFORM_CONFIRM_TEXT}}",
								Ext.bind(this._removeForm, this));
	},

	/**
	 * Actual form removal process if the user clicked 'Ok'
	 * @param {String} answer the user's answer
	 */
	_removeForm: function(answer)
	{
		if (answer == 'yes')
		{
		    // FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
			var form = this._currentRemovableForm;
			tinyMCE.activeEditor.execCommand('mceSelectNode', false, form);
			form.parentNode.removeChild(form);
			tinyMCE.activeEditor.execCommand('mceInsertContent', false, '<p><br _moz_dirty=""/></p>');
			tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
		}
		tinyMCE.activeEditor.focus();
	},
	
	/**
	 * Listener controlling the state of the form removal button
	 * @param {Ametys.cms.editor.EditorButtonController} controller The controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	canRemoveForm: function(controller, field, node)
	{
		var form = (field != null && node != null && field.getEditor() != null) ? this._getForm(field.getEditor(), field.getEditor().selection.getNode()) : null;
		
		controller.setDisabled(form == null);
		this._currentRemovableForm = form;
	},
	
	/**
	 * @private
	 * Get the form in the editor above the given dom node
     * @param {Object} editor The active editor
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 * @return the dom node of the form
	 */
	_getForm: function(editor, node)
	{
		while (true)
		{
			var specificNode = editor && editor.dom && editor.dom.getParent(node, 'div');
			if (specificNode == null)
			{
				return null;
			}
			else if (specificNode != null && specificNode.getAttribute('form') == 'form')
			{
				return specificNode;
			}
			node = specificNode.parentNode;
		}
	},
	
	/**
	 * Compute the name of the form, comparing with possible existing form names 
	 * @return the name of the form
	 */
	_findName: function()
	{
		var formName = "{{i18n PLUGINS_FORMS_FORMS_EDITOR_CMS_FORMNAME_DEFAULTVALUE}}";
		var i = 0;
		var found = false;
		do
		{
			i++;
			
			found = this._isFreeCMSFormName(null, formName + " " + i);
		} while (!found);
		return encodeURIComponent(formName + ' ' + i);
	},
	
	/**
	 * @private
	 * Is the name of the cms form available ?
	 * @param {String} id the id of the form
	 * @param {String} name the wished name for the form
	 * @return true if the form name is available, false otherwise
	 */
	_isFreeCMSFormName: function(id, name)
	{
	    // FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
		var forms = tinyMCE.activeEditor.dom.doc.getElementsByTagName("div");
		
		Ext.Array.each(forms, function(form) {
			if (form.getAttribute("form") == "form" && form.getAttribute("type") == "cms" && (id == null || form.id != id) 
				&& encodeURIComponent(name) == form.getAttribute("form_name"))
			{
				return false;
			}
		});
		
		return true;
	}
});