/*
 *  Copyright 2017 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.
 */

/**
 * Singleton to manage the 'edition.richtext' widget for the FormEditionPanel.
 * Allow to add CSS files, supported tags and common listeners.
 */

Ext.define('Ametys.form.widget.RichText.RichTextConfiguration', {
    extend: 'Ext.util.Observable',
    
    singleton: true,
    
    /**
     * @property {Object} _validators A map of cagtegory / list of registered validator. "" is the default category
     * @property {Function[]} _validators.key List of registered validator #addValidator used in #validates for a category
     * @private
     */
    _validators: {},
    
    /**
     * @property {Object} __convertorsOnGetContent A map of category / list of registered convertors. "" is the default category.
     * @property {Function[]} __convertorsOnGetContent.key List of registered convertors #addConvertors used in #convertOnGetContent for a category
     * @private
     */
    _convertorsOnGetContent: {},
    /**
     * @property {Object} __convertorsOnSetContent A map of category / list of registered convertors. "" is the default category
     * @property {Function[]} __convertorsOnSetContent.key List of registered convertors #addConvertors used in #convertOnSetContent for a category
     * @private
     */
    _convertorsOnSetContent: {},
    
    /**
     * @event setcontent
     * Fires when the editor received new content. This allows to convert storing tags to internal tags. Use object.content to get/set the full html. See Ametys.form.field.RichText#event-setcontent for parameters.
     */
    /**
     * @event getcontent
     * Fires when the editor received content. This allows to convert internal tags to storing tags. Use object.content to get/set the full html. See Ametys.form.field.RichText#event-getcontent for parameters.
     */
    /**
     * @event keypress
     * Fires when the editor has a key press. See Ametys.form.field.RichText#event-keypress for parameters.
     */
    /**
     * @event keydown
     * Fires when the editor has a key down. See Ametys.form.field.RichText#event-keydown for parameters.
     */
    /**
     * @event keyup
     * Fires when the editor has a key up. See Ametys.form.field.RichText#event-keydown for parameters.
     */
    /**
     * @event visualaid
     * Fires when the editor pre process the serialization. See Ametys.form.field.RichText#event-visualaid for parameters.
     */
    /**
     * @event preprocess
     * Fires when the editor pre process the serialization. See Ametys.form.field.RichText#event-preprocess for parameters.
     */
    /**
     * @event htmlnodeselected
     * Fires when a HTML node is selected in editor. See Ametys.form.field.RichText#event-htmlnodeselected for parameters.
     */
    
    constructor: function()
    {
        this.callParent(arguments);

        var me = this;
        Ametys.data.ServerComm.callMethod({
            role: "org.ametys.core.ui.widgets.richtext.RichTextConfigurationExtensionPoint",
            methodName: "toJSON",
            parameters: [ Ametys.getAppParameters() ],
            callback: {
                handler: function(json) { RichTextConfigurationBase.setConfigurationFromServer(json, me); }
            },
            errorMessage: true
        });
    },
            
    /**
     * Validates the value among the existing validators for the inline editor
     * @param {String} value The current field value
     * @param {String} [category=""] The category to validates
     * @return {Boolean/String} validator.return
     *
     * - True if the value is valid
     * - An error message if the value is invalid
     */
    validates: function(value, category)
    {
        return RichTextConfigurationBase.validates(value, category);
    },
        
    /**
     * Get all added css files as one string
     * @param {String} [category=""] The category to apply.
     * @return {String} The comma-separated list of added files
     */
    getCSSFiles: function(category)
    {
        return RichTextConfigurationBase.getCSSFiles(category);
    },
    
    /**
     * Get all supported tags as a tinymce string. See valid_element tinymce configuration doc for the exact format.
     * @param {String} [category=""] The category to apply.
     * @return {String} The supported tags.
     */
    getTags: function(category)
    {
        return RichTextConfigurationBase.getTags(category);
    },
    
    /**
     * Get all supported styles as a tinymce conf object. See valid_styles tinymce configuration doc for the exact format.
     * @param {String} [category=""] The category to apply.
     * @return {Object} The supported properties for the style attribute.
     */
    getStylesForTags: function(category)
    {
        return RichTextConfigurationBase.getStylesForTags(category);
    },
    
    /**
     * Get all supported classes as a tinymce conf object. See valid_classes tinymce configuration doc for the exact format.
     * @param {String} [category=""] The category to apply.
     * @return {Object} The supported properties for the style attribute.
     */
    getClassesForTags: function(category)
    {
        return RichTextConfigurationBase.getClassesForTags(category);
    },    
        
    /**
     * Get tag informations. 
     * @param {String} tagName The name of the tag to handle in the configuration
     * @param {String} [category=""] The category of configuration where to handle
     * @return {Object} See #handleTag to know more
     */
    getTag: function(tagName, category)
    {
        return RichTextConfigurationBase.getTag(tagName, category);
    },
        
    /**
     * Get styles informations of a type. 
     * @param {String} type The type of element to style. Such as "paragraph", "table", "link", "image", "ol", "ul"...
     * @param {String} [category=""] The category of configuration where to handle
     * @return {Object[]} The styles available by group. See #handleStyledGroup return value. Can be null or empty.
     */
    getStyledElements: function(type, category)
    {
        return RichTextConfigurationBase.getStyledElements(type, category);
    },
    
    /**
     * Handle validators
     * @param {Object} validators The validators to handle.
     */
    handleValidators: function(validators, category)
    {
        var me = this;
        Ext.Array.forEach(validators, function(validator) {
            var validatorInstance = Ext.create(validator['class'].name, validator['class'].parameters);
            me.addValidator(Ext.bind(validatorInstance.validates, validatorInstance), category);
        });
    },
    
    /**
     * Handle convertors
     * @param {Object} convertors The convertor to handle.
     */
    handleConvertors: function(convertors, category)
    {
        var me = this;
        Ext.Array.forEach(convertors, function(convertor) {
            var convertorInstance = Ext.create(convertor['class'].name, convertor['class'].parameters);
            me.addConvertor(convertorInstance.onGetContent ? Ext.bind(convertorInstance.onGetContent, convertorInstance) : null, convertorInstance.onSetContent ? Ext.bind(convertorInstance.onSetContent, convertorInstance) : null, category);
        });
    },
    
    /**
     * @private
     * Add a custom validation function to be called during inline editor validation ({@link Ext.form.field.Field#getErrors}).
     * @param {Function} validator The new validator to add. This function will have the following signature:
     * @param {Object} validator.value The current field value
     * @param {Boolean/String} validator.return
     * - True if the value is valid
     * - An error message if the value is invalid
     * @param {String} [category=""] The category where to register.
     */
    addValidator: function(validator, category)
    {
        category = category || "";
        
        this._validators[category] = this._validators[category] || []; 
        this._validators[category].push(validator);
    },
    
    /**
     * Validates the value among the existing validators for the inline editor
     * @param {String} value The current field value
     * @param {String} [category=""] The category to validates
     * @return {Boolean/String} validator.return
     *
     * - True if the value is valid
     * - An error message if the value is invalid
     */
    validates: function(value, category)
    {
        this.checkIfInitialized();

        category = category || "";
        
        var returnValues = "";
        
        Ext.each(this._validators[category] || [], function (validator) {
            var returnValue = validator.apply(null, [value]);
            if (returnValue !== true)
            {
                returnValues += returnValue + "\n";
            }
        });
        
        return returnValues.length == 0 ? true : returnValues.substring(0, returnValues.length - 1);
    },
    
    /**
     * @private
     * Add a custom convertor function to be called during inline editor conversion process getValue/setValue.
     * @param {Function} onGetContent The new function to add that will be called on getValue to convert the internal richtext structure to the external value
     * @param {Ametys.form.field.RichText} onGetContent.field The current richtext field
     * @param {tinymce.Editor} onGetContent.editor The current richtext editor
     * @param {Object} onGetContent.object The object of value to be modified
     * @param {Function} onSetContent The new function to add that will be called on setValue to convert the external value to the internal richtext structure 
     * @param {tinymce.Editor} onSetContent.editor The current richtext editor
     * @param {Object} onSetContent.object The object of value to be modified
     * @param {String} [category=""] The category where to register.
     */
    addConvertor: function(onGetContent, onSetContent, category)
    {
        category = category || "";
        
        if (onGetContent)
        {
            this._convertorsOnGetContent[category] = this._convertorsOnGetContent[category] || []; 
            this._convertorsOnGetContent[category].push(onGetContent);
        }

        if (onSetContent)
        {
            this._convertorsOnSetContent[category] = this._convertorsOnSetContent[category] || []; 
            this._convertorsOnSetContent[category].push(onSetContent);
        }
    },
    
    /**
     * Converts the value on get content
     * @param {Ametys.form.field.RichText} field The richtext to convert
     * @param {tinymce.Editor} editor The current richtext editor
     * @param {Object} object The object of value to be modified
     * @param {String} [category=""] The category to apply.
     */
    convertOnGetContent: function(field, editor, object, category)
    {
        this.checkIfInitialized();

        category = category || "";

        Ext.Array.each(this._convertorsOnGetContent[category] || [], function (convertorOnGetContent) {
            convertorOnGetContent.apply(null, [field, editor, object]);
        });
        
        this.fireEvent('getcontent', field, editor, object);
    },
    
    /**
     * Converts the value on set content
     * @param {Ametys.form.field.RichText} field The richtext to convert
     * @param {tinymce.Editor} editor The current richtext editor
     * @param {Object} object The object of value to be modified
     * @param {String} [category=""] The category to apply.
     */
    convertOnSetContent: function(field, editor, object, category)
    {
        this.checkIfInitialized();

        category = category || "";

        Ext.Array.each(this._convertorsOnSetContent[category] || [], function (convertorOnSetContent) {
            convertorOnSetContent.apply(null, [field, editor, object]);
        });
        
        this.fireEvent('setcontent', field, editor, object);
    },
    
    /**
     * @private
     * Check if RichTextConfiguration is initialized and throw an exception otherwise
     */
    checkIfInitialized: function()
    {
        RichTextConfigurationBase.checkIfInitialized();
    },
    
    /**
     * Get language code. 
     * @return {String} language code.
     */
    getLanguageCode: function()
    {
        return Ametys.LANGUAGE_CODE;
    },
    
    /**
     * Get debug mode. 
     * @return {Boolean} debug mode.
     */
    getDebugMode: function()
    {
        return Ametys.DEBUG_MODE;
    },
    
    /**
     * Get Rtl mode. 
     * @return {Boolean} Rtl mode.
     */
    getRtlMode: function()
    {
        return Ametys.RTL_MODE;
    },
    
    /**
     * Get context path. 
     * @return {String} context path.
     */
    getContextPath: function()
    {
        return Ametys.CONTEXT_PATH;
    },
    
});