/**
 *
 * carbon-angular v0.0.0 | table-model.class.js
 *
 * Copyright 2014, 2020 IBM
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


import { EventEmitter } from "@angular/core";
import { TableHeaderItem } from "./table-header-item.class";
import { TableItem } from "./table-item.class";
import { Subject } from "rxjs";
var TableModel = /** @class */ (function () {
    function TableModel() {
        this.dataChange = new EventEmitter();
        this.rowsSelectedChange = new EventEmitter();
        this.rowsExpandedChange = new EventEmitter();
        /**
         * Gets emitted when `selectAll` is called. Emits false if all rows are deselected and true if
         * all rows are selected.
         */
        this.selectAllChange = new Subject();
        /**
         * Contains information about selection state of rows in the table.
         */
        this.rowsSelected = [];
        /**
         * Contains information about expanded state of rows in the table.
         */
        this.rowsExpanded = [];
        /**
         * Contains information about the context of the row.
         *
         * It affects styling of the row to reflect the context.
         *
         * string can be one of `"success" | "warning" | "info" | "error" | ""` and it's
         * empty or undefined by default
         */
        this.rowsContext = [];
        /**
         * Contains class name(s) of the row.
         *
         * It affects styling of the row to reflect the appended class name(s).
         *
         * It's empty or undefined by default
         */
        this.rowsClass = [];
        /**
         * Contains information about the header cells of the table.
         */
        this.header = [];
        /**
         * Tracks the current page.
         */
        this.currentPage = 1;
        /**
         * Length of page.
         */
        this.pageLength = 10;
        /**
         * Set to true when there is no more data to load in the table
         */
        this.isEnd = false;
        /**
         * Set to true when lazy loading to show loading indicator
         */
        this.isLoading = false;
        /**
         * Used in `data`
         */
        this._data = [[]];
        TableModel.COUNT++;
    }
    Object.defineProperty(TableModel.prototype, "data", {
        /**
         * Gets the full data.
         *
         * You can use it to alter individual `TableItem`s but if you need to change
         * table structure, use `addRow()` and/or `addColumn()`
         */
        get: function () {
            return this._data;
        },
        /**
         * Sets data of the table.
         *
         * Make sure all rows are the same length to keep the column count accurate.
         */
        set: function (newData) {
            if (!newData || (Array.isArray(newData) && newData.length === 0)) {
                newData = [[]];
            }
            this._data = newData;
            // init rowsSelected
            this.rowsSelected = new Array(this._data.length).fill(false);
            this.rowsExpanded = new Array(this._data.length).fill(false);
            // init rowsContext
            this.rowsContext = new Array(this._data.length);
            // init rowsClass
            this.rowsClass = new Array(this._data.length);
            // only create a fresh header if necessary (header doesn't exist or differs in length)
            if (this.header == null || (this.header.length !== this._data[0].length && this._data[0].length > 0)) {
                var header = new Array();
                for (var i = 0; i < this._data[0].length; i++) {
                    header.push(new TableHeaderItem());
                }
                this.header = header;
            }
            this.dataChange.emit();
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(TableModel.prototype, "totalDataLength", {
        /**
         * Total length of data that table has access to, or the amount manually set
         */
        get: function () {
            // if manually set data length
            if (this._totalDataLength && this._totalDataLength >= 0) {
                return this._totalDataLength;
            }
            // if empty dataset
            if (this.data && this.data.length === 1 && this.data[0].length === 0) {
                return 0;
            }
            return this.data.length;
        },
        /**
         * Manually set data length in case the data in the table doesn't
         * correctly reflect all the data that table is to display.
         *
         * Example: if you have multiple pages of data that table will display
         * but you're loading one at a time.
         *
         * Set to `null` to reset to default behavior.
         */
        set: function (length) {
            this._totalDataLength = length;
        },
        enumerable: true,
        configurable: true
    });
    /**
     * Returns an id for the given column
     *
     * @param column the column to generate an id for
     * @param row the row of the header to generate an id for
     */
    TableModel.prototype.getId = function (column, row) {
        if (row === void 0) { row = 0; }
        return "table-header-" + row + "-" + column + "-" + TableModel.COUNT;
    };
    /**
     * Returns the id of the header. Used to link the cells with headers (or headers with headers)
     *
     * @param column the column to start getting headers for
     * @param colSpan the number of columns to get headers for (defaults to 1)
     */
    TableModel.prototype.getHeaderId = function (column, colSpan) {
        if (colSpan === void 0) { colSpan = 1; }
        if (column === "select" || column === "expand") {
            return this.getId(column);
        }
        var ids = [];
        for (var i = column; i >= 0; i--) {
            if (this.header[i]) {
                for (var j = 0; j < colSpan; j++) {
                    ids.push(this.getId(i + j));
                }
                break;
            }
        }
        return ids.join(" ");
    };
    /**
     * Finds closest header by trying the `column` and then working its way to the left
     *
     * @param column the target column
     */
    TableModel.prototype.getHeader = function (column) {
        if (!this.header) {
            return null;
        }
        for (var i = column; i >= 0; i--) {
            var headerCell = this.header[i];
            if (headerCell) {
                return headerCell;
            }
        }
        return null;
    };
    /**
     * Returns how many rows is currently selected
     */
    TableModel.prototype.selectedRowsCount = function () {
        var count = 0;
        if (this.rowsSelected) {
            this.rowsSelected.forEach(function (rowSelected) {
                if (rowSelected) {
                    count++;
                }
            });
        }
        return count;
    };
    /**
     * Returns how many rows is currently expanded
     */
    TableModel.prototype.expandedRowsCount = function () {
        var count = 0;
        if (this.rowsExpanded) {
            this.rowsExpanded.forEach(function (rowExpanded) {
                if (rowExpanded) {
                    count++;
                }
            });
        }
        return count;
    };
    /**
     * Returns `index`th row of the table.
     *
     * Negative index starts from the end. -1 being the last element.
     *
     * @param index
     */
    TableModel.prototype.row = function (index) {
        return this.data[this.realRowIndex(index)];
    };
    /**
     * Adds a row to the `index`th row or appends to table if index not provided.
     *
     * If row is shorter than other rows or not provided, it will be padded with
     * empty `TableItem` elements.
     *
     * If row is longer than other rows, others will be extended to match so no data is lost.
     *
     * If called on an empty table with no parameters, it creates a 1x1 table.
     *
     * Negative index starts from the end. -1 being the last element.
     *
     * @param [row]
     * @param [index]
     */
    TableModel.prototype.addRow = function (row, index) {
        // if table empty create table with row
        if (!this.data || this.data.length === 0 || this.data[0].length === 0) {
            var newData = new Array();
            newData.push(row ? row : [new TableItem()]); // row or one empty one column row
            this.data = newData;
            return;
        }
        var realRow = row;
        var columnCount = this.data[0].length;
        if (row == null) {
            realRow = new Array();
            for (var i = 0; i < columnCount; i++) {
                realRow.push(new TableItem());
            }
        }
        if (realRow.length < columnCount) {
            // extend the length of realRow
            var difference = columnCount - realRow.length;
            for (var i = 0; i < difference; i++) {
                realRow.push(new TableItem());
            }
        }
        else if (realRow.length > columnCount) {
            // extend the length of header
            var difference = realRow.length - this.header.length;
            for (var j = 0; j < difference; j++) {
                this.header.push(new TableHeaderItem());
            }
            // extend the length of every other row
            for (var i = 0; i < this.data.length; i++) {
                var currentRow = this.data[i];
                difference = realRow.length - currentRow.length;
                for (var j = 0; j < difference; j++) {
                    currentRow.push(new TableItem());
                }
            }
        }
        if (index == null) {
            this.data.push(realRow);
            // update rowsSelected property for length
            this.rowsSelected.push(false);
            // update rowsExpanded property for length
            this.rowsExpanded.push(false);
            // update rowsContext property for length
            this.rowsContext.push(undefined);
            // update rowsClass property for length
            this.rowsClass.push(undefined);
        }
        else {
            var ri = this.realRowIndex(index);
            this.data.splice(ri, 0, realRow);
            // update rowsSelected property for length
            this.rowsSelected.splice(ri, 0, false);
            // update rowsExpanded property for length
            this.rowsExpanded.splice(ri, 0, false);
            // update rowsContext property for length
            this.rowsContext.splice(ri, 0, undefined);
            // update rowsClass property for length
            this.rowsClass.splice(ri, 0, undefined);
        }
        this.dataChange.emit();
    };
    /**
     * Deletes `index`th row.
     *
     * Negative index starts from the end. -1 being the last element.
     *
     * @param index
     */
    TableModel.prototype.deleteRow = function (index) {
        var rri = this.realRowIndex(index);
        this.data.splice(rri, 1);
        this.rowsSelected.splice(rri, 1);
        this.rowsExpanded.splice(rri, 1);
        this.rowsContext.splice(rri, 1);
        this.rowsClass.splice(rri, 1);
        this.dataChange.emit();
    };
    TableModel.prototype.hasExpandableRows = function () {
        return this.data.some(function (data) { return data.some(function (d) { return d && d.expandedData; }); }); // checking for some in 2D array
    };
    TableModel.prototype.isRowExpandable = function (index) {
        return this.data[index].some(function (d) { return d && d.expandedData; });
    };
    TableModel.prototype.isRowExpanded = function (index) {
        return this.rowsExpanded[index];
    };
    TableModel.prototype.getRowContext = function (index) {
        return this.rowsContext[index];
    };
    /**
     * Returns `index`th column of the table.
     *
     * Negative index starts from the end. -1 being the last element.
     *
     * @param index
     */
    TableModel.prototype.column = function (index) {
        var column = new Array();
        var ri = this.realColumnIndex(index);
        var rc = this.data.length;
        for (var i = 0; i < rc; i++) {
            var row = this.data[i];
            column.push(row[ri]);
        }
        return column;
    };
    /**
     * Adds a column to the `index`th column or appends to table if index not provided.
     *
     * If column is shorter than other columns or not provided, it will be padded with
     * empty `TableItem` elements.
     *
     * If column is longer than other columns, others will be extended to match so no data is lost.
     *
     * If called on an empty table with no parameters, it creates a 1x1 table.
     *
     * Negative index starts from the end. -1 being the last element.
     *
     * @param [column]
     * @param [index]
     */
    TableModel.prototype.addColumn = function (column, index) {
        // if table empty create table with row
        if (!this.data || this.data.length === 0 || this.data[0].length === 0) {
            var newData = new Array();
            if (column == null) {
                newData.push([new TableItem()]);
            }
            else {
                for (var i = 0; i < column.length; i++) {
                    var item = column[i];
                    newData.push([item]);
                }
            }
            this.data = newData;
            return;
        }
        var rc = this.data.length; // row count
        var ci = this.realColumnIndex(index);
        // append missing rows
        for (var i = 0; column != null && i < column.length - rc; i++) {
            this.addRow();
        }
        rc = this.data.length;
        if (index == null) {
            // append to end
            for (var i = 0; i < rc; i++) {
                var row = this.data[i];
                row.push(column == null || column[i] == null ? new TableItem() : column[i]);
            }
            // update header if not already set by user
            if (this.header.length < this.data[0].length) {
                this.header.push(new TableHeaderItem());
            }
        }
        else {
            if (index >= this.data[0].length) {
                // if trying to append
                ci++;
            }
            // insert
            for (var i = 0; i < rc; i++) {
                var row = this.data[i];
                row.splice(ci, 0, column == null || column[i] == null ? new TableItem() : column[i]);
            }
            // update header if not already set by user
            if (this.header.length < this.data[0].length) {
                this.header.splice(ci, 0, new TableHeaderItem());
            }
        }
        this.dataChange.emit();
    };
    /**
     * Deletes `index`th column.
     *
     * Negative index starts from the end. -1 being the last element.
     *
     * @param index
     */
    TableModel.prototype.deleteColumn = function (index) {
        var rci = this.realColumnIndex(index);
        var rowCount = this.data.length;
        for (var i = 0; i < rowCount; i++) {
            this.data[i].splice(rci, 1);
        }
        // update header if not already set by user
        if (this.header.length > this.data[0].length) {
            this.header.splice(rci, 1);
        }
        this.dataChange.emit();
    };
    TableModel.prototype.moveColumn = function (indexFrom, indexTo) {
        var headerFrom = this.header[indexFrom];
        this.addColumn(this.column(indexFrom), indexTo);
        this.deleteColumn(indexFrom + (indexTo < indexFrom ? 1 : 0));
        this.header[indexTo + (indexTo > indexFrom ? -1 : 0)] = headerFrom;
    };
    /**
     * Sorts the data currently present in the model based on `compare()`
     *
     * Direction is set by `ascending` and `descending` properties of `TableHeaderItem`
     * in `index`th column.
     *
     * @param index The column based on which it's sorting
     */
    TableModel.prototype.sort = function (index) {
        var _this = this;
        this.pushRowStateToModelData();
        this.data.sort(function (a, b) { return (_this.header[index].descending ? -1 : 1) * _this.header[index].compare(a[index], b[index]); });
        this.popRowStateFromModelData();
        this.header.forEach(function (column) { return column.sorted = false; });
        this.header[index].sorted = true;
    };
    /**
     * Appends `rowsSelected` and `rowsExpanded` info to model data.
     *
     * When sorting rows, do this first so information about row selection
     * gets sorted with the other row info.
     *
     * Call `popRowSelectionFromModelData()` after sorting to make everything
     * right with the world again.
     */
    TableModel.prototype.pushRowStateToModelData = function () {
        for (var i = 0; i < this.data.length; i++) {
            var rowSelectedMark = new TableItem();
            rowSelectedMark.data = this.rowsSelected[i];
            this.data[i].push(rowSelectedMark);
            var rowExpandedMark = new TableItem();
            rowExpandedMark.data = this.rowsExpanded[i];
            this.data[i].push(rowExpandedMark);
            var rowContext = new TableItem();
            rowContext.data = this.rowsContext[i];
            this.data[i].push(rowContext);
            var rowClass = new TableItem();
            rowClass.data = this.rowsClass[i];
            this.data[i].push(rowClass);
        }
    };
    /**
     * Restores `rowsSelected` from data pushed by `pushRowSelectionToModelData()`
     *
     * Call after sorting data (if you previously pushed to maintain selection order)
     * to make everything right with the world again.
     */
    TableModel.prototype.popRowStateFromModelData = function () {
        for (var i = 0; i < this.data.length; i++) {
            this.rowsClass[i] = this.data[i].pop().data;
            this.rowsContext[i] = this.data[i].pop().data;
            this.rowsExpanded[i] = !!this.data[i].pop().data;
            this.rowsSelected[i] = !!this.data[i].pop().data;
        }
    };
    /**
     * Checks if row is filtered out.
     *
     * @param index
     * @returns true if any of the filters in header filters out the `index`th row
     */
    TableModel.prototype.isRowFiltered = function (index) {
        var _this = this;
        var realIndex = this.realRowIndex(index);
        return this.header.some(function (item, i) { return item && item.filter(_this.row(realIndex)[i]); });
    };
    /**
     * Select/deselect `index`th row based on value
     *
     * @param index index of the row to select
     * @param value state to set the row to. Defaults to `true`
     */
    TableModel.prototype.selectRow = function (index, value) {
        if (value === void 0) { value = true; }
        this.rowsSelected[index] = value;
        this.rowsSelectedChange.emit(index);
    };
    /**
     * Selects or deselects all rows in the model
     *
     * @param value state to set all rows to. Defaults to `true`
     */
    TableModel.prototype.selectAll = function (value) {
        if (value === void 0) { value = true; }
        if (this.data.length >= 1) {
            for (var i = 0; i < this.rowsSelected.length; i++) {
                this.selectRow(i, value);
            }
        }
        this.selectAllChange.next(value);
    };
    TableModel.prototype.isRowSelected = function (index) {
        return this.rowsSelected[index];
    };
    /**
     * Expands/Collapses `index`th row based on value
     *
     * @param index index of the row to expand or collapse
     * @param value expanded state of the row. `true` is expanded and `false` is collapsed
     */
    TableModel.prototype.expandRow = function (index, value) {
        if (value === void 0) { value = true; }
        this.rowsExpanded[index] = value;
        this.rowsExpandedChange.emit(index);
    };
    /**
     * Gets the true index of a row based on it's relative position.
     * Like in Python, positive numbers start from the top and
     * negative numbers start from the bottom.
     *
     * @param index
     */
    TableModel.prototype.realRowIndex = function (index) {
        return this.realIndex(index, this.data.length);
    };
    /**
     * Gets the true index of a column based on it's relative position.
     * Like in Python, positive numbers start from the top and
     * negative numbers start from the bottom.
     *
     * @param index
     */
    TableModel.prototype.realColumnIndex = function (index) {
        return this.realIndex(index, this.data[0].length);
    };
    /**
     * Generic function to calculate the real index of something.
     * Used by `realRowIndex()` and `realColumnIndex()`
     *
     * @param index
     * @param length
     */
    TableModel.prototype.realIndex = function (index, length) {
        if (index == null) {
            return length - 1;
        }
        else if (index >= 0) {
            return index >= length ? length - 1 : index;
        }
        else {
            return -index >= length ? 0 : length + index;
        }
    };
    /**
     * The number of models instantiated, used for (among other things) unique id generation
     */
    TableModel.COUNT = 0;
    return TableModel;
}());
export { TableModel };
//# sourceMappingURL=table-model.class.js.map