/*
 * Copyright 2017 Palantir Technologies, Inc. All rights reserved.
 *
 * 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 { render } from "@testing-library/react";
import { Component } from "react";

import { describe, it } from "@blueprintjs/test-commons/vitest";

import { CellType, expectCellLoading } from "./cell/cellTestUtils";
import * as Classes from "./common/classes";
import { ElementHarness } from "./harness";

import { Cell, Column, ColumnHeaderCell, ColumnLoadingOption, RowHeaderCell, Table, TableLoadingOption } from ".";

interface TableLoadingOptionsTesterProps {
    columnLoadingOptions: ColumnLoadingOption[];
    tableLoadingOptions: TableLoadingOption[];
}

class TableLoadingOptionsTester extends Component<TableLoadingOptionsTesterProps> {
    public static isCellLoading = (index: number) => {
        if (index === 0) {
            return true;
        } else if (index === 1) {
            return false;
        } else {
            return undefined;
        }
    };

    private static cellRenderer = (rowIndex: number) => {
        return <Cell loading={TableLoadingOptionsTester.isCellLoading(rowIndex)}>some cell text</Cell>;
    };

    private static columnHeaderCellRenderer = (columnIndex: number) => {
        return <ColumnHeaderCell loading={TableLoadingOptionsTester.isCellLoading(columnIndex)} name="column header" />;
    };

    private static rowHeaderCellRenderer = (rowIndex: number) => {
        return <RowHeaderCell loading={TableLoadingOptionsTester.isCellLoading(rowIndex)} name="row header" />;
    };

    public render() {
        const { columnLoadingOptions, tableLoadingOptions } = this.props;
        return (
            <Table
                loadingOptions={tableLoadingOptions}
                numRows={3}
                rowHeaderCellRenderer={TableLoadingOptionsTester.rowHeaderCellRenderer}
            >
                <Column
                    loadingOptions={columnLoadingOptions}
                    cellRenderer={TableLoadingOptionsTester.cellRenderer}
                    columnHeaderCellRenderer={TableLoadingOptionsTester.columnHeaderCellRenderer}
                />
                <Column
                    loadingOptions={columnLoadingOptions}
                    columnHeaderCellRenderer={TableLoadingOptionsTester.columnHeaderCellRenderer}
                />
                <Column
                    loadingOptions={columnLoadingOptions}
                    columnHeaderCellRenderer={TableLoadingOptionsTester.columnHeaderCellRenderer}
                />
            </Table>
        );
    }
}

describe("Loading Options", () => {
    const allTableLoadingOptions = generatePowerSet([
        TableLoadingOption.CELLS,
        TableLoadingOption.COLUMN_HEADERS,
        TableLoadingOption.ROW_HEADERS,
    ]);
    const allColumnLoadingOptions = generatePowerSet([ColumnLoadingOption.CELLS, ColumnLoadingOption.HEADER]);

    // Below is an exhaustive set of tests of all possible combinations of loading options
    allTableLoadingOptions.forEach(tableLoadingOptions => {
        allColumnLoadingOptions.forEach(columnLoadingOptions => {
            it(`table: [${tableLoadingOptions}], column: [${columnLoadingOptions}]`, () => {
                const { container } = render(
                    <TableLoadingOptionsTester
                        columnLoadingOptions={columnLoadingOptions}
                        tableLoadingOptions={tableLoadingOptions}
                    />,
                );
                const tableHarness = new ElementHarness(container);

                // only testing the first column of body cells because the second and third
                // columns are meant to test column related loading combinations
                const quadrantSelector = `.${Classes.TABLE_QUADRANT_MAIN}`;
                const cells = Array.from(
                    tableHarness.element!.querySelectorAll(
                        `${quadrantSelector} .${Classes.TABLE_CELL}.${Classes.columnCellIndexClass(0)}`,
                    ),
                );
                const columnHeaders = Array.from(
                    tableHarness.element!.querySelectorAll(
                        `${quadrantSelector} .${Classes.TABLE_COLUMN_HEADERS} .${Classes.TABLE_HEADER}`,
                    ),
                );
                const rowHeaders = Array.from(
                    tableHarness.element!.querySelectorAll(
                        `${quadrantSelector} .${Classes.TABLE_ROW_HEADERS} .${Classes.TABLE_HEADER}`,
                    ),
                );
                testLoadingOptionOverrides(
                    columnHeaders,
                    CellType.COLUMN_HEADER,
                    TableLoadingOptionsTester.isCellLoading,
                    columnLoadingOptions,
                    tableLoadingOptions,
                );
                testLoadingOptionOverrides(
                    rowHeaders,
                    CellType.ROW_HEADER,
                    TableLoadingOptionsTester.isCellLoading,
                    columnLoadingOptions,
                    tableLoadingOptions,
                );
                testLoadingOptionOverrides(
                    cells,
                    CellType.BODY_CELL,
                    TableLoadingOptionsTester.isCellLoading,
                    columnLoadingOptions,
                    tableLoadingOptions,
                );
            });
        });
    });
});

function generatePowerSet<T>(list: T[]) {
    const base2 = (num: number) => num.toString(2);
    const numberOfSubsets = Math.pow(2, list.length);
    const listOfSubsets: T[][] = [[]];

    for (let i = 1; i < numberOfSubsets; i++) {
        const subset: T[] = [];
        // front-pad the string and then slice from the back to ensure fixed length binary string
        const binaryString = (Array(list.length).join("0") + base2(i)).slice(list.length * -1);
        for (let j = 0; j < binaryString.length; j++) {
            if (binaryString.charAt(j) === "1") {
                subset.push(list[j]);
            }
        }
        listOfSubsets.push(subset);
    }

    return listOfSubsets;
}

/*
 * This function tests the expected loading option override behavior for all cell types.
 *
 * For convenience, it accepts a list of cells of a single type and tests that each cell conforms to
 * the expected loading option override behavior.
 *
 * For any given cell, beginning at the cell-level, if loading options are present, use them for
 * cell rendering and ignore parent options. If loading options are absent look to the closest
 * parent. For body cells and column headers, this means look to the column-level loading options.
 * For row headers, this means look to the table-level. Repeat this process until loading options
 * are found. If loading options ultimately remain undefined, do not render the loading state for
 * the cell.
 */
function testLoadingOptionOverrides(
    cells: Element[],
    cellType: CellType,
    cellLoading: (index: number) => boolean | undefined,
    columnLoadingOptions: ColumnLoadingOption[],
    tableLoadingOptions: TableLoadingOption[],
) {
    cells.forEach((cell, i) => {
        if (cellLoading(i)) {
            expectCellLoading(cell, cellType, true);
        } else if (cellLoading(i) === false) {
            expectCellLoading(cell, cellType, false);
        } else if (
            (cellType === CellType.BODY_CELL || cellType === CellType.COLUMN_HEADER) &&
            columnLoadingOptions != null
        ) {
            // cast is safe because cellType is guaranteed to not be TableLoadingOption.ROW_HEADERS
            const loading = columnLoadingOptions.indexOf(cellType as any as ColumnLoadingOption) >= 0;
            // HACKHACK: see https://github.com/palantir/blueprint/issues/5114
            console.debug(`Skipped test assertion for '${cellType}' @ index ${i}, expecting loading=${loading}`);
            // expectCellLoading(cell, cellType, loading);
        } else if (tableLoadingOptions != null) {
            expectCellLoading(cell, cellType, tableLoadingOptions.indexOf(cellType) >= 0);
        } else {
            expectCellLoading(cell, cellType, false);
        }
    });
}
