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

/**
 * Abstract class to create a field that does not extend {@link Ext.form.field.Base} (field not representing by an input field).<br/>
 * You have to implement {@link #setValue} and  {@link #getSubmitValue}
 * To handle global errors override {@link #getErrors}.<br/>
 */
Ext.define('Ametys.form.AbstractField', {
    extend:'Ext.form.FieldContainer',
    
    mixins: {field: 'Ext.form.field.Field'},
    
    statics: {
        /**
         * @cfg {String} READABLE_TEXT_CLS
         * The CSS class to use for a component/container/panel used to display a readable value.
         */
        READABLE_TEXT_CLS: 'a-form-readable-text',
        /**
         * @private
         * @cfg {String} BASE_FIELD_CLS
         * The CSS class to use when the field contains no visible input field but a readable text.
         */
        BASE_FIELD_CLS: 'a-form-abstract-field'
    },
    
    /**
     * @cfg {String} invalidCls
     * The CSS class to use when marking the component invalid.
     */
    invalidCls : Ext.baseCSSPrefix + 'form-invalid',
    
    /**
     * @cfg {String} warningCls
     * The CSS class to use when marking the component warning.
     */
    warningCls : Ext.baseCSSPrefix + 'form-warning',
    
    /**
     * @cfg {String} dirtyCls
     * The CSS class to use when the field value {@link #isDirty is dirty}.
     */
    dirtyCls : Ext.baseCSSPrefix + 'form-dirty',
    
    /**
     * @cfg {String} emptyCls
     * The CSS class to use when the value if the field is empty.
     */
    emptyCls: Ext.baseCSSPrefix + 'form-empty',
    
    /**
     * @property {String} fieldFocusCls The CSS to apply to root element when field is focused 
     * @private
     */
    fieldFocusCls: "x-field-focus",
    
    /**
     * @cfg {String} blankText
     * The error text to display if the **{@link #allowBlank}** validation fails
     */
    blankText : "{{i18n PLUGINS_CORE_UI_DEFAULT_VALIDATOR_MANDATORY}}",
    
    /**
     *  @cfg {Boolean} [allowBlank=true]. Specify false to validate that the value is not empty
     */
    allowBlank: true,
    
    /**
     *  @cfg {Boolean} [preventMark=false] true to disable displaying any error message set on this object.
     */
    preventMark: false,
    
    /**
     *  @cfg {Boolean} [focusable=true] true to allow this field to be focused
     */
    focusable: true,
    
    /**
     * @cfg {Number} [tabIndex=0] DOM tabIndex attribute for the focused element
     */
    tabIndex: 0,
    
    /**
     * @cfg {Boolean} [validateOnBlur=true] Should validation be done on blur 
     */
    validateOnBlur: true,
    
    /**
     * @property {Boolean} triggerDialogBoxOpened If this field trigger the opening of a dialog box, this boolean should be set to 'true' as soon as the dialog is opened and set to 'false' as soon as the dialog is closed.<br/>
     * Be careful to set this boolean to 'false' when the dialog box is cancelled.<br/>
     * Always focus the field after closing dialog box.<br/>
     * It is necessary to handle this boolean correctly to edit the field in a edition grid.
     */
    triggerDialogBoxOpened: false,
    
    constructor: function(config)
    {
        config = config || {};
        config.cls = Ext.Array.from(config.cls);
        config.cls.push("x-field");
        config.cls.push(this.getDefaultCls());
        
        config.listeners = config.listeners || {};
        config.listeners.blur = {
            fn: this._onBlur,
            scope: this
        }
        
        this.callParent(arguments);
    },
    
    /**
     * @protected
     * Get the name of a CSS class that will be added during creation to the field
     * @return {String} A CSS classname
     */
    getDefaultCls: function()
    {
        return Ametys.form.AbstractField.BASE_FIELD_CLS;
    },
    
    initComponent: function ()
    {
        this.callParent();
        this.initField();
    },
    
    /**
     * @protected
     * On blur listener.
     * Currently used to validate the field
     */
    _onBlur: function()
    {
        if (this.validateOnBlur)
        {
            this.validate();
        }
    },
    
    /**
     * Called when the field's dirty state changes. Adds/removes the dirtyCls on the main element.
     * @param {Boolean} isDirty The new dirty state
     */
    onDirtyChange: function(isDirty) 
    {
        this[isDirty ? 'addCls' : 'removeCls'](this.dirtyCls);
    },
    
    /**
     * Overrides the method from the Ext.form.Labelable mixin to also add the invalidCls to the inputEl,
     * as that is required for proper styling in IE with nested fields (due to lack of child selector)
     */
    renderActiveError: function() 
    {
        var me = this,
            hasError = me.hasActiveError();
        if (me.el) {
            // Add/remove invalid class
            me.el[hasError ? 'addCls' : 'removeCls'](me.invalidCls + '-field');
        }
        me.mixins.labelable.renderActiveError.call(me);
    },
    
    isValid: function() 
    {
        var me = this,
        disabled = me.disabled,
        validate = me.forceValidation || !disabled;
        
        return validate ? me.validateValue(me.getRawValue()) : disabled;
    },
    
    /**
     * @protected
     * Returns the raw value of the field, without performing any normalization, conversion, or validation. To get a normalized and converted value see getValue.
     * @return {Object} the raw value
     */
    getRawValue: function()
    {
        return this.getValue();
    },
    
    setValue: function (value) 
    {   
        this.mixins.field.setValue.apply(this, arguments);
        
        if (Ext.isEmpty(value) || (Ext.isObject(value) && Ext.Object.isEmpty(value)))
        {
            this.addCls(this.emptyCls);
        }
        else
        {
            this.removeCls(this.emptyCls)
        }
    },
    
    /**
     * @private
     * Private override to use getSubmitValue() as a convenience
     */
    getSubmitData: function() 
    {
        var me = this,
            data = null,
            val;
        if ((!me.disabled || me.submitDisabledValue) && me.submitValue) 
        {
            val = me.getSubmitValue();
            if (val !== null) {
                data = {};
                data[me.getName()] = val;
            }
        }
        return data;
    },
    
    /**
     * Returns the value that would be included in a standard form submit for this field. This will be combined with the
     * field's name to form a name=value pair in the {@link #getSubmitData submitted parameters}. If an empty string is
     * returned then just the name= will be submitted; if null is returned then nothing will be submitted.
     *
     * @return {String} The value to be submitted, or null.
     */
    getSubmitValue: function ()
    {
    	return this.getValue();
    },
    
    /**
     * <strong>Override this method to add specific validation.</strong><br/>
     * <strong>Validation on empty field are already made.</strong><br/>
     * Inherited documentation:<br/>
     * @inheritdoc
     * @method getErrors
     */
    getErrors: function (value)
    {
        value = value || this.getValue();
        
        var errors = [];
        
        if (!this.allowBlank && (!value || (Ext.isArray(value) && value.length == 0)))
        {
            errors.push(this.blankText);
        }
        
        return errors;
    },
    
    /**
     * Uses {@link #getErrors} to build an array of validation errors. If any errors are found, they are passed to
     * {@link #markInvalid} and false is returned, otherwise true is returned.
     *
     * Previously, subclasses were invited to provide an implementation of this to process validations - from 3.2
     * onwards {@link #getErrors} should be overridden instead.
     *
     * @param {Object} value The value to validate
     * @return {Boolean} True if all validations passed, false if one or more failed
     * @private
     */
    validateValue: function(value) 
    {
        var me = this,
            errors = me.getErrors(value),
            isValid = Ext.isEmpty(errors);
        
        if (!me.preventMark) 
        {
            if (isValid) 
            {
                me.clearInvalid();
            } else 
            {
                me.markInvalid(errors);
            }
        }

        return isValid;
    },
    
    markInvalid: function(errors) 
    {
        // Save the message and fire the 'invalid' event
        var me = this,
            oldMsg = me.getActiveError();
        
        if (me.hasActiveWarning())
        {
            // Hide active warning message(s) if exist
            me.hideActiveWarning();
        }
        
        me.setActiveErrors(Ext.Array.from(errors));
        if (oldMsg !== me.getActiveError()) 
        {
            me.updateLayout();
        }
    },
    
    clearInvalid: function() 
    {
        // Clear the message and fire the 'valid' event
        var me = this,
            hadError = me.hasActiveError();
        
        me.unsetActiveError();
        if (hadError) 
        {
            me.updateLayout();
        }
        
        if (me.hasActiveWarning())
        {
            // Display active warning message(s) if exist
            me.renderActiveWarning();
        }
    },
    
    markWarning: function (warns)
    {
        this.setActiveWarnings(Ext.Array.from(warns));
        
        if (this.hasActiveError())
        {
            // Hide active warning message(s)
            this.hideActiveWarning();
        }
        
        this.updateLayout();
    },
    
    clearWarning: function() 
    {
        this.unsetActiveWarnings();
        this.updateLayout();
    },
    
    getFocusEl: function()
    {
    	var focusEl = this.element || this.el;
    	Ext.Array.each(this.items.items, function(item) {
    		if (item != null && item.focusable && Ext.isFunction(item.focus) 
				&& (!Ext.isFunction(item.isVisible) || item.isVisible()) 
    			&& (!Ext.isFunction(item.isDisabled) || !item.isDisabled()))
			{
    			focusEl = item;
    			return false;
			}
    	});
    	
    	return focusEl;
    },

    /**
     * @inheritdoc
     * @param {Ext.event.Event} e The event
     */
    onFocus: function(e) 
    {
        var me = this;

        me.callParent(arguments);

        me.addCls(me.fieldFocusCls);
    },

    /**
     * @inheritdoc
     * @param {Ext.event.Event} e The event
     */
    onBlur: function(e) 
    {
        var me = this;

        me.callParent(arguments);

        me.removeCls(me.fieldFocusCls);    
    }
});