/**
 * The `editable` plugin enables form-based, grid row editing. Editing begins by double-tapping
 * a row. This can be set to any event, which we'll discuss below. The editor consists of a form
 * positioned on the right side of the viewport.
 *
 * There is a button to save or cancel all changes for the edit in the toolbar, and the
 * row is deletable by default.
 *
 * The default editable grid can be defined like so:
 *
 *     @example
 *     Ext.create({
 *         xtype: 'grid',
 *         fullscreen: true,
 *         plugins: {
 *             grideditable: true
 *         },
 *         store: {
 *             fields: [],
 *             data: [{
 *                 name: 'Jake'
 *             }, {
 *                 name: 'Finn'
 *             }]
 *         },
 *         columns: [{
 *             text: 'Name',
 *             dataIndex: 'name',
 *             flex: 1,
 *             editable: true
 *         }]
 *     });
 *
 * By opening up the plugins type as an object (or an array of objects), you can modify your
 * editor more significantly.  You can see the changeable bits below:
 *
 *     @example
 *     Ext.create({
 *         xtype: 'grid',
 *         fullscreen: true,
 *         plugins: {
 *             grideditable: {
 *                 triggerEvent: 'childdoubletap',
 *                 enableDeleteButton: true,
 *                 formConfig: null, // See more below
 *
 *                 defaultFormConfig: {
 *                     xtype: 'formpanel',
 *                     scrollable: true,
 *                     items: [{
 *                         xtype: 'fieldset'
 *                     }]
 *                 },
 *
 *                 toolbarConfig: {
 *                     xtype: 'titlebar',
 *                     docked: 'top',
 *                     items: [{
 *                         xtype: 'button',
 *                         ui: 'decline',
 *                         text: 'Cancel',
 *                         align: 'left',
 *                         action: 'cancel'
 *                     }, {
 *                         xtype: 'button',
 *                         ui: 'confirm',
 *                         text: 'Submit',
 *                         align: 'right',
 *                         action: 'submit'
 *                     }]
 *                 },
 *             }
 *         },
 *         store: {
 *             fields: [],
 *             data: [{
 *                 name: 'Jake'
 *             }, {
 *                 name: 'Finn'
 *             }]
 *         },
 *         columns: [{
 *             text: 'Name',
 *             dataIndex: 'name',
 *             flex: 1,
 *             editable: true
 *         }]
 *     });
 *
 *  As you can see, you can easily modify nearly every bit of the editor window.  As mentioned
 *  above, the toolbar and delete button are the only components included by default.  That's
 *  where formConfig comes into play.
 *
 *  By adding formConfig, you can hardcode the form that gets created when editing a row.
 *  There are no fields set on the form initially, so you will need to define them
 *  yourself.  For example, if you had a "name" column, and you wanted it to be editable,
 *  you would do something like this in your plugins object:
 *
 *     formConfig: {
 *        items: [{
 *           xtype: 'textfield',
 *           name: 'name',
 *           label: 'Name'
 *        }]
 *     }
 *
 *  Now, upon opening the editor, you would see a textfield populated with the editable value from
 *  its corresponding record.
 *
 *  If you want to alter certain form configurations, but still have the default editor fields applied, use
 *  the defaultFormConfig instead.
 */
Ext.define('Ext.grid.plugin.Editable', {
    extend: 'Ext.plugin.Abstract',
    alias: 'plugin.grideditable',
 
    requires: [
        'Ext.form.FieldSet',
        'Ext.form.Panel',
        'Ext.Sheet',
        'Ext.TitleBar'
    ],
 
    config: {
        /**
         * @private
         */
        grid: null,
 
        /**
         * @cfg {String} triggerEvent
         * The event used to trigger the showing of the editor form. This event should
         * be an event that is fired by the grid.
         */
        triggerEvent: 'childdoubletap',
 
        /**
         * @cfg {Object} formConfig
         * By changing the formConfig you can hardcode the form that gets created when editing a row.
         * Note that the fields are not set on this form, so you will have to define them yourself in this config.
         * If you want to alter certain form configurations, but still have the default editor fields applied, use
         * the defaultFormConfig instead.
         */
        formConfig: null,
 
        /**
         * @cfg {Object} defaultFormConfig
         * Configures the default form appended to the editable panel.
         */
        defaultFormConfig: {
            xtype: 'formpanel',
            scrollable: true,
            items: [{
                xtype: 'fieldset'
            }]
        },
 
        /**
         * @cfg {Object} toolbarConfig
         * Configures the toolbar appended to the editable panel.
         */
        toolbarConfig: {
            xtype: 'titlebar',
            docked: 'top',
            items: [{
                xtype: 'button',
                ui: 'alt',
                text: 'Cancel',
                align: 'left',
                action: 'cancel'
            }, {
                xtype: 'button',
                ui: 'alt',
                text: 'Submit',
                align: 'right',
                action: 'submit'
            }]
        },
 
        /**
         * @cfg {Boolean} enableDeleteButton
         * Creates a delete button, which allows the user to delete the selected row.
         */
        enableDeleteButton: true
    },
 
    init: function(grid) {
        this.setGrid(grid);
 
        grid.setTouchAction({
            doubleTapZoom: false
        });
    },
 
    destroy: function() {
        this.cleanup();
        this.callParent();
    },
 
    updateGrid: function(grid, oldGrid) {
        var triggerEvent = this.getTriggerEvent();
        if (oldGrid) {
            oldGrid.un(triggerEvent, 'onTrigger', this);
        }
 
        if (grid) {
            grid.on(triggerEvent, 'onTrigger', this);
        }
    },
 
    onCancelTap: function() {
        this.sheet.hide();
    },
 
    onSubmitTap: function() {
        this.form.getRecord().set(this.form.getValues());
        this.sheet.hide();
    },
 
    onSheetHide: function() {
        this.cleanup();
    },
 
    getEditorFields: function(columns) {
        var fields = [],
            ln = columns.length,
            // <debug>
            map = {},
            // </debug>
            i, column, editor, editable, cfg;
 
        for (= 0; i < ln; i++) {
            column = columns[i];
            editable = column.getEditable();
            editor = editable !== false && column.getEditor();
 
            if (!editor && editable) {
                cfg = column.getDefaultEditor();
                editor = Ext.create(cfg);
                column.setEditor(editor);
            }
 
            if (editor) {
                // <debug>
                if (map[column.getDataIndex()]) {
                    Ext.raise('An editable column with the same dataIndex "' + 
                        column.getDataIndex() + '" already exists.');
                }
                map[column.getDataIndex()] = true;
                // </debug>
 
                if (editor.isEditor) {
                    editor = editor.getField();
                }
                editor.setLabel(column.getText());
                editor.setName(column.getDataIndex());
                fields.push(editor);
            }
        }
 
        return fields;
    },
 
    onTrigger: function(grid, location) {
        var me = this,
            record = location.record,
            formConfig = me.getFormConfig(),
            toolbarConfig = me.getToolbarConfig(),
            fields, form, sheet, toolbar;
 
        // Don't want to react to grid headers etc
        if (!record || !location.row) {
            return;
        }
 
        if (formConfig) {
            me.form = form = Ext.factory(formConfig, Ext.form.Panel);
        } else {
            me.form = form = Ext.factory(me.getDefaultFormConfig());
 
            fields = me.getEditorFields(grid.getColumns());
            form.down('fieldset').setItems(fields);
            form.clearFields = true;
        }
 
        toolbar = Ext.factory(toolbarConfig, Ext.form.TitleBar);
        me.submitButton = toolbar.down('button[action=submit]');
        toolbar.down('button[action=cancel]').on('tap', 'onCancelTap', me);
        me.submitButton.on('tap', 'onSubmitTap', me);
 
        // We sync the enabled state of the submit button with form validity
        form.on({
            change: 'onFieldChange',
            delegate: 'field',
            scope: me
        });
 
        form.setRecord(record);
 
        me.sheet = sheet = grid.add({
            xtype: 'sheet',
            items: [toolbar, form],
            hideOnMaskTap: true,
            enter: 'right',
            exit: 'right',
            right: 0,
            width: 320,
            layout: 'fit',
            stretchY: true,
            hidden: true
        });
 
        if (me.getEnableDeleteButton()) {
            form.add({
                xtype: 'button',
                text: 'Delete',
                ui: 'decline',
                margin: 10,
                handler: function() {
                    grid.getStore().remove(record);
                    sheet.hide();
                }
            });
        }
 
        sheet.on('hide', 'onSheetHide', me);
 
        sheet.show();
    },
 
    privates: {
        onFieldChange: function() {
            this.submitButton.setDisabled(!this.form.isValid());
        },
 
        cleanup: function() {
            var me = this,
                form = me.form;
 
            if (form && !form.destroyed && form.clearFields) {
                form.removeAll(false);
            }
 
            me.form = me.sheet = Ext.destroy(me.sheet);
        }
    }
});