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

/**
 * Singleton class to add a service into selected page and zone
 * @private
 */
Ext.define('Ametys.plugins.web.zone.ServiceActions', {
    singleton: true,
    
    /**
     * @private
     * @property {Ametys.window.DialogBox} _box The service dialog box.
     */
    
    /**
     * @private
     * @property {Ametys.form.ConfigurableFormPanel} _form The configurable form panel in the service dialog box.
     */
    
    /**
     * @private
     * @property {Boolean} _hasTabs True if the current service has tabs.
     */
    
    /**
     * @private
     * @property {Number[]} _unvisitedCards The indexes of the cards which were not visited yet.
     */
    
    /**
     * Action function to be called by the controller.
     * Update its state given the controller configuration and open the service configuration dialog box to create a service
     * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
     * @param {String} controller.serviceId (required) The service identifier
     */
    add: function (controller)
    {
        var target = controller.getMatchingTargets()[0];
        if (target != null)
        {
            var btnConfig = controller.getInitialConfig();
            
            var params = {
                pageId: target.getParameters().id,
                zoneName: target.getParameters()['zone-name'],
                serviceId: controller.getInitialConfig('serviceId')
            };
            
            // Additional parameters
            for (var c in btnConfig)
            {
                if (c.indexOf("service-") == 0)
                {
                    params[c] = btnConfig[c];
                }
            }
            
            this.open ('new', params, controller.getInitialConfig('params-action'));
        }
    },
    
    /**
     * Action function to be called by the controller.
     * Update its state given the controller configuration and open the service configuration dialog box to edit a service.
     * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
     */
    edit: function (controller)
    {
        var target = controller.getMatchingTargets()[0];
        var zoneItemId = target.getParameters()['zoneitem-id'];
        var zoneItemTarget = target.getSubtarget(function (target) { return target.getId() == 'zoneitem' && target.getParameters().id == zoneItemId});
        
        if (target == null || zoneItemId == null || !zoneItemTarget.getParameters().service)
        {
            Ametys.log.ErrorDialog.display({
                title: "{{i18n PLUGINS_WEB_CONTENT_CONFIGURESERVICE_SELECTION_ERROR}}", 
                text: "{{i18n PLUGINS_WEB_CONTENT_CONFIGURESERVICE_SELECTION_ERROR_DESC}}",
                details: "",
                category: this.self.getName()
            });
            return;
        }
        
        Ametys.data.ServerComm.callMethod({
            role: "org.ametys.web.repository.page.PageDAO",
            methodName: 'getServiceParametersAction',
            parameters: [zoneItemTarget.getParameters().service],
            callback: {
                handler: this._getServiceParametersActionCb,
                scope: this,
                arguments: {
                    serviceId: zoneItemTarget.getParameters().service,
                    pageId: target.getParameters().id,
                    zoneItemId: zoneItemTarget.getParameters().id
                }
            },
            errorMessage: false
        });
    },
    
    /**
     * @private
     * Callback function invoked after retrieving service parameters' action
     * @param {String} paramAction The service parameters' action. If null or empty, the default one #_open will execute
     * @param {Object} args The callback arguments
     */
    _getServiceParametersActionCb: function (paramAction, args)
    {
        var params = {
            serviceId: args.serviceId,
            pageId: args.pageId,
            zoneItemId: args.zoneItemId
        };
        
        this.open ('edit', params, paramAction);
    },
    
    /**
     * Open the dialog box to configure service
     * @param {String} mode The mode: 'new' for service creation or 'edit' for service configuration
     * @param {Object} params the parameters
     * @param {String} params.pageId (required) The page id
     * @param {String} params.zoneName The zone name. Required for service creation
     * @param {String} params.zoneItemId The zone item id. Required for service edition
     * @param {String} params.serviceId (required) The service id
     * @param {String} [paramsAction] the action name to execute to configure parameters instead of the default one
     */
    open: function (mode, params, paramsAction)
    {
        var cb = mode == 'new' ? this._createServiceCb : this._editServiceCb;
        
        if (!Ext.isEmpty(paramsAction))
        {
            Ametys.executeFunctionByName (paramsAction, null, null, params, Ext.bind(cb, this), mode);
        }
        else
        {
            this._open (params, Ext.bind(cb, this), mode);
        }
    },
    
    /**
     * @private
     * Callback function after service has created and dialog box has closed.
     * @param {Object} params The params passed to the {@link #open} method
     */
    _createServiceCb: function (params)
    {
        var i18nDesc = "{{i18n PLUGINS_WEB_NOTIFICATION_CREATE_SERVICE_DESCRIPTION}}";
        
        Ametys.data.ServerComm.callMethod({
            role: "org.ametys.web.repository.page.PageDAO",
            methodName: 'getServiceInfo',
            parameters: [params.pageId, params.serviceId],
            callback: {
                handler: function(serviceInfo) {
                    Ametys.notify({
                        type: 'info',
                        iconGlyph: serviceInfo.iconGlyph,
                        icon: serviceInfo.iconGlyph != null ? null : Ametys.CONTEXT_PATH + serviceInfo.mediumIcon,
                        title: "{{i18n PLUGINS_WEB_NOTIFICATION_CREATE_SERVICE_TITLE}}",
                        description: Ext.String.format(i18nDesc, serviceInfo.label, serviceInfo['page-title']),
                        action: Ext.bind(Ametys.tool.ToolsManager.openTool, Ametys.tool.ToolsManager, [params.pageId.startsWith('sitemap://') ? 'uitool-sitemappage' : 'uitool-page', {id: params.pageId}], false)
                    });
                }
            },
            errorMessage: true
        });
    },
    
    /**
     * @private
     * Callback function after service has edited and dialog box has closed.
     * @param {Object} params The params passed to the {@link #open} method
     */
    _editServiceCb: function ()
    {
        // Nothing to do
    },
    
    /**
     * Open the dialog box to configure service
     * @param {Object} params the parameters
     * @param {String} params.pageId (required) The page id
     * @param {String} params.zoneName The zone name. Required for service creation
     * @param {String} params.zoneItemId The zone item id. Required for service edition
     * @param {String} params.serviceId (required) The service id
     * @param {Function} callback the callback function
     * @param {String} mode The mode: 'new' for service creation or 'edit' for service configuration
     */
    _open: function(params, callback, mode)
    {
        var me = this;
        function configureCallback(success)
        {
            if (success)
            {
                me._box.show();
                me._initForm (params);
            }
        }
        
        this._params = params;
        this._mode = mode;
        this._callback = callback;
        
        this.configureForm(params, configureCallback);
    },
    
    /**
     * @private
     * Create the service dialog box
     * @param {Object} cfg the configuration of the dialog box
     */
    _createDialogBox: function(cfg)
    {
        this._box = Ext.create('Ametys.window.DialogBox', Ext.apply(cfg, {
            title :"{{i18n PLUGINS_WEB_CONTENT_ADDSERVICE_PARAMETERS}}",
            iconCls : 'ametysicon-hammer2',
            
            scrollable: false,
            
            layout: 'fit',
            
            bodyPadding: '0',
            
            items: [],
            
            closeAction: 'destroy',
            
            referenceHolder: true,
            defaultButton: 'validate',
            
            buttons : [{
                    itemId: 'button-previous',
                    text :"{{i18n PLUGINS_WEB_CONTENT_ADDSERVICE_PREV_BUTTON}}",
                    disabled: true,
                    handler: Ext.bind(this._navHandler, this, [-1])
                },
                {
                    itemId: 'button-next',
                    text :"{{i18n PLUGINS_WEB_CONTENT_ADDSERVICE_NEXT_BUTTON}}",
                    disabled: true,
                    handler: Ext.bind(this._navHandler, this, [1])
                },
                {
                    reference: 'validate',
                    itemId: 'button-ok',
                    text :"{{i18n PLUGINS_WEB_ZONE_ADDSERVICEACTION_OK}}",
                    handler: this._validate,
                    scope: this
                }, {
                    text :"{{i18n PLUGINS_WEB_ZONE_ADDSERVICEACTION_CANCEL}}",
                    handler: function() { this._box.close(); },
                    scope: this
            } ]
        }));
    },
    
    /**
     * Configure form for service parameters
     * @param {Object} params the parameters
     * @param {String} params.serviceId (required) The service id
     * @param {String} params.pageId The page id. This is required if #zoneItemId is empty
     * @param {String} params.zoneName The zone name. This is required if #zoneItemId is empty
     * @param {String} params.zoneItemId The zone item id. This is required if #pageId is empty.
     * @param {Function} callback the callback function
     */
    configureForm: function(params, callback)
    {
        Ametys.plugins.web.zone.ZoneItemManager.getServiceParameterDefinitions([params.serviceId, params.pageId, params.zoneItemId, params.zoneName], this._getServiceParametersCb, {scope: this, arguments: [callback]});
    },

    /**
     * @private
     * Callback function after retrieving service's parameters.<br/>
     * Draw the forms.
     * @param {Object} response the server response
     * @param {Object[]} args the callback arguments.
     */
    _getServiceParametersCb: function(response, args)
    {
        // Configurable Form Panel
        var elements = response.parameters.elements;
        this._hasTabs = false;
        var me = this;
        
        Ext.Object.each(elements, function(key, value, object) {
            if (value.role && value.role == 'tab')
            {
                me._hasTabs = true;
            }
        });
        
        if (Ext.Object.isEmpty(elements))
        {
            // There is no parameter
            if (this._mode == 'edit')
            {
                // Should never happen given that the button should be disabled
                Ametys.Msg.show({
                       title: "{{i18n PLUGINS_WEB_CONTENT_CONFIGURESERVICE_LABEL}}",
                       msg: "{{i18n PLUGINS_WEB_CONTENT_CONFIGURESERVICE_NO_PARAMETERS}}",
                       buttons: Ext.Msg.OK,
                       icon: Ext.MessageBox.INFO
                });
            }
            else
            {
                // Create service directly
                Ametys.plugins.web.zone.ZoneItemManager.addService([this._params.pageId, this._params.zoneName, this._params.serviceId, {}], this._saveParametersCb, {scope: this});
            }
            return;
        }

        var cfpConfig = {
            hideDisabledFields: true,
            
            additionalWidgetsConfFromParams: {
                contentType: 'contentType' // some widgets requires the contentType configuration  
            }
        };

        var tabsWidth = 180;
        if (this._hasTabs)
        {
            Ext.apply(cfpConfig, {
                tabPosition: 'left',
                
                layout: 'fit',
                itemsLayout: 'fit',
                tabItemsLayout: 'anchor',
                scrollable: false,
                tabBar: {defaults: {width: tabsWidth}},
                
                spacingCls: false
            });
        }
        
        this._form = Ext.create('Ametys.form.ConfigurableFormPanel', cfpConfig);
        this._form.configure(elements);
        
        // Dialog box
        var boxCfg = {};
        this._boxDimensions(boxCfg, response.height, response.width, tabsWidth);
        this._createDialogBox(boxCfg);
        var unvisitedCardsSize = this._hasTabs ? Ext.Object.getSize(elements) : 1;
        this._unvisitedCards = Array(unvisitedCardsSize).fill(null).map(function(v, i) {return i;}); // creates array [0, 1, 2, 3, 4, 5...]
        this._box.add(this._form);
        
        args[0](true);
    },
    
    /**
     * @private
     * Fill in the configuration object the height and the width of the dialog box.
     * @param {Object} boxCfg The configuration of the dialog box to fill
     * @param {Number} height The height to apply. Can be null
     * @param {Number} width The width to apply. Can be null
     * @param {Number} tabsDefaultWidth The width of the tabs when multiple groups
     */
    _boxDimensions: function(boxCfg, height, width, tabsDefaultWidth)
    {
        var ratio = 0.80,
            multGroupMaxHeight = window.innerHeight * ratio,
            maxWidth = window.innerWidth * ratio;
            
        if (height)
        {
            if (height >= 0)
            {
                // Apply specified height, but cannot be bigger than multGroupMaxHeight
                boxCfg['height'] = Math.min(height, multGroupMaxHeight);
            }
            else
            {
                // height < 0, apply the max possible height
                boxCfg['height'] = multGroupMaxHeight;
            }
        }
        else
        {
            // Default, fit the height to the form
            Ext.apply(boxCfg, {
                freezeHeight: this._hasTabs,
                minHeight: 300,
                maxHeight: 500
            });
        }
        
        // Default width
        var boxCfgWidth = 820;
        if (width)
        {
            if (width >= 0)
            {
                // Apply specified width, but cannot be bigger than maxWidth
                boxCfgWidth = Math.min(width, maxWidth);
            }
            else
            {
                // width < 0, apply the max possible width
                boxCfgWidth = maxWidth;
            }
        }
        
        if (this._hasTabs)
        {
            // Add the tabs width for a service with multiple groups
            boxCfgWidth += tabsDefaultWidth;
        }
        
        boxCfg['width'] = boxCfgWidth;
    },
    
    /**
     * @private
     * Initialize forms
     * @param {Object} params the initial parameters
     */
    _initForm: function(params)
    {
        if (this._hasTabs)
        {
            var tabPanel = this._getFormTabPanel();
            tabPanel.on('tabchange', this._updateButtons, this);
        }
        this._updateButtons();
        
        if (this._mode == 'edit')
        {
            Ametys.plugins.web.zone.ZoneItemManager.getServiceParameterValues([params.zoneItemId, params.serviceId], this._initFormCb, {scope: this});
        }
        else
        {
            this._initFormCb();
        }
    },
    
    /**
     * @private
     * Initialize form values
     * @param {Object} response The values of service parameters
     */
    _initFormCb: function (response)
    {
        this._form.setValues(response);
        this._form.focus();
    },
    
    /**
     * @private
     * Go to the previous or next page
     * @param {Number} index 1 to go the next page, -1 to go to the previous page
     */
    _navHandler: function (index)
    {
        // 'tabchange' listener will do the job for updating buttons
        this._getFormTabPanel().setActiveTab(this._getCurrentCardIndex() + index);
    },
    
    /**
     * @private
     * Update dialog box buttons
     */
    _updateButtons: function()
    {
        var currentCardIndex = 0,
            lastCardIndex = 0;
        if (this._hasTabs)
        {
            var cfpTabPanel = this._getFormTabPanel();
            currentCardIndex = this._getCurrentCardIndex();
            lastCardIndex = cfpTabPanel.items.getCount() - 1;
        }
        
        // PREVIOUS BUTTON
        var hasAPreviousCard = currentCardIndex > 0;
        
        // NEXT BUTTON
        var hasANextCard = currentCardIndex < lastCardIndex;
        
        // OK BUTTON
        Ext.Array.remove(this._unvisitedCards, currentCardIndex);
        var allCardsVisited = this._unvisitedCards.length == 0;
        var enableOkBtn = allCardsVisited || this._mode == 'edit';

        // APPLY
        var nextButton = this._box.down("button[itemId='button-next']");
        var previousButton = this._box.down("button[itemId='button-previous']");
        var okButton = this._box.down("button[itemId='button-ok']");
        
        previousButton.setDisabled(!hasAPreviousCard);
        nextButton.setDisabled(!hasANextCard);
        okButton.setDisabled(!enableOkBtn);
    },
    
    /**
     * @private
     * Gets the TabPanel of the ConfigurableFormPanel
     * @return {Ext.tab.Panel} The tab panel of the form. Can be null (if there is only one group and thus no tab panel for instance)
     */
    _getFormTabPanel: function()
    {
        if (this._hasTabs)
        {
            return this._form && this._form.getFormContainer().items.first();
        }
        else
        {
            return null;
        }
    },
    
    /**
     * @private
     * Gets the index of the current card
     * @return {Number} The index of the current card
     */
    _getCurrentCardIndex: function()
    {
        if (this._hasTabs)
        {
            var cfpTabPanel = this._getFormTabPanel();
            return cfpTabPanel.items.indexOf(cfpTabPanel.getLayout().activeItem);
        }
        else
        {
            return 0;
        }
    },
    
    /**
     * @private
     * Validate the dialog box and create the service.
     */
    _validate: function ()
    {
        var invalidFields = this._form.getInvalidFields();
        var values = this._form.getJsonValues();
        
        if (invalidFields.length > 0)
        {
            Ametys.Msg.show({
                   title: "{{i18n PLUGINS_WEB_CONTENT_ADDSERVICE_PARAMETERS_SAVE}}",
                   msg: "{{i18n PLUGINS_WEB_CONTENT_ADDSERVICE_PARAMETERS_SAVE_INVALIDFIELDS}}" + Ametys.form.SaveHelper.getInvalidFieldsAsReadableList(invalidFields),
                   buttons: Ext.Msg.OK,
                   icon: Ext.MessageBox.ERROR
            });
            return;
        }

        if (this._mode == 'new')
        {
            Ametys.plugins.web.zone.ZoneItemManager.addService([this._params.pageId, this._params.zoneName, this._params.serviceId, values], this._saveParametersCb, {scope: this});
        }
        else
        {
            Ametys.plugins.web.zone.ZoneItemManager.editServiceParameterValues([this._params.zoneItemId, this._params.serviceId, values], this._saveParametersCb, {scope: this});
        }
    },
    
    /**
     * @private
     * Callback after saving service
     * @param {Object} response The server response
     * @param {Object} args The callback arguments
     */
    _saveParametersCb: function (response, args)
    {
        var errors = response.error;
        if (Ext.isEmpty(errors))
        {
            if (this._box)
            {
                this._box.close();
            }
            
            // Execute callback function passed to #open method
            if (Ext.isFunction(this._callback))
            {
                this._callback(this._params);
            }
        }
    },
	
	/**
	 * Action function to be called by the controller.
	 * Allow an user to copy a service, in order to duplicate it later (with a paste action).
	 * @param {Ametys.plugins.web.page.controller.CopyServiceController} controller The controller calling this function
	 */
	copy: function (controller)
	{
		var target = controller.getMatchingTargets()[0];
		if (target != null)
        {
	        var zoneItemId = target.getParameters()['zoneitem-id'];
	        var zoneItemTarget = target.getSubtarget(function (target) { return target.getId() == 'zoneitem' && target.getParameters().id == zoneItemId});
	        
	        if (zoneItemId == null || !zoneItemTarget.getParameters().service)
	        {
	            Ametys.log.ErrorDialog.display({
	                title: "{{i18n PLUGINS_WEB_CONTENT_COPY_SERVICE_SELECTION_ERROR}}", 
	                text: "{{i18n PLUGINS_WEB_CONTENT_COPY_SERVICE_SELECTION_ERROR_DESC}}",
	                details: "",
	                category: this.self.getName()
	            });
	            return;
	        }
	        
			Ametys.clipboard.Clipboard.setData (Ametys.message.MessageTarget.ZONE_ITEM, {id: zoneItemId, service: zoneItemTarget.getParameters().service});
        }
	},
	
	/**
	 * This action pastes the copied service in a new zone
	 * @param {Ametys.plugins.web.page.controller.PasteServiceController} controller The controller calling this function
	 */
	paste: function (controller)
	{
		var clipboardData = Ametys.clipboard.Clipboard.getData();
		
		if (clipboardData.length > 0 && Ametys.clipboard.Clipboard.getType() == Ametys.message.MessageTarget.ZONE_ITEM && clipboardData[0].service)
		{
			var target = controller.getMatchingTargets()[0];
	        if (target != null)
	        {
	            Ametys.plugins.web.zone.ZoneItemManager.pasteService([clipboardData[0].id, clipboardData[0].service, target.getParameters().id, target.getParameters()['zone-name']]);
	        }
		}
		else
		{
			Ametys.Msg.show({
				title: "{{i18n PLUGINS_WEB_CONTENT_PASTE_SERVICE_LABEL}}",
				msg: "{{i18n PLUGINS_WEB_CONTENT_PASTE_SERVICE_NO_COPIED_SERVICE_MSG}}",
				buttons: Ext.Msg.OK,
				icon: Ext.Msg.ERROR
			});
		}
	}
});