/**
 * A class which encapsulates a range of columns defining a selection in a grid.
 *
 */
Ext.define('Ext.grid.selection.Columns', {
    extend: 'Ext.dataview.selection.Selection',
    alias: 'selection.columns',
 
    /**
     * @property {Boolean} isColumns 
     * This property indicates the this selection represents selected columns.
     * @readonly
     */
    isColumns: true,
 
    //------------------------------------------------------------------------- 
    // Base Selection API 
 
    clone: function() {
        var me = this,
            result = new me.self(me.view),
            columns = me.selectedColumns;
 
        if (columns) {
            result.selectedColumns = Ext.Array.slice(columns);
        }
        return result;
    },
 
    eachRow: function (fn, scope) {
        var columns = this.selectedColumns;
 
        if (columns && columns.length) {
            this.view.getStore().each(fn, scope || this);
        }
    },
 
    eachColumn: function (fn, scope) {
        var me = this,
            view = me.view,
            columns = me.selectedColumns,
            len,
            i;
 
        if (columns) {
            len = columns.length;
            for (= 0; i < len; i++) {
                if (fn.call(scope || me, columns[i], i) === false) {
                    return false;
                }
            }
        }
    },
 
    eachCell: function (fn, scope) {
        var me = this,
            view = me.view,
            columns = me.selectedColumns,
            len,
            i,
            context = new Ext.grid.Location(view);
 
        if (columns) {
            len = columns.length;
 
            // Use Store#each instead of copying the entire dataset into an array and iterating that. 
            view.getStore().each(function(record) {
                context = context.clone({record: record});
                for (= 0; i < len; i++) {
                    context = context.clone({column: columns[i]});
                    if (fn.call(scope || me, context, context.columnIndex, context.recordIndex) === false) {
                        return false;
                    }
                }
            });
        }
    },
 
    //------------------------------------------------------------------------- 
    // Methods unique to this type of Selection 
 
    /**
     * Returns `true` if the passed {@link Ext.grid.column.Column column} is selected.
     * @param {Ext.grid.column.Column} column The column to test.
     * @return {Boolean} `true` if the passed {@link Ext.grid.column.Column column} is selected.
     */
    isSelected: function(column) {
        var selectedColumns = this.selectedColumns;
 
        if (column && column.isGridColumn && selectedColumns && selectedColumns.length) {
            return Ext.Array.contains(selectedColumns, column);
        }
 
        return false;
    },
 
    /**
     * Returns the number of columns selected.
     * @return {Number} The number of columns selected.
     */
    getCount: function() {
        var selectedColumns = this.selectedColumns;
        return selectedColumns ? selectedColumns.length : 0;
    },
 
    /**
     * Returns the columns selected.
     * @return {Ext.grid.column.Column[]} The columns selected.
     */
    getColumns: function() {
        return this.selectedColumns || [];
    },
 
    //------------------------------------------------------------------------- 
 
    privates: {
        /**
         * Adds the passed Column to the selection.
         * @param {Ext.grid.column.Column} column
         * @private
         */
        add: function(column, suppressEvent) {
            //<debug> 
            if (!column.isGridColumn) {
                Ext.raise('Column selection must be passed a grid Column header object');
            }
            //</debug> 
 
            var me = this,
                selModel = me.getSelectionModel();
 
            Ext.Array.include((me.selectedColumns || (me.selectedColumns = [])), column);
            me.refreshColumns(column);
            selModel.updateHeaderState();
            if (!suppressEvent) {
                selModel.fireSelectionChange();
            }
        },
 
        /**
         * @private
         */
        clear: function(suppressEvent) {
            var me = this,
                selModel = me.getSelectionModel(),
                prevSelection = me.selectedColumns;
 
            if (prevSelection && prevSelection.length) {
                me.selectedColumns = [];
                me.refreshColumns.apply(me, prevSelection);
                selModel.updateHeaderState();
                if (!suppressEvent) {
                    selModel.fireSelectionChange();
                }
            }
        },
 
        setRangeStart: function(startColumn) {
            var me = this,
                prevSelection = me.getColumns();
 
            me.startColumn = startColumn;
            me.selectedColumns = [startColumn];
            prevSelection.push(startColumn);
            me.refreshColumns.apply(me, prevSelection);
        },
 
        setRangeEnd: function(endColumn) {
            var me = this,
                prevSelection = me.getColumns(),
                headerCt = this.view.ownerGrid.getHeaderContainer(),
                columns = headerCt.getVisibleColumns(),
                start = headerCt.indexOfLeaf(me.startColumn),
                end = headerCt.indexOfLeaf(endColumn),
                i;
 
            // Allow looping through columns 
            if (end < start) {
                i = start;
                start = end;
                end = i;
            }
 
            me.selectedColumns = [];
            for (= start; i <= end; i++) {
                me.selectedColumns.push(columns[i]);
                prevSelection.push(columns[i]);
            }
            me.refreshColumns.apply(me, prevSelection);
        },
 
        /**
         * @return {Boolean}
         * @private
         */
        isAllSelected: function() {
            var selectedColumns = this.selectedColumns;
 
            // All selected means all columns, across both views if we are in a locking assembly. 
            return selectedColumns && selectedColumns.length === this.view.getHeaderContainer().getVisibleColumns().length;
        },
 
        /**
         * @private
         */
        refreshColumns: function(column) {
            var me = this,
                view = me.view,
                store = view.store,
                renderInfo = view.renderInfo,
                columns = arguments,
                len = columns.length,
                selected = [],
                location, rowIdx, colIdx;
 
            if (view.rendered) {
                for (colIdx = 0; colIdx < len; colIdx++) {
                    selected[colIdx] = me.isSelected(columns[colIdx]);
                }
 
                for (rowIdx = renderInfo.indexTop; rowIdx < renderInfo.indexBottom; rowIdx++) {
                    location = new Ext.grid.Location(view, store.getAt(rowIdx));
                    for (colIdx = 0; colIdx < len; colIdx++) {
                        // Note colIdx is not the column's visible index. setColumn must be passed the column object 
                        location = location.cloneForColumn(columns[colIdx]);
                        if (selected[colIdx]) {
                            view.onCellSelect(location);
                        } else {
                            view.onCellDeselect(location);
                        }
                    }
                }
            }
        },
 
        /**
         * Removes the passed Column from the selection.
         * @param {Ext.grid.column.Column} column
         * @private
         */
        remove: function(column) {
            //<debug> 
            if (!column.isGridColumn) {
                Ext.raise('Column selection must be passed a grid Column header object');
            }
            //</debug> 
 
            if (this.selectedColumns) {
                Ext.Array.remove(this.selectedColumns, column);
 
                // Might be being called because of column removal/hiding. 
                // In which case the view will have selected cells removed, so no refresh needed. 
                if (column.getGrid() && column.isVisible()) {
                    this.refreshColumns(column);
                }
            }
        },
 
        /**
         * @private
         */
        selectAll: function () {
            var me = this;
 
            me.clear();
            me.selectedColumns = me.getSelectionModel().lastContiguousColumnRange =
                me.view.getHeaderContainer().getVisibleColumns();
            me.refreshColumns.apply(me, me.selectedColumns);
        },
 
        extendRange: function(extensionVector) {
            var me = this,
                columns = me.view.getHeaderContainer().getVisibleColumns(),
                i;
 
            for (= extensionVector.start.columnIndex; i <=  extensionVector.end.columnIndex; i++) {
                me.add(columns[i]);
            }
        },
 
        onSelectionFinish: function() {
            var me = this,
                range = me.getContiguousSelection();
 
            if (range) {
                me.getSelectionModel().onSelectionFinish(me,
                    new Ext.grid.Location(me.view, {record: 0, column: range[0]}),
                    new Ext.grid.Location(me.view, {record: me.view.getStore().getCount() - 1, column: range[1]}));
            } else {
                me.getSelectionModel().onSelectionFinish(me);
            }
        },
 
        /**
         * @return {Array} `[startColumn, endColumn]` if the selection represents a visually contiguous set of columns.
         * The SelectionReplicator is only enabled if there is a contiguous block.
         * @private
         */
        getContiguousSelection: function() {
            var selection = Ext.Array.sort(this.getColumns(), function(c1, c2) {
                    // Use index *in ownerGrid* so that a locking assembly can order columns correctly 
                    return c1.getGrid().ownerGrid.getHeaderContainer().indexOfLeaf(c1) - c2.getGrid().ownerGrid.getHeaderContainer().indexOfLeaf(c2);
                }),
                len = selection.length,
                i;
 
            if (len) {
                for (= 1; i < len; i++) {
                    if (selection[i].getVisibleIndex() !== selection[- 1].getVisibleIndex() + 1) {
                        return false;
                    }
                }
                return [selection[0], selection[len - 1]];
            }
        }
    }
});