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

/**
 * This helper provides a dialog box to create a content.
 */
Ext.define('Ametys.cms.uihelper.EditContent', {
    singleton: true,
    
    /**
     * @property {Ametys.cms.content.Content} _content The modified content.
     * @private
     */
    
    /**
     * @property {String} [_viewName=main] The name of the view to be displayed in attributes tree. Can be null to use the default value.
     * @private
     */
    
    /**
     * @property {String} [_fallbackViewName=main] The name of the fallback view to be displayed in attributes tree. Can be null to use the default value.
     * @private
     */
    
    /**
     * @property {Number} [_editWorkflowActionId=2] The id of the workflow action to edit the target content. Can be null to use the default value.
     * @private
     */
    
    /**
     * @property {String} [_contentMessageTargetType=Ametys.message.MessageTarget#CONTENT] The message target type.
     * @private
     */
    
    /**
     * @property {Ametys.form.ConfigurableFormPanel} _form The form panel
     * @private
     */
    
    /**
     * @property {Ametys.window.DialogBox} _box The dialog box
     * @private
     */
    
    /**
     * @property {Boolean} [_fullContents=false] True to returns {@link Ametys.cms.content.Content}. If false only the content identifiers will be returned
     * @private
     */
    
    /**
     * @property {Function} [_cbFn=Ext.emptyFn] The callback function to call after the modification of the content
     * @private
     */
    
    /**
     * @property {Function} [_cbScope=this] The scope for the #_cbFn callback function.
     * @private
     */
    
    /**
     * @property {Function} [_validateCb=this._editContent] The callback function to call after the validation of the dialog box.
     * @private
     */
    
    /**
     * @property {Function} [_validateCbScope=this] The scope for the #_validateCb callback function.
     * @private
     */
    
    /**
     * @property {Boolean} _editionFormReady The _editionFormReady is set to true when the form is ready.
     * @private
     */
    
    /**
     * Action function to be called by the controller.
     * Will open the dialog box to edit content
     * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
     */
    act: function (controller)
    {
        var config = controller.initialConfig;
        
        var target = controller.getMatchingTargets()[0];
        if (target != null)
        {
            var contentId = target.getParameters().id;
            
            Ametys.cms.content.ContentDAO.getContent(contentId, function(content) {
                config.content = content;
                Ametys.cms.uihelper.EditContent.open(config);
            });            
        }
    },
    
    
    /**
     * Open a dialog box to edit a content.
     * @param {Object} config The configuration options. Has the following parameters:
     * @param {Ametys.cms.content.Content} config.content (required) The content to edit
     * @param {String} [config.contentMessageTargetType=Ametys.message.MessageTarget#CONTENT] The message target factory role to use for the event of the bus thrown after content edition
     * @param {Number} [config.editWorkflowActionId=2] The id of the workflow edition action
     * @param {String} [config.viewName=default-edition] The name of view to used for modification.
     * @param {String} [config.fallbackViewName=main] The name of fallback view to used for modification.
     * @param {Function} [config.validateCallback] If provided, this function will be called when the user clicks on 'OK' instead of the default function, which creates the content.
     * @param {Object} [config.validateCallbackScope] The scope of the validate callback.
     * @param {Boolean} [config.fullContents=false] If `true`, the callback function receives a Content object as argument. If `false`, only the content id is provided.
     * @param {String} [config.iconCls=ametysicon-edit5] The icon class for the dialog box.
     * @param {String} [config.title] The title of the dialog box. If not provided, a default title will be displayed.
     * @param {String} [config.helpmessage1] The message displayed at the top of the dialog box. If not provided, a default message will be displayed.
     * @param {String} [config.helpmessage2] The message displayed at the bottom of the dialog box. If not provided, a default message will be displayed.
     * @param {Object} [config.dialogConfig] The dialog configuration to override or add additional parameters to the edit dialog box.
     * @param {Object} [config.formConfig] The configurable form panel configuration to override or add additional parameters to the edit configurable form panel.
     * @param {Function} callback The callback function invoked when the content has been created. The callback function will received the following parameters:
     * @param {String/Ametys.cms.content.Content} callback.content The created content as an id or a {@link Ametys.cms.content.Content}. See #config.fullContents
     * @param {Object} [scope=window] The callback scope, default to window.
     */
    open: function (config, callback, scope)
    {
        this._content = config.content;
        
        // Content is mandatory
        if (!this._content)
        {
            Ametys.log.ErrorDialog.display({
                title: "{{i18n plugin.cms:PLUGINS_CMS_CONTENT_UIHELPER_EDITCONTENT_CONFIGURATION_ERROR}}",
                text: "{{i18n plugin.cms:PLUGINS_CMS_CONTENT_UIHELPER_EDITCONTENT_CONTENT_ERROR_TEXT}}",
                category: Ext.getClassName(this)
            });
            return;
        }
        
        this._contentMessageTargetType = config.contentMessageTargetType || Ametys.message.MessageTarget.CONTENT;
        
        this._editWorkflowActionId = config.editWorkflowActionId || 2;
        this._viewName = config.viewName || 'default-edition';
        this._fallbackViewName = config.fallbackViewName || 'main';
        
        this._validateCb = config.validateCallback;
        this._validateCbScope = config.validateCallbackScope;
        
        this._cbFn = callback || Ext.emptyFn;
        this._cbScope = scope || window;
        
        this._fullContents = config.fullContents === true;
        
        this._initializeAndShowDialogBox(config);
    },
    
    /**
     * @private
     * Initialize and show dialog box
     * @param {Object} config The configuration
     */
    _initializeAndShowDialogBox : function (config)
    {
        var title = config.title || Ext.String.format("{{i18n plugin.cms:PLUGINS_CMS_CONTENT_UIHELPER_EDITCONTENT_TITLE}}",  this._content.getTitle())
        var iconCls = config.iconCls || 'ametysicon-edit5';
        var helpMsg1 = config.helpmessage1 || "{{i18n plugin.cms:PLUGINS_CMS_CONTENT_UIHELPER_EDITCONTENT_MESSAGE1}}";
        var helpMsg2 = config.helpmessage2 || "{{i18n plugin.cms:PLUGINS_CMS_CONTENT_UIHELPER_EDITCONTENT_MESSAGE2}}";
        var width = config.width || 500;
        var maxHeight = config.maxHeight || window.innerHeight*0.8;
        var disallowEdition = config.disallowEdition != null ? config.disallowEdition : false;
        var dialogConfig = config.dialogConfig || {};
        var formConfig = config.formConfig || {};
        
        // Form creation (can be empty)
        this._form = Ext.create('Ametys.form.ConfigurableFormPanel', Ext.applyIf (formConfig, {
            border : false,
            scrollable : false,
            
            labelAlign: config.labelAlign || 'top',
            
            defaultFieldConfig : {
                labelWidth : config.labelWidth || 130,
                anchor : '100%'
            },
            
            bodyStyle : {
                padding : 0
            },
            
            additionalWidgetsConf: {
                standaloneEdition: true // standalone edition (use case: a popup without ribbon (for example richtext buttons) or FO edition)  
            },
            
            additionalWidgetsConfFromParams: {
                contentType: 'contentType', // some widgets require the contentType configuration
                editableSource: 'editableSource' // for richtext widgets we want to check the right on content
            },
            
            fieldNamePrefix : 'content.input.',
            displayGroupsDescriptions: false
        }));
        
        this._box = Ext.create('Ametys.window.DialogBox', Ext.applyIf (dialogConfig, {
            title: title,
            iconCls: iconCls,
            
            width: width,
            maxHeight: maxHeight,
            
            scrollable: true,
            
            layout: 'anchor',
            defaultType: 'textfield',
            defaults: {
                cls: 'ametys',
                labelAlign: 'right',
                labelSeparator: '',
                labelWidth: 130,
                anchor: '100%',
                style: 'margin-right:' + (Ametys.form.ConfigurableFormPanel.OFFSET_FIELDSET + 10 + Ametys.form.ConfigurableFormPanel.PADDING_TAB) + 'px'
            },
            
            items: [{
                        xtype: 'component',
                        itemId: 'helpmessage1',
                        cls: 'a-text',
                        html: helpMsg1
                    },
                    this._form,
                    {
                        xtype: 'component',
                        itemId: 'helpmessage2',
                        cls: 'a-text',
                        html: helpMsg2
                    }
            ],
        
            selectDefaultFocus: true,
            closeAction: 'destroy',
            
            referenceHolder: true,
            defaultButton: 'validate',
            
            listeners:
            {
                scope: this,
                close: function() {this._content.unlock(null, false);}
            },
            
            buttons : [{
                reference: 'validate',
                disabled: disallowEdition,
                text :"{{i18n plugin.cms:PLUGINS_CMS_CONTENT_UIHELPER_EDITCONTENT_OK}}",
                handler: this._validate,
                scope: this
            }, {
                text :"{{i18n plugin.cms:PLUGINS_CMS_CONTENT_UIHELPER_EDITCONTENT_CANCEL}}",
                handler: Ext.bind(function() {this._box.close();}, this) 
            }]
        }));
        
        this._content.lock(Ext.bind(this._drawEdition, this), false);
    },
    
    /**
     * Draw edition form
     * @private
     */
    _drawEdition: function (success)
    {
        if (!success)
        {
            Ametys.plugins.cms.content.actions.SaveAction.backToContent(this._content.getId());
            return;
        }
        
        Ametys.data.ServerComm.suspend();
        
        // 1 Ask server for view definition
        Ametys.data.ServerComm.callMethod({
            role: 'org.ametys.cms.content.ContentHelper',
            methodName: 'getContentViewAsJSON',
            parameters: [this._content.getId(), this._viewName, this._fallbackViewName, true],
            callback: {
                scope: this,
                handler: this._drawEditionForm,
                ignoreOnError: false
            },
            waitMessage: true
        });
        
        // 2 Ask server for view values
        Ametys.data.ServerComm.send({
            url: '_content.xml',
            parameters:  {contentId: this._content.getId(), isEdition: "true", viewName: this._viewName, fallbackViewName: this._fallbackViewName}, 
            priority: Ametys.data.ServerComm.PRIORITY_MAJOR, 
            waitMessage: true,
            callback: {
                handler: this._fillEditionForm,
                scope: this
            }
        });
        
        Ametys.data.ServerComm.restart();
    },
    
    /**
     * Callback function called after retrieving view from the content.
     * This draws the form for content edition
     * @param {Object} response The XML response provided by the {@link Ametys.data.ServerComm}
     * @param {Object} args The callback parameters passed to the {@link Ametys.data.ServerComm#send} method
     * @private
     */
    _drawEditionForm: function (response, args)
    {
        if (response === undefined)
        {
            Ametys.notify({
                title: Ext.String.format("{{i18n PLUGINS_CMS_TOOL_CONTENT_FORM_VIEW_ERROR_TITLE}}", this._content.getTitle()),
                description: "{{i18n plugin.cms:PLUGINS_CMS_TOOL_CONTENT_FORM_VIEW_ERROR_MESSAGE}}",
                type: "warn"
            });
            
            this.getLogger().error("Cannot modify content '" + this._content.getId() + "' because the view is not compatible with edition");
        }
        else
        {
            this._form.additionalWidgetsConf.contentInfo = {
                contentId: this._content.getId()
            };
            
            this._form.configure(response.view.elements);
            
            this._editionFormReady = true;
            this._box.show();
        }
    },
    
    /**
     * Callback function called after retrieving metadata values from the
     * content. This sets the the form field values
     * 
     * @param {Object} response The XML response provided by the {@link Ametys.data.ServerComm}
     * @param {Object} args The callback parameters passed to the {@link Ametys.data.ServerComm#send} method
     * @private
     */
    _fillEditionForm: function (response, args)
    {
        if (Ametys.data.ServerComm.handleBadResponse("{{i18n plugin.cms:PLUGINS_CMS_TOOL_CONTENT_FORMDEFINITION_ERROR}} '" + this._content.getId() + "'", response, Ext.getClassName(this)))
        {
            return;
        }
        
        if (!this._editionFormReady)
        {
            if (this.getLogger().isWarnEnabled())
            {
                this.getLogger().warn("#_fillEditionForm has been called but the edition form is not drawn");
            }
        }
        
        this._form.setValues(response, "metadata");
        
        this._form.on({
            afterlayout: {fn: this._focusForm, scope: this, single: true}
        });
    },
    
    /**
     * @private
     * Focuses the form panel
     */
    _focusForm: function()
    {
        this._form.focus();
    },
    
    /**
     * This function is called when validating the dialog box.
     * @private
     */
    _validate: function()
    {
        var validateFn = this._validateCb ? this._validateCb : this._editContent;
        var validateScope = this._validateCb && this._validateCbScope ? this._validateCbScope : this;
        
        var invalidFields = Ext.Array.merge(this._form.getInvalidFields(), this._form.getInvalidRepeaters());
        if (invalidFields.length > 0)
        {
            Ametys.Msg.show({
                   title: "{{i18n PLUGINS_CMS_SAVE_ACTION_SAVE}}", //{{i18n CONTENT_EDITION_SAVE_LABEL}}",
                   msg: "{{i18n PLUGINS_CMS_SAVE_ACTION_INVALIDFIELDS}}" + Ametys.form.SaveHelper.getInvalidFieldsAsReadableList(invalidFields), //"{{i18n CONTENT_EDITION_SAVE_INVALID_FIELD}}",
                   buttons: Ext.Msg.OK,
                   icon: Ext.MessageBox.ERROR
            });
            return;
        }
        
        var warnedFields = this._form.getWarnedFields();
        if (!Ext.Object.isEmpty(warnedFields))
        {
            Ametys.Msg.show({
                title: "{{i18n PLUGINS_CMS_SAVE_ACTION_SAVE}}",
                msg: "{{i18n PLUGINS_CMS_SAVE_ACTION_WARNED_FIELDS_START}}" + Ametys.form.SaveHelper.getWarnedFieldsAsReadableList(warnedFields) + "{{i18n PLUGINS_CMS_SAVE_ACTION_WARNED_FIELDS_END}}",
                buttons: Ext.Msg.YESNO,
                icon: Ext.Msg.QUESTION,
                modal: true,
                fn: Ext.bind (this._bypassWarnedFields, this, [validateFn, validateScope], 1),
                scope: this
            });
            return;
        }
        
        validateFn.call(validateScope);
    },
    
    /**
     * Callback function called after modification process
     * @param {Boolean} success If the edition was successful
     * @param {Ametys.cms.content.Content} content The created content. Can be null if the creation failed
     * @param {String[]} workflowErrors Errors messages related to the workflow
     * @param {Object} fieldsInError The list of fields in error associated with their error message
     * @param {Object} fieldsWithWarning The list of fields with warning associated with their warning message
     * @param {Boolean} ignoreWarnings True to ignore warnings from server response
     * @private
     */
    _editContentCb: function (success, content, workflowErrors, fieldsInError, fieldsWithWarning, ignoreWarnings)
    {
        if (!success && !Ext.Object.isEmpty(fieldsInError))
        {
            this._form.markFieldsInvalid (fieldsInError);
            return;
        }
        
        if (!success && ignoreWarnings)
        {
            // The ignore warning can only be set to true if there was warnings
            // But it can be warnings from global validator, then fieldsWithWarning is empty
            this._editContent(true);
            return;
        }
        
        if (!success && !Ext.Object.isEmpty(fieldsWithWarning))
        {
            this._form.markFieldsWarning (fieldsWithWarning);
            return;
        }

        if (!success)
        {
            return;
        }
        
        if (content != null)
        {
            // Success
            this._box.close();
            this._cbFn.call(this._cbScope, content);
        }
    },
    
    /**
     * @private
     * The default callback function when the dialog is validated : its purpose it to transmis the edition to the server
     * @param {Boolean} ignoreWarnings True to ignore warnings during content modification
     */
    _editContent: function(ignoreWarnings)
    {
        var formValues = this._form.getJsonValues();
        
        var params = {
            contentId: this._content.getId(),
            values: formValues,
            viewName: this._viewName,
            fallbackViewName: this._fallbackViewName,
            editWorkflowActionId: this._editWorkflowActionId,
            ignoreWarnings: ignoreWarnings || false,
            bypassedWarnings: this._form.getWarnedFields()
        };
        
        Ametys.cms.content.ContentDAO.editContent(
            params, 
            this._editContentCb, 
            this, 
            this._fullContents, 
            this._contentMessageTargetType, 
            null, 
            this._box
        );
    },
    
    /**
     * @private
     * This function is called after validated confirm dialog box
     * @param {String} answer The button answer ('yes' or 'no')
     * @param {Function} validateFn The validation function
     * @param {Object} validateScope The scope for validatation function
     */
    _bypassWarnedFields : function (answer, validateFn, validateScope)
    {
        if (answer == 'yes')
        {
            validateFn.call(validateScope);
        }
    }
});