/**
 * This plugin allows pivot grid data export using various exporters. Each exporter should extend
 * the {@link Ext.exporter.Base} class.
 *
 * Two new methods are created on the pivot grid by this plugin:
 *
 *  - saveDocumentAs(config): saves the document
 *  - getDocumentData(config): returns the document content
 *
 * Example usage:
 *
 *
 *      {
 *          xtype: 'pivotgrid',
 *          plugins: {
 *              pivotexporter: true
 *          }
 *      }
 *
 *      pivot.saveDocumentAs({
 *          type: 'xlsx',
 *          title: 'My export',
 *          fileName: 'myExport.xlsx'
 *      });
 *
 *
 * When the exported data needs to be formatted then the
 * {@link Ext.pivot.dimension.Item#exportStyle} can be used on either left axis or aggregate
 * dimensions.
 *
 *      {
 *          xtype: 'pivotgrid',
 *
 *          matrix: {
 *              aggregate: [{
 *                  dataIndex:  'value',
 *                  header:     'Total',
 *                  aggregator: 'sum',
 *                  exportStyle: {
 *                      format: 'Currency',
 *                      alignment: {
 *                          horizontal: 'Right'
 *                      }
 *                  }
 *              }],
 *
 *              leftAxis: [{
 *                  dataIndex:  'date',
 *                  header:     'Transaction date',
 *                  exportStyle: {
 *                      format: 'Short Date',
 *                      alignment: {
 *                          horizontal: 'Right'
 *                      }
 *                  }
 *              },{
 *                  dataIndex:  'company',
 *                  header:     'Company',
 *                  sortable:   false
 *              }]
 *          }
 *          // ...
 *      }
 *
 */
Ext.define('Ext.pivot.plugin.Exporter', {
    alternateClassName: [
        'Mz.pivot.plugin.ExcelExport'
    ],

    alias: [
        'plugin.pivotexporter',
        'plugin.mzexcelexport'
    ],

    extend: 'Ext.exporter.Plugin',

    /**
     * @event beforedocumentsave
     * Fires on the pivot grid before a document is exported and saved.
     * @param {Ext.pivot.Grid} pivotGrid Reference to the pivot grid
     * @param {Object} params Additional parameters sent with this event
     * @param {Object} params.config The config object used in the {@link #saveDocumentAs} method
     * @param {Ext.exporter.Base} params.exporter A reference to the exporter object used to save
     * the document
     */

    /**
     * @event documentsave
     * Fires on the pivot grid whenever a document is exported and saved.
     * @param {Ext.pivot.Grid} pivotGrid Reference to the pivot grid
     * @param {Object} params Additional parameters sent with this event
     * @param {Object} params.config The config object used in the {@link #saveDocumentAs} method
     * @param {Ext.exporter.Base} params.exporter A reference to the exporter object used to save
     * the document
     */

    /**
     * @event dataready
     * Fires on the pivot grid when the {Ext.exporter.data.Table data object} is ready.
     * You could adjust styles or data before the document is generated and saved.
     * @param {Ext.pivot.Grid} pivotGrid Reference to the pivot grid
     * @param {Object} params Additional parameters sent with this event
     * @param {Object} params.config The config object used in the {@link #saveDocumentAs} method
     * @param {Ext.exporter.Base} params.exporter A reference to the exporter object used to save
     * the document
     */

    /**
     *  `"both"` (the default) - The plugin is added to both grids
     *  `"top"` - The plugin is added to the containing Panel
     *  `"locked"` - The plugin is added to the locked (left) grid
     *  `"normal"` - The plugin is added to the normal (right) grid
     *
     * @private
     */
    lockableScope: 'top',

    init: function(cmp) {
        var me = this;

        //<debug>
        // this plugin is available only for the pivot grid
        if (!cmp.isPivotGrid) {
            Ext.raise('This plugin is only compatible with Ext.pivot.Grid');
        }
        //</debug>

        return me.callParent([cmp]);
    },

    /**
     * @method prepareData
     * @inheritdoc
     */
    prepareData: function(config) {
        var me = this,
            matrix, group, columns, headers, total, i, j, dLen, tLen,
            dataIndexes, row, value;

        me.matrix = matrix = me.cmp.getMatrix();
        me.onlyExpandedNodes = (config && config.onlyExpandedNodes);

        if (!me.onlyExpandedNodes) {
            me.setColumnsExpanded(matrix.topAxis.getTree(), true);
        }

        columns = Ext.clone(matrix.getColumnHeaders());
        headers = me.getColumnHeaders(columns, config);
        dataIndexes = me.getDataIndexColumns(columns);

        if (!me.onlyExpandedNodes) {
            me.setColumnsExpanded(matrix.topAxis.getTree());
        }

        group = {
            columns: headers,
            groups: []
        };
        me.extractGroups(group, matrix.leftAxis.getTree(), dataIndexes);

        tLen = matrix.totals.length;
        dLen = dataIndexes.length;

        if (tLen) {
            group.summaries = [];

            for (i = 0; i < tLen; i++) {
                total = matrix.totals[i];

                row = {
                    cells: [{
                        value: total.title
                    }]
                };

                for (j = 1; j < dLen; j++) {
                    value = total.record.data[dataIndexes[j].dataIndex];
                    row.cells.push({
                        value: (value == null || value === 0) && matrix.showZeroAsBlank ? '' : value
                    });
                }

                group.summaries.push(row);
            }
        }

        me.matrix = me.onlyExpandedNodes = null;

        return new Ext.exporter.data.Table(group);
    },

    /**
     * If we have to export everything then expand all top axis tree nodes temporarily
     *
     * @param items
     * @param expanded
     *
     * @private
     */
    setColumnsExpanded: function(items, expanded) {
        var i;

        for (i = 0; i < items.length; i++) {
            if (Ext.isDefined(expanded)) {
                items[i].backupExpanded = items[i].expanded;
                items[i].expanded = expanded;
            }
            else {
                items[i].expanded = items[i].backupExpanded;
                items[i].backupExpanded = null;
            }

            if (items[i].children) {
                this.setColumnsExpanded(items[i].children, expanded);
            }
        }
    },

    /**
     * Returns an array of column headers to be used in the export file
     *
     * @param {Array} columns
     * @param {Object} config
     *
     * @return {Array}
     *
     * @private
     */
    getColumnHeaders: function(columns, config) {
        var cols = [],
            length = columns.length,
            i, obj, col, doExtract;

        for (i = 0; i < length; i++) {
            col = columns[i];

            doExtract = this.onlyExpandedNodes
                ? (!col.group || col.group.expanded || (!col.group.expanded && col.subTotal))
                : true;

            if (doExtract) {
                /* eslint-disable multiline-ternary */
                obj = {
                    text: (col.subTotal && col.group &&
                           col.group.expanded ? col.group.getTextTotal() : col.text)
                };
                /* eslint-enable multiline-ternary */

                if (col.columns) {
                    obj.columns = this.getColumnHeaders(col.columns, config);
                }
                else {
                    obj.width = col.dimension ? col.dimension.getWidth() : col.width || 100;
                    obj.style = col.dimension
                        ? this.getExportStyle(col.dimension.getExportStyle(), config)
                        : null;
                }

                cols.push(obj);
            }
        }

        return cols;
    },

    /**
     * Find all columns that have a dataIndex
     *
     * @param columns
     *
     * @returns {Array}
     *
     * @private
     */
    getDataIndexColumns: function(columns) {
        var cols = [],
            i, col, doExtract;

        for (i = 0; i < columns.length; i++) {
            col = columns[i];
            doExtract = this.onlyExpandedNodes
                ? (!col.group || col.group.expanded || (!col.group.expanded && col.subTotal))
                : true;

            if (doExtract) {

                if (col.dataIndex) {
                    cols.push({
                        dataIndex: col.dataIndex,
                        agg: col.dimension ? col.dimension.getId() : null
                    });
                }
                else if (col.columns) {
                    Ext.Array.insert(cols, cols.length, this.getDataIndexColumns(col.columns));
                }
            }
        }

        return cols;
    },

    /**
     * Extract data from left axis groups.
     *
     * @param group
     * @param items
     * @param columns
     *
     * @returns {Object}
     *
     * @private
     */
    extractGroups: function(group, items, columns) {
        var i, j, iLen, cLen, doExtract, item, row,
            subGroup, record, value, cells;

        iLen = items.length;

        for (i = 0; i < iLen; i++) {
            item = items[i];

            if (item.record) {
                group.rows = group.rows || [];

                cells = [];
                row = {
                    cells: cells
                };

                for (j = 0; j < columns.length; j++) {
                    value = item.record.data[columns[j].dataIndex];
                    cells.push({
                        // eslint-disable-next-line max-len
                        value: (value == null || value === 0) && this.matrix.showZeroAsBlank ? '' : value
                    });
                }

                group.rows.push(row);
            }
            else if (item.children) {
                group.groups = group.groups || [];

                subGroup = {
                    text: item.name
                };
                doExtract = this.onlyExpandedNodes ? item.expanded : true;

                if (doExtract) {
                    this.extractGroups(subGroup, item.children, columns);
                }

                subGroup.summaries = [];
                cells = [{
                    value: (doExtract ? item.getTextTotal() : item.value)
                }];
                row = {
                    cells: cells
                };

                record = (item.expanded ? item.records.expanded : item.records.collapsed);
                cLen = columns.length;

                for (j = 1; j < cLen; j++) {
                    value = record.data[columns[j].dataIndex];
                    cells.push({
                        // eslint-disable-next-line max-len
                        value: (value == null || value === 0) && this.matrix.showZeroAsBlank ? '' : value
                    });
                }

                subGroup.summaries.push(row);
                group.groups.push(subGroup);
            }
        }
    }
});
