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

/**
 * This widget is a tri-state widget used for all types of externalizable field.
 * A externalizable field is a field with a editable local value and a non-editable external value.
 */
Ext.define('Ametys.form.widget.Externalizable', {
    extend: 'Ametys.form.AbstractFieldsWrapper',
    alias: ['widget.externalizable'],
    
    statics: {
    	/** 
         * @property {String} _LOCAL_SUFFIX The suffix for local widget
         * @readonly
         * @private
         */ 
    	_LOCAL_SUFFIX: "_local",
        
    	/** 
         * @property {String} _LOCAL_SUFFIX The suffix for local widget
         * @readonly
         * @private
         */ 
    	_EXTERNAL_SUFFIX: "_external",
    	
    	/** 
         * @property {String} _STATUS_SUFFIX The suffix for status field
         * @readonly
         * @private
         */ 
    	_STATUS_SUFFIX: "_status",
        
        /**
         * Get the value currently in use. 
         * If the value comes from a externalizable field, the local or external value will be returned according to the current status.
         * @param {Object} value the value
         * @return {Object} value the value in use
         */
        getValueInUse: function(value)
        {
            if (Ext.isObject(value) && value.status && value.local !== undefined && value.external !== undefined)
            {
                return value.status == 'external' ? value.external : value.local;
            }
            else
            {
                return value;
            }
        }
    },
    	
    /**
     * @property {String} externalizableCls The base class for this field
     * @private
     */
    externalizableCls: "x-field-externalizable",
    
    /**
     * @property {String} localStatusBtnIconCls The separated CSS classes to be applied to the icon of status button when local value is active.
     * @private
     */
    localStatusBtnIconCls: 'ametysicon-v-switch-off',
    
    /**
     * @property {String} localStatusBtnCls The CSS class to apply to the status button when local value is active.
     * @private
     */
    localStatusBtnCls: 'x-field-externalizable-btn-local',
    
    /**
     * @property {String} localStatusBtnTooltip The tooltip for synchronization button when local value is active
     * @private
     */
    localStatusBtnTooltip: "{{i18n PLUGINS_CORE_UI_WIDGET_EXTERNALIZABLE_SYNCHRO_OFF}}",
    
    /**
     * @property {String} extStatusBtnIconCls The separated CSS classes to be applied to the icon of status button when external value is active.
     * @private
     */
    extStatusBtnIconCls: 'ametysicon-v-switch-on',
    
    /**
     * @property {String} extStatusBtnCls The CSS class to apply to the status button when external value is active.
     * @private
     */
    extStatusBtnCls: 'x-field-externalizable-btn-external',
    
    /**
     * @property {String} extStatusBtnTooltip The tooltip for synchronization button when external value is active
     * @private
     */
    extStatusBtnTooltip: "{{i18n PLUGINS_CORE_UI_WIDGET_EXTERNALIZABLE_SYNCHRO_ON}}",
    
    /** 
     * @cfg {String/Object} layout @inheritdoc
     * @private 
     */
    layout: { 
        type: 'hbox',
        align: 'middle'
    },
    
    config: {
        /**
         * @cfg {Boolean} readOnly true to prevent the user from changing the field
         */
        readOnly: false
    },

    /** 
     * @cfg {Object/Object[]} items @inheritdoc
     * @private 
     */
     
    /**
     * @readonly
     * @property {Boolean} isExternalizableField True means the field is a tri-state field for externalizable values
     */
    isExternalizableField: true, 
    
    
    /**
     * @private
     * @property {Ext.form.Field} _localField The field holding the local value
     */
    
    /**
     * @private
     * @property {Ext.form.Field} _externalField The field holding the external value
     */
    
    /**
     * @private
     * @property {Ext.Button} _statusBtn The button to switch on/off the synchronization
     */
     
    constructor: function(config)
    {
        config.cls = Ext.Array.from(config.cls);
        config.cls.push(this.externalizableCls);
        config.id = config.id || Ext.id();
        
        this._localField = this._createWrappedField(config.name + Ametys.form.widget.Externalizable._LOCAL_SUFFIX, config, false);
        this._externalField = this._createWrappedField(config.name + Ametys.form.widget.Externalizable._EXTERNAL_SUFFIX, config, true);
        
        if (this._localField.isRichText)
        {
        	this.isRichText = this._localField.isRichText;
        	this.getNode = Ext.bind(this._localField.getNode, this._localField);
        	this.getEditor = Ext.bind(this._localField.getEditor, this._localField);
            this.hasSemanticAnnotations = Ext.bind(this._localField.hasSemanticAnnotations, this._localField);
            this.getSemanticAnnotation = Ext.bind(this._localField.getSemanticAnnotation, this._localField);
            this.getSemanticAnnotations = Ext.bind(this._localField.getSemanticAnnotations, this._localField);
        }
        
        this._statusBtn = Ext.create ('Ext.Button', {
        	cls: 'a-btn-lighter ' + this.localStatusBtnCls,
        	iconCls: this.localStatusBtnIconCls,
        	tooltip: this.localStatusBtnTooltip,
        	border: false,
        	enableToggle: true,
        	toggleHandler: this._switchStatus,
        	scope: this,
        	scale: 'large',
        	width: 40,
        	height: 40
        });
        
        config.items = [this._statusBtn, {
        	xtype: 'container',
        	flex: 1,
        	layout: 'anchor',
        	items:[
        	   this._localField, 
        	   this._externalField
        	]
        }]
        
        this.callParent(arguments);
    },
    
    /**
     * @private
     * Create the wrapped field (local or external)
     * @param {String} fieldName The field name
     * @param {Object} config The initial configuration
     * @param {Boolean} external True if the field is the created field
     */
    _createWrappedField: function (fieldName, config, external)
    {
    	var clonedConfig = Ext.clone(config);
    	
    	var wrappedWidgetCfg = Ext.applyIf({
            name: fieldName,
            hideLabel: true,
            width: '100%',
            readOnly: config.readOnly || external,
            disabled: external,
            id: Ext.id(),
            msgTarget: 'none',
            preventMark: true
        }, clonedConfig);
    	
        delete wrappedWidgetCfg.itemId;
        delete wrappedWidgetCfg.ametysDescription;
        delete wrappedWidgetCfg.ametysDescriptionUrl;
    	delete wrappedWidgetCfg.fieldLabel;
    	delete wrappedWidgetCfg.anchor;
    	delete wrappedWidgetCfg.style;
    	delete wrappedWidgetCfg.cls;
    	delete wrappedWidgetCfg['wrapped-widget'];
    	
    	if (external)
    	{
    		wrappedWidgetCfg.style = {
    			marginBottom: 0
    		}
    	}
        
        if (clonedConfig.validation)
        {
            var validation = clonedConfig.validation;
            
            wrappedWidgetCfg.validationConfig = validation;
            wrappedWidgetCfg.regexp = validation.regexp || null;
            
            if (validation.invalidText)
            {
            	wrappedWidgetCfg.invalidText = validation.invalidText;
            }
            if (validation.regexText)
            {
            	wrappedWidgetCfg.regexText = validation.regexText;
            }
        }
        
        if (clonedConfig.enumeration)
        {
            wrappedWidgetCfg.enumeration = Ext.clone(clonedConfig.enumeration);
        }
        
        var field = Ametys.form.WidgetManager.getWidget (clonedConfig['wrapped-widget'], config.type.toLowerCase(), wrappedWidgetCfg);
        
        if (field.isRichText)
        {
            /**
             * @event editorhtmlnodeselected
             * Fires when a HTML node is selected in the editor of a richtext field
             * @param {Ext.form.Field} field The editor field
             * @param {tinymce.Editor} editor The tinyMCE editor
             * @param {HTMLElement} node The HTML element selected
             */
             field.on({
                'editorhtmlnodeselected': { fn: function (field, node) { this.fireEvent ('editorhtmlnodeselected', this, node)}, scope: this }
            });
        }
        
        // if field is disabled or not visible we return no errors
        field.getErrors = Ext.Function.createInterceptor(field.getErrors, function() { return this.isVisible() && !this.isDisabled(); }, null, []);
        field.on('resize', this._onWrappedResize, this);
        return field;
    },
    
    /**
     * @private
     * Listener when the wrapped fields are resized
     * @param {Ext.form.field.Field} field The resized field
     * @param {Number} width The new width that was set.
     * @param {Number} height The new height that was set.
     * @param {Number} oldWidth The previous width. Can be null at first size
     * @param {Number} oldHeight The previous height. Can be null at first size
     */
    _onWrappedResize: function(field, width, height, oldWidth, oldHeight)
    {
        if (oldHeight && height != oldHeight)
        {
            this.setHeight(this.getHeight() + height - oldHeight);
        }
    },
    
    /**
     * @private
     * Handle when the status button is pressed
     * @param {Ext.Button} btn The toggle button
     * @param {Boolean} state The state of the button
     */
    _switchStatus: function (btn, state)
    {
    	this._switchToExternalStatus(state);
    	this.renderActiveWarning();
    },
    
    /**
     * @private
     * Switch the current status of synchronization
     * @param {Boolean} state true if to switch to the external status, false otherwise
     */
    _switchToExternalStatus: function (state)
    {
        var oldValue = this.getValue();
        
    	this._status = state ? 'external' : 'local';
    	if (state)
    	{
    		this._statusBtn.setTooltip(this.extStatusBtnTooltip);
    		this._statusBtn.setIconCls(this.extStatusBtnIconCls);
    		this._statusBtn.removeCls(this.localStatusBtnCls);
    		this._statusBtn.addCls(this.extStatusBtnCls);
    	}
    	else
    	{
    		this._statusBtn.setTooltip(this.localStatusBtnTooltip);
    		this._statusBtn.setIconCls(this.localStatusBtnIconCls);
    		this._statusBtn.removeCls(this.extStatusBtnCls);
    		this._statusBtn.addCls(this.localStatusBtnCls);
    	}
    	
        if (!this.isDisabled()) // do not change state if the widget is disabled
        {
            this._localField.setDisabled(state);
            this._externalField.setDisabled(!state);
        }
        
        this.fireEvent('change', this, this.getValue(), oldValue);
    },
    
    isEqual: function (newVal, oldVal)
    {
        if (Ext.isObject(newVal) && Ext.isObject(oldVal))
        {
            return newVal.status == oldVal.status
                && newVal.local == oldVal.local
                && newVal.external == oldVal.external
        }
        
        return this.callParent(arguments);
    },
    
    getValue: function()
    {
        return {
        	local: this._localField.getValue(),
        	external: this._externalField.getValue(),
        	status: this._status
        }
    },
    
    getRawValue: function()
    {
        return {
            local: this._localField.getRawValue(),
            external: this._externalField.getRawValue(),
            status: this._status
        }
    },    
    
    getSubmitValue: function ()
    {
    	var value = {
        	local: this._localField.getSubmitValue(),
        	external: this._externalField.getSubmitValue(),
        	status: this._status
    	}
    	
        return Ext.encode(value);
    },
    
    setValue: function(value)
    {
    	this.callParent(arguments);
    	
    	var localValue = value,
    		extValue = null,
    		external = false;
    	
        if (Ext.isObject(value) && (value.status || value.local || value.external))
        {
        	localValue = value.local;
        	if (Ext.isObject(localValue) && localValue.value)
            {
                localValue = localValue.value;
            }
        	extValue = value.external;
        	if (Ext.isObject(extValue) && extValue.value)
            {
                extValue = extValue.value;
            }
        	external = value.status == 'external';
        }
        
        this._localField.setValue(localValue);
    	this._externalField.setValue(extValue);
    	this._statusBtn.toggle(external, false);
    	this._switchToExternalStatus(external);
    },  
    
    getErrors: function(value)
    {
        if (this._status == 'external')
        {
            return this._externalField.getErrors.apply(this._externalField, value && value.external ? [value.external] : []);
        }
        else
        {
            return this._localField.getErrors.apply(this._localField, value && value.local ? [value.local] : []);
        }
    },
    
    getActiveWarning: function ()
    {
        // Use template to merge the render of global and specific warnings to external or local field
        // This template is the same that is usually sed in setActiveWarning method
        let activeWarnings = this.getActiveWarnings();
        return Ext.XTemplate.getTpl(this, 'activeWarnsTpl').apply({
            warns: activeWarnings,
            listCls: Ext.plainListCls 
        });
    },
    
    getActiveWarnings: function ()
    {
        let activeWarnings = Ext.Array.clone(this.callParent(arguments));

    	let currentFieldWarnings = this._status == 'external' ? this._externalField.getActiveWarnings() : this._localField.getActiveWarnings();
        Ext.Array.push(activeWarnings, currentFieldWarnings);
        
        return activeWarnings;
    },
    
    enable: function()
    {
        // Enable state has only influence on local and status button (external field is always disabled)
        if (this._status == 'local')
        {
            this._localField.enable();
        }
        this._statusBtn.enable();
        
        this.disabled = false;
    },
    
    disable: function()
    {
        // Disable state has only influence on local and status button (external field is always disabled)
        this._localField.disable();
        this._statusBtn.disable();
        
        this.disabled = true;
    },
    
    isDisabled:function()
    {
        return this.disabled;
    },
    
    setReadOnly: function(readOnly)
    {
        // New status of read-only has only influence on local and status button (external field is always read-only)
        this._localField.setReadOnly(readOnly);
        this._statusBtn.setDisabled(readOnly);
    }
});