/**
 * This class implements the data structure required by an exporter.
 */
Ext.define('Ext.exporter.data.Table', {
    extend: 'Ext.exporter.data.Base',
 
    requires: [
        'Ext.exporter.data.Column',
        'Ext.exporter.data.Group'
    ],
 
    isDataTable: true,
 
    config: {
        /**
         * @cfg {Ext.exporter.data.Column[]} columns
         *
         * Collection of columns that need to be exported.
         *
         */
        columns: null,
        /**
         * @cfg {Ext.exporter.data.Group[]} groups
         *
         * Collection of groups that need to be exported.
         *
         */
        groups: null
    },
 
    autoGenerateId: false,
 
    applyColumns: function(data, dataCollection){
        return this.checkCollection(data, dataCollection, 'Ext.exporter.data.Column');
    },
 
    updateColumns: function(collection, oldCollection){
        var me = this;
 
        if(oldCollection){
            oldCollection.un({
                add: me.onColumnAdd,
                remove: me.onColumnRemove,
                scope: me
            });
            Ext.destroy(oldCollection.items, oldCollection);
        }
        if(collection){
            collection.on({
                add: me.onColumnAdd,
                remove: me.onColumnRemove,
                scope: me
            });
            me.onColumnAdd(collection, {items: collection.getRange()});
            me.syncColumns();
        }
    },
 
    syncColumns: function(){
        var cols = this.getColumns(),
            depth = this.getColDepth(cols, -1),
            result = {},
            i, j, length, len, keys, arr, prevCol, index;
 
        if(!cols){
            return;
        }
        length = cols.length;
        for(= 0; i < length; i++){
            cols.items[i].sync(depth);
        }
        this.getColumnLevels(cols, depth, result);
        keys = Ext.Object.getKeys(result);
        length = keys.length;
 
        for(= 0; i < length; i++){
            arr = result[keys[i]];
            len = arr.length;
            for(= 0; j < len; j++){
                if(=== 0){
                    index = 1;
                }else if(arr[- 1]) {
                    prevCol = arr[- 1].getConfig();
                    index += (prevCol.mergeAcross ? prevCol.mergeAcross + 1 : 1);
                }else{
                    index++;
                }
                if(arr[j]){
                    arr[j].setIndex(index);
                }
            }
        }
    },
 
    getLeveledColumns: function(){
        var cols = this.getColumns(),
            depth = this.getColDepth(cols, -1),
            result = {};
 
        this.getColumnLevels(cols, depth, result, true);
        return result;
    },
 
    /**
     * Fetch all bottom columns from the `columns` hierarchy.
     *
     * @return {Ext.exporter.data.Column[]}
     */
    getBottomColumns: function(){
        var result = this.getLeveledColumns(),
            keys, len;
 
        keys = Ext.Object.getKeys(result);
        len = keys.length;
 
        return len ? result[keys[keys.length - 1]] : [];
    },
 
    getColumnLevels: function(columns, depth, result, topDown){
        var col, i, j, len, name, level, cols;
 
        if (!columns) {
            return;
        }
 
        len = columns.length;
        for (= 0; i < len; i++) {
            col = columns.items[i];
            level = col.getLevel();
            cols = col.getColumns();
 
            name = 's' + level;
            result[name] = result[name] || [];
            result[name].push(col);
 
            if(!cols) {
                for (= level + 1; j <= depth; j++) {
                    name = 's' + j;
                    result[name] = result[name] || [];
                    result[name].push(topDown ? col : null);
                }
            }else{
                this.getColumnLevels(cols, depth, result, topDown);
            }
        }
    },
 
    onColumnAdd: function(collection, details){
        var items = details.items,
            length = items.length,
            i, item;
 
        for(= 0; i < length; i++) {
            item = items[i];
            item.setTable(this);
        }
        this.syncColumns();
    },
 
    onColumnRemove: function(collection, details){
        Ext.destroy(details.items);
        this.syncColumns();
    },
 
    getColumnCount: function(){
        var cols = this._columns,
            s = 0,
            i, length;
 
        if(cols) {
            length = cols.length;
            for (= 0; i < length; i++) {
                s += cols.items[i].getColumnCount();
            }
        }
        return s;
    },
 
    getColDepth: function(columns, level){
        var m = 0,
            len;
 
        if (!columns) {
            return level;
        }
 
        len = columns.length;
        for (var i = 0; i < len; i++) {
            m = Math.max(m, this.getColDepth(columns.items[i]._columns, level + 1));
        }
 
        return m;
    },
 
    /**
     * Convenience method to add columns.
     * @param {Object/Array} config
     * @return {Ext.exporter.data.Column/Ext.exporter.data.Column[]}
     */
    addColumn: function(config){
        if(!this._columns){
            this.setColumns([]);
        }
        return this._columns.add(config || {});
    },
 
    /**
     * Convenience method to fetch a column by its id.
     * @param id
     * @return {Ext.exporter.data.Column}
     */
    getColumn: function(id){
        return this._columns ? this._columns.get(id) : null;
    },
 
    applyGroups: function(data, dataCollection){
        return this.checkCollection(data, dataCollection, 'Ext.exporter.data.Group');
    },
 
    /**
     * Convenience method to add groups.
     * @param {Object/Array} config
     * @return {Ext.exporter.data.Group/Ext.exporter.data.Group[]}
     */
    addGroup: function(config){
        if(!this._groups){
            this.setGroups([]);
        }
        return this._groups.add(config || {});
    },
 
    /**
     * Convenience method to fetch a group by its id.
     * @param id
     * @return {Ext.exporter.data.Group}
     */
    getGroup: function(id){
        return this._groups ? this._groups.get(id) : null;
    }
});