/**
 * A mixin for {@link Ext.container.Container} components that are likely to have form fields in their
 * items subtree. Adds the following capabilities:
 *
 * - Methods for handling the addition and removal of {@link Ext.form.Labelable} and {@link Ext.form.field.Field}
 *   instances at any depth within the container.
 * - Events ({@link #fieldvaliditychange} and {@link #fielderrorchange}) for handling changes to the state
 *   of individual fields at the container level.
 * - Automatic application of {@link #fieldDefaults} config properties to each field added within the
 *   container, to facilitate uniform configuration of all fields.
 *
 * This mixin is primarily for internal use by {@link Ext.form.Panel} and {@link Ext.form.FieldContainer},
 * and should not normally need to be used directly. @docauthor Jason Johnston <[email protected]>
 */
Ext.define('Ext.form.FieldAncestor', {

    /**
     * @cfg {Object} fieldDefaults
     * If specified, the properties in this object are used as default config values for each {@link Ext.form.Labelable}
     * instance (e.g. {@link Ext.form.field.Base} or {@link Ext.form.FieldContainer}) that is added as a descendant of
     * this container. Corresponding values specified in an individual field's own configuration, or from the {@link
     * Ext.container.Container#defaults defaults config} of its parent container, will take precedence. See the
     * documentation for {@link Ext.form.Labelable} to see what config options may be specified in the fieldDefaults.
     *
     * Example:
     *
     *     new Ext.form.Panel({
     *         fieldDefaults: {
     *             labelAlign: 'left',
     *             labelWidth: 100
     *         },
     *         items: [{
     *             xtype: 'fieldset',
     *             defaults: {
     *                 labelAlign: 'top'
     *             },
     *             items: [{
     *                 name: 'field1'
     *             }, {
     *                 name: 'field2'
     *             }]
     *         }, {
     *             xtype: 'fieldset',
     *             items: [{
     *                 name: 'field3',
     *                 labelWidth: 150
     *             }, {
     *                 name: 'field4'
     *             }]
     *         }]
     *     });
     *
     * In this example, field1 and field2 will get labelAlign:'top' (from the fieldset's defaults) and labelWidth:100
     * (from fieldDefaults), field3 and field4 will both get labelAlign:'left' (from fieldDefaults and field3 will use
     * the labelWidth:150 from its own config.
     */


    /**
     * Initializes the FieldAncestor's state; this must be called from the initComponent method of any components
     * importing this mixin.
     * @protected
     */
    initFieldAncestor: function() {
        var me = this,
            onSubtreeChange = me.onFieldAncestorSubtreeChange;

        me.addEvents(
            /**
             * @event fieldvaliditychange
             * Fires when the validity state of any one of the {@link Ext.form.field.Field} instances within this
             * container changes.
             * @param {Ext.form.FieldAncestor} this
             * @param {Ext.form.Labelable} The Field instance whose validity changed
             * @param {String} isValid The field's new validity state
             */
            'fieldvaliditychange',

            /**
             * @event fielderrorchange
             * Fires when the active error message is changed for any one of the {@link Ext.form.Labelable} instances
             * within this container.
             * @param {Ext.form.FieldAncestor} this
             * @param {Ext.form.Labelable} The Labelable instance whose active error was changed
             * @param {String} error The active error message
             */
            'fielderrorchange'
        );

        // Catch addition and removal of descendant fields
        me.on('add', onSubtreeChange, me);
        me.on('remove', onSubtreeChange, me);

        me.initFieldDefaults();
    },

    /**
     * @private Initialize the {@link #fieldDefaults} object
     */
    initFieldDefaults: function() {
        if (!this.fieldDefaults) {
            this.fieldDefaults = {};
        }
    },

    /**
     * @private
     * Handle the addition and removal of components in the FieldAncestor component's child tree.
     */
    onFieldAncestorSubtreeChange: function(parent, child) {
        var me = this,
            isAdding = !!child.ownerCt;

        function handleCmp(cmp) {
            var isLabelable = cmp.isFieldLabelable,
                isField = cmp.isFormField;
            if (isLabelable || isField) {
                if (isLabelable) {
                    me['onLabelable' + (isAdding ? 'Added' : 'Removed')](cmp);
                }
                if (isField) {
                    me['onField' + (isAdding ? 'Added' : 'Removed')](cmp);
                }
            }
            else if (cmp.isContainer) {
                Ext.Array.forEach(cmp.getRefItems(), handleCmp);
            }
        }
        handleCmp(child);
    },

    /**
     * Called when a {@link Ext.form.Labelable} instance is added to the container's subtree.
     * @param {Ext.form.Labelable} labelable The instance that was added
     * @protected
     */
    onLabelableAdded: function(labelable) {
        var me = this;

        // buffer slightly to avoid excessive firing while sub-fields are changing en masse
        me.mon(labelable, 'errorchange', me.handleFieldErrorChange, me, {buffer: 10});

        labelable.setFieldDefaults(me.fieldDefaults);
    },

    /**
     * Called when a {@link Ext.form.field.Field} instance is added to the container's subtree.
     * @param {Ext.form.field.Field} field The field which was added
     * @protected
     */
    onFieldAdded: function(field) {
        var me = this;
        me.mon(field, 'validitychange', me.handleFieldValidityChange, me);
    },

    /**
     * Called when a {@link Ext.form.Labelable} instance is removed from the container's subtree.
     * @param {Ext.form.Labelable} labelable The instance that was removed
     * @protected
     */
    onLabelableRemoved: function(labelable) {
        var me = this;
        me.mun(labelable, 'errorchange', me.handleFieldErrorChange, me);
    },

    /**
     * Called when a {@link Ext.form.field.Field} instance is removed from the container's subtree.
     * @param {Ext.form.field.Field} field The field which was removed
     * @protected
     */
    onFieldRemoved: function(field) {
        var me = this;
        me.mun(field, 'validitychange', me.handleFieldValidityChange, me);
    },

    /**
     * @private Handle validitychange events on sub-fields; invoke the aggregated event and method
     */
    handleFieldValidityChange: function(field, isValid) {
        var me = this;
        me.fireEvent('fieldvaliditychange', me, field, isValid);
        me.onFieldValidityChange(field, isValid);
    },

    /**
     * @private Handle errorchange events on sub-fields; invoke the aggregated event and method
     */
    handleFieldErrorChange: function(labelable, activeError) {
        var me = this;
        me.fireEvent('fielderrorchange', me, labelable, activeError);
        me.onFieldErrorChange(labelable, activeError);
    },

    /**
     * Fired when the validity of any field within the container changes.
     * @param {Ext.form.field.Field} field The sub-field whose validity changed
     * @param {Boolean} valid The new validity state
     * @protected
     */
    onFieldValidityChange: Ext.emptyFn,

    /**
     * Fired when the error message of any field within the container changes.
     * @param {Ext.form.Labelable} field The sub-field whose active error changed
     * @param {String} error The new active error message
     * @protected
     */
    onFieldErrorChange: Ext.emptyFn

});