// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors.                              ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

import { test, expect } from "@perspective-dev/test";
import perspective from "../perspective_client";

const jsc = require("jsverify");

const replicate = (n, g) => jsc.tuple(new Array(n).fill(g));

const array_equals = function (a, b) {
    return JSON.stringify(a) === JSON.stringify(b);
};

const generator = function (length = 100, has_zero = true) {
    const min = has_zero ? 0 : 1;
    return jsc.record({
        a: replicate(length, jsc.number(min, 1000000)),
        b: replicate(length, jsc.number(min, 1000000)),
        c: replicate(length, jsc.string),
        d: replicate(length, jsc.datetime),
        e: replicate(length, jsc.bool),
    });
};

/**
 * Uses invariant testing to assert base correctness of computed columns.
 */
((perspective) => {
    test.describe("Invariant testing", function () {
        test.describe("Inverse expressions should be invariant", function () {
            jsc.property("(x - y) + x == y", generator(), async (data) => {
                const table = await perspective.table(data);

                const view = await table.view({
                    expressions: { '("a" - "b") + "b"': '("a" - "b") + "b"' },
                });
                const result = await view.to_columns();
                const expected = array_equals(
                    result['("a" - "b") + "b"'],
                    data["a"],
                );
                view.delete();
                table.delete();
                return expected;
            });

            jsc.property("(x + y) - x - y == 0", generator(), async (data) => {
                const table = await perspective.table(data);

                const view = await table.view({
                    expressions: {
                        '("a" + "b") - "a" - "b"': '("a" + "b") - "a" - "b"',
                    },
                });
                const result = await view.to_columns();
                const expected = array_equals(
                    result['("a" + "b") - "a" - "b"'],
                    Array(data["a"].length).fill(0),
                );
                view.delete();
                table.delete();
                return expected;
            });

            jsc.property(
                "(x + y) - (x + y) == 0",
                generator(),
                async (data) => {
                    const table = await perspective.table(data);

                    const view = await table.view({
                        expressions: {
                            '("a" + "b") - ("a" + "b")':
                                '("a" + "b") - ("a" + "b")',
                        },
                    });
                    const result = await view.to_columns();
                    const expected = array_equals(
                        result['("a" + "b") - ("a" + "b")'],
                        Array(data["a"].length).fill(0),
                    );
                    view.delete();
                    table.delete();
                    return expected;
                },
            );

            jsc.property("(x - x) == 0", generator(), async (data) => {
                const table = await perspective.table(data);

                const view = await table.view({
                    expressions: { '"a" - "a"': '"a" - "a"' },
                });
                const result = await view.to_columns();
                const expected = array_equals(
                    result['"a" - "a"'],
                    Array(data["a"].length).fill(0),
                );
                view.delete();
                table.delete();
                return expected;
            });

            jsc.property("(x / x) == 1", generator(), async (data) => {
                const table = await perspective.table(data);

                const view = await table.view({
                    expressions: { '"a" / "a"': '"a" / "a"' },
                });
                const result = await view.to_columns();
                const expected = array_equals(
                    result['"a" / "a"'],
                    Array(data["a"].length).fill(1),
                );
                view.delete();
                table.delete();
                return expected;
            });

            jsc.property("(x + x) - x - x == 0", generator(), async (data) => {
                const table = await perspective.table(data);

                const view = await table.view({
                    expressions: {
                        '("a" + "a") - "a" - "a"': '("a" + "a") - "a" - "a"',
                    },
                });
                const result = await view.to_columns();
                const expected = array_equals(
                    result['("a" + "a") - "a" - "a"'],
                    Array(data["a"].length).fill(0),
                );
                view.delete();
                table.delete();
                return expected;
            });

            jsc.property(
                "sqrt(x ^ 2) == x",
                generator(100, false),
                async (data) => {
                    const table = await perspective.table(data);

                    const view = await table.view({
                        expressions: {
                            'sqrt(pow("a", 2))': 'sqrt(pow("a", 2))',
                        },
                    });
                    const result = await view.to_columns();
                    const expected = array_equals(
                        result['sqrt(pow("a", 2))'],
                        data["a"],
                    );
                    view.delete();
                    table.delete();
                    return expected;
                },
            );

            // jsc.property(
            //     "x ^ 2 == (x * x)",
            //     generator(100, false),
            //     async (data) => {
            //         const table = await perspective.table(data);

            //         const view = await table.view({
            //             expressions: ['pow("a", 2)', '"a" * "a"'].reduce((x, y) => Object.assign(x, {[y]: y}), {}),
            //         });
            //         const result = await view.to_columns();
            //         const expected = array_equals(
            //             result['pow("a", 2)'],
            //             result['"a" * "a"']
            //         );
            //         view.delete();
            //         table.delete();
            //         return expected;
            //     }
            // );

            jsc.property(
                "x % x == 100",
                generator(100, false),
                async (data) => {
                    const table = await perspective.table(data);

                    const view = await table.view({
                        expressions: {
                            'percent_of("a", "a")': 'percent_of("a", "a")',
                        },
                    });
                    const result = await view.to_columns();
                    const expected = array_equals(
                        result['percent_of("a", "a")'],
                        Array(data["a"].length).fill(100),
                    );
                    view.delete();
                    table.delete();
                    return expected;
                },
            );

            jsc.property(
                "abs(x) ? x > 0 == x",
                generator(100, false),
                async (data) => {
                    const table = await perspective.table(data);

                    const view = await table.view({
                        expressions: { 'abs("a")': 'abs("a")' },
                    });
                    const result = await view.to_columns();
                    const expected = array_equals(
                        result['abs("a")'],
                        data["a"],
                    );
                    view.delete();
                    table.delete();
                    return expected;
                },
            );
        });

        test.describe("Comparison operations should be pure with the same inputs.", function () {
            jsc.property(
                "== should always be true with the same input column",
                generator(),
                async (data) => {
                    const table = await perspective.table(data);

                    const view = await table.view({
                        expressions: { '"a" == "a"': '"a" == "a"' },
                    });

                    const result = await view.to_columns();
                    const expected = array_equals(
                        result['"a" == "a"'],
                        Array(data["a"].length).fill(true),
                    );
                    view.delete();
                    table.delete();
                    return expected;
                },
            );

            jsc.property(
                "> should always be false with the same input column",
                generator(),
                async (data) => {
                    const table = await perspective.table(data);

                    const view = await table.view({
                        expressions: { '"a" > "a"': '"a" > "a"' },
                    });

                    const result = await view.to_columns();
                    const expected = array_equals(
                        result['"a" > "a"'],
                        Array(data["a"].length).fill(false),
                    );
                    view.delete();
                    table.delete();
                    return expected;
                },
            );

            jsc.property(
                "< should always be false with the same input column",
                generator(),
                async (data) => {
                    const table = await perspective.table(data);

                    const view = await table.view({
                        expressions: { '"a" < "a"': '"a" < "a"' },
                    });

                    const result = await view.to_columns();
                    const expected = array_equals(
                        result['"a" < "a"'],
                        Array(data["a"].length).fill(false),
                    );
                    view.delete();
                    table.delete();
                    return expected;
                },
            );

            jsc.property(
                "String == should always be true with the same input column",
                generator(),
                async (data) => {
                    const table = await perspective.table({
                        a: "float",
                        b: "float",
                        c: "string",
                        d: "datetime",
                        e: "boolean",
                    });

                    await table.update(data);

                    const view = await table.view({
                        expressions: { '"c" == "c"': '"c" == "c"' },
                    });

                    const result = await view.to_columns();
                    const expected = array_equals(
                        result['"c" == "c"'],
                        Array(data["c"].length).fill(true),
                    );
                    view.delete();
                    table.delete();
                    return expected;
                },
            );

            jsc.property(
                "Datetime == should always be true with the same input column",
                generator(),
                async (data) => {
                    const table = await perspective.table({
                        a: "float",
                        b: "float",
                        c: "string",
                        d: "datetime",
                        e: "boolean",
                    });

                    await table.update(data);

                    const view = await table.view({
                        expressions: { '"d" == "d"': '"d" == "d"' },
                    });

                    const result = await view.to_columns();
                    const expected = array_equals(
                        result['"d" == "d"'],
                        Array(data["d"].length).fill(true),
                    );
                    view.delete();
                    table.delete();
                    return expected;
                },
            );

            jsc.property(
                "Date == should always be true with the same input column",
                generator(),
                async (data) => {
                    const table = await perspective.table({
                        a: "float",
                        b: "float",
                        c: "string",
                        d: "date",
                        e: "boolean",
                    });

                    await table.update(data);

                    const view = await table.view({
                        expressions: { '"d" == "d"': '"d" == "d"' },
                    });

                    const result = await view.to_columns();
                    const expected = array_equals(
                        result['"d" == "d"'],
                        Array(data["d"].length).fill(true),
                    );
                    view.delete();
                    table.delete();
                    return expected;
                },
            );
        });
    });
})(perspective);
