// (C) 2007-2019 GoodData Corporation
import {
    shouldFollowPointer,
    shouldFollowPointerForDualAxes,
    shouldStartOnTick,
    shouldEndOnTick,
    getChartProperties,
    pointInRange,
    getStackedMaxValue,
    getStackedMinValue,
    shouldXAxisStartOnTickOnBubbleScatter,
    shouldYAxisStartOnTickOnBubbleScatter,
    alignChart,
} from "../helpers";
import { VisualizationTypes } from "../../../../../constants/visualizationTypes";
import { ChartAlignTypes, IChartConfig } from "../../../../../interfaces/Config";
import { BOTTOM, TOP } from "../../../../../constants/alignments";

describe("helpers", () => {
    describe("getChartProperties", () => {
        const config: IChartConfig = {
            xaxis: {
                rotation: "60",
                visible: false,
            },
            yaxis: {
                labelsEnabled: true,
            },
        };

        it("should return properties from config", () => {
            expect(getChartProperties(config, VisualizationTypes.COLUMN)).toEqual({
                xAxisProps: { rotation: "60", visible: false },
                yAxisProps: { labelsEnabled: true },
            });
        });

        it("should return properties from config for bar chart with switched axes", () => {
            expect(getChartProperties(config, VisualizationTypes.BAR)).toEqual({
                yAxisProps: { rotation: "60", visible: false },
                xAxisProps: { labelsEnabled: true },
            });
        });
    });

    describe("shouldStartOnTick, shouldEndOnTick", () => {
        const nonStackedChartOptions = {
            hasStackByAttribute: false,
            data: {
                series: [
                    {
                        data: [{ y: 20 }],
                        visible: true,
                    },
                ],
            },
        };

        const stackedChartOptions = {
            hasStackByAttribute: true,
            data: {
                series: [
                    {
                        data: [{ y: 20 }, { y: 10 }, { y: 5 }],
                        visible: true,
                    },
                ],
            },
        };

        describe("shouldStartOnTick", () => {
            it("should return false when min and max are set", () => {
                const chartOptions = {
                    ...nonStackedChartOptions,
                    yAxisProps: { min: "5", max: "10" },
                };
                expect(shouldStartOnTick(chartOptions)).toBeFalsy();
            });

            it("should return false when min is greater than max", () => {
                const chartOptions = {
                    ...nonStackedChartOptions,
                    yAxisProps: { min: "10", max: "5" },
                };

                expect(shouldStartOnTick(chartOptions)).toBeTruthy();
            });

            it("should return false if max is set but greater than min data value (non stacked)", () => {
                const chartOptions = {
                    ...nonStackedChartOptions,
                    yAxisProps: { max: "40" },
                };

                expect(shouldStartOnTick(chartOptions)).toBeFalsy();
            });

            it("should return false if max is set but greater than min data value (stacked)", () => {
                const chartOptions = {
                    ...stackedChartOptions,
                    yAxisProps: { max: "40" },
                };

                expect(shouldStartOnTick(chartOptions)).toBeFalsy();
            });

            it("should return true when no max or min are set", () => {
                const chartOptions = {
                    ...nonStackedChartOptions,
                    yAxisProps: {},
                };

                expect(shouldStartOnTick(chartOptions)).toBeTruthy();
            });

            it("should return true if max is set but smaller than min data value (non stacked)", () => {
                const chartOptions = {
                    ...nonStackedChartOptions,
                    yAxisProps: { max: "-40" },
                };

                expect(shouldStartOnTick(chartOptions)).toBeTruthy();
            });

            it("should return true if max is set but smaller than min data value (stacked)", () => {
                const chartOptions = {
                    ...stackedChartOptions,
                    yAxisProps: { max: "-40" },
                };

                expect(shouldStartOnTick(chartOptions)).toBeTruthy();
            });
        });

        describe("shouldEndOnTick", () => {
            it("should return false when min and max are set", () => {
                const chartOptions = {
                    ...nonStackedChartOptions,
                    yAxisProps: { min: "5", max: "10" },
                };
                expect(shouldEndOnTick(chartOptions)).toBeFalsy();
            });

            it("should return false when min is greater than max", () => {
                const chartOptions = {
                    ...nonStackedChartOptions,
                    yAxisProps: { min: "10", max: "5" },
                };

                expect(shouldEndOnTick(chartOptions)).toBeTruthy();
            });

            it("should return false if min is set but smaller than max data value (non stacked)", () => {
                const chartOptions = {
                    ...nonStackedChartOptions,
                    yAxisProps: { min: "1" },
                };

                expect(shouldEndOnTick(chartOptions)).toBeFalsy();
            });

            it("should return false if min is set but smaller than max data value (stacked)", () => {
                const chartOptions = {
                    ...stackedChartOptions,
                    yAxisProps: { min: "1" },
                };

                expect(shouldEndOnTick(chartOptions)).toBeFalsy();
            });

            it("should return true when no max or min are set", () => {
                const chartOptions = {
                    ...nonStackedChartOptions,
                    yAxisProps: {},
                };

                expect(shouldEndOnTick(chartOptions)).toBeTruthy();
            });

            it("should return true if min is set but bigger than max data value (non stacked)", () => {
                const chartOptions = {
                    ...nonStackedChartOptions,
                    yAxisProps: { min: "40" },
                };

                expect(shouldEndOnTick(chartOptions)).toBeTruthy();
            });

            it("should return true if min is set but bigger than max data value (stacked)", () => {
                const chartOptions = {
                    ...stackedChartOptions,
                    yAxisProps: { min: "40" },
                };

                expect(shouldEndOnTick(chartOptions)).toBeTruthy();
            });
        });

        describe("shouldXAxisStartOnTickOnBubbleScatter", () => {
            it("should return true when min is not set", () => {
                const chartOptions = {
                    ...nonStackedChartOptions,
                };

                expect(shouldXAxisStartOnTickOnBubbleScatter(chartOptions)).toBeTruthy();
            });

            it("should return false when min is set", () => {
                const chartOptions = {
                    ...nonStackedChartOptions,
                    xAxisProps: { min: "40" },
                };

                expect(shouldXAxisStartOnTickOnBubbleScatter(chartOptions)).toBeFalsy();
            });
        });

        describe("shouldYAxisStartOnTickOnBubbleScatter", () => {
            it("should return true when min is not set", () => {
                const chartOptions = {
                    ...nonStackedChartOptions,
                };

                expect(shouldYAxisStartOnTickOnBubbleScatter(chartOptions)).toBeTruthy();
            });

            it("should return false when min is set", () => {
                const chartOptions = {
                    ...nonStackedChartOptions,
                    yAxisProps: { min: "10" },
                };

                expect(shouldYAxisStartOnTickOnBubbleScatter(chartOptions)).toBeFalsy();
            });

            it("should return true if min is set but bigger than max data value (non stacked)", () => {
                const chartOptions = {
                    ...nonStackedChartOptions,
                    yAxisProps: { min: "40" },
                };

                expect(shouldEndOnTick(chartOptions)).toBeTruthy();
            });
        });
    });

    describe("shouldFollowPointer", () => {
        const nonStackedChartOptions = {
            type: VisualizationTypes.COLUMN,
            yAxes: [{ title: "atitle" }],
            xAxes: [{ title: "xtitle" }],
            data: {
                series: [
                    {
                        color: "rgb(0, 0, 0)",
                        name: "<b>aaa</b>",
                        data: [
                            {
                                name: "data1",
                                y: 50,
                            },
                            {
                                name: "data2",
                                y: 150,
                            },
                            {
                                name: "data3",
                                y: -12,
                            },
                        ],
                        visible: true,
                    },
                ],
            },
        };

        const stackedChartOptions = {
            type: VisualizationTypes.COLUMN,
            hasStackByAttribute: true,
            yAxes: [{ title: "atitle" }],
            xAxes: [{ title: "xtitle" }],
            data: {
                series: [
                    {
                        color: "rgb(0, 0, 0)",
                        name: "<b>aaa</b>",
                        data: [
                            {
                                name: "data1",
                                y: 25,
                            },
                            {
                                name: "data2",
                                y: 75,
                            },
                            {
                                name: "data3",
                                y: -12,
                            },
                        ],
                        visible: true,
                    },
                    {
                        color: "rgb(0, 0, 0)",
                        name: "<b>bbb</b>",
                        data: [
                            {
                                name: "data1",
                                y: 25,
                            },
                            {
                                name: "data2",
                                y: 75,
                            },
                            {
                                name: "data3",
                                y: -12,
                            },
                        ],
                        visible: true,
                    },
                ],
            },
        };

        const stackedChartOptionsPositiveValues = {
            data: {
                series: [
                    {
                        color: "rgb(20,178,226)",
                        data: [
                            {
                                y: 27,
                                name: "AAAA",
                            },
                        ],
                        name: "AAAA",
                    },
                    {
                        color: "rgb(0,193,141)",
                        data: [
                            {
                                y: 26,
                                name: "BBBB",
                            },
                        ],
                        name: "BBBB",
                    },
                    {
                        color: "rgb(229,77,66)",
                        data: [
                            {
                                y: 26,
                                name: "CCCC",
                            },
                        ],
                        name: "CCCC",
                    },
                    {
                        color: "rgb(241,134,0)",
                        data: [
                            {
                                y: 29,
                                name: "DDDD",
                            },
                        ],
                        name: "DDDD",
                    },
                ],
            },
        };

        const stackedChartOptionsNegativeValues = {
            data: {
                series: [
                    {
                        color: "rgb(20,178,226)",
                        data: [
                            {
                                y: -27,
                                name: "AAAA",
                            },
                        ],
                        name: "AAAA",
                    },
                    {
                        color: "rgb(0,193,141)",
                        data: [
                            {
                                y: -26,
                                name: "BBBB",
                            },
                        ],
                        name: "BBBB",
                    },
                    {
                        color: "rgb(229,77,66)",
                        data: [
                            {
                                y: -26,
                                name: "CCCC",
                            },
                        ],
                        name: "CCCC",
                    },
                    {
                        color: "rgb(241,134,0)",
                        data: [
                            {
                                y: -29,
                                name: "DDDD",
                            },
                        ],
                        name: "DDDD",
                    },
                ],
            },
        };

        describe("Non stacked chart", () => {
            it("should return false when no extremes are defined", () => {
                const result = shouldFollowPointer({
                    ...nonStackedChartOptions,
                    xAxisProps: {},
                    yAxisProps: {},
                });

                expect(result).toBeFalsy();
            });

            it("should return false when data values are in axis range", () => {
                const result = shouldFollowPointer({
                    ...nonStackedChartOptions,
                    yAxisProps: {
                        min: "-30",
                        max: "200",
                    },
                });

                expect(result).toBeFalsy();
            });

            it("should return true when min is in negative value", () => {
                const result = shouldFollowPointer({
                    ...nonStackedChartOptions,
                    yAxisProps: {
                        min: "-10",
                    },
                });

                expect(result).toBeTruthy();
            });

            it("should return true when min and max are within data values", () => {
                const result = shouldFollowPointer({
                    ...nonStackedChartOptions,
                    yAxisProps: {
                        min: "60",
                        max: "100",
                    },
                });

                expect(result).toBeTruthy();
            });

            it("should return true when min is bigger than minimal value", () => {
                const result = shouldFollowPointer({
                    ...nonStackedChartOptions,
                    yAxisProps: {
                        min: "0",
                    },
                });

                expect(result).toBeTruthy();
            });

            it("should return true when min is bigger than minimal value", () => {
                const result = shouldFollowPointer({
                    ...nonStackedChartOptions,
                    yAxisProps: {
                        min: "0",
                    },
                });

                expect(result).toBeTruthy();
            });
        });

        describe("Stacked chart", () => {
            it("should return false when no extremes are defined", () => {
                const result = shouldFollowPointer({
                    ...stackedChartOptions,
                    xAxisProps: {},
                    yAxisProps: {},
                });

                expect(result).toBeFalsy();
            });

            it("should return false when data values are in axis range", () => {
                const result = shouldFollowPointer({
                    ...stackedChartOptions,
                    yAxisProps: {
                        min: "-30",
                        max: "200",
                    },
                });

                expect(result).toBeFalsy();
            });

            it("should return true when min and max are within data values", () => {
                const result = shouldFollowPointer({
                    ...stackedChartOptions,
                    yAxisProps: {
                        min: "60",
                        max: "100",
                    },
                });

                expect(result).toBeTruthy();
            });

            it("should return true when min is bigger 0 and less than min seriesValue", () => {
                const result = shouldFollowPointer({
                    ...stackedChartOptionsPositiveValues,
                    yAxisProps: {
                        min: "20",
                    },
                });

                expect(result).toBeTruthy();
            });

            it("should return true when max is negative and max less than min seriesValue", () => {
                const result = shouldFollowPointer({
                    ...stackedChartOptionsNegativeValues,
                    yAxisProps: {
                        max: "-20",
                    },
                });

                expect(result).toBeTruthy();
            });
        });
    });

    describe("shouldFollowPointerForAxes", () => {
        const dualAxesChartOptions = {
            type: VisualizationTypes.COLUMN,
            yAxes: [{ title: "atitle" }, { title: "btitle" }],
            xAxes: [{ title: "xtitle" }],
            data: {
                series: [
                    {
                        color: "rgb(0, 0, 0)",
                        name: "aaa",
                        data: [
                            {
                                name: "data1",
                                y: 25,
                            },
                            {
                                name: "data2",
                                y: 75,
                            },
                            {
                                name: "data3",
                                y: -12,
                            },
                        ],
                        visible: true,
                    },
                    {
                        color: "rgb(0, 0, 0)",
                        name: "bbb",
                        data: [
                            {
                                name: "data1",
                                y: 25,
                            },
                            {
                                name: "data2",
                                y: 75,
                            },
                            {
                                name: "data3",
                                y: -12,
                            },
                        ],
                        yAxis: 1,
                        visible: true,
                    },
                ],
            },
        };

        it("should return false when there is one Y axis", () => {
            const chartOptions = {
                ...dualAxesChartOptions,
                yAxes: [{ title: "atitle" }],
            };
            const result = shouldFollowPointerForDualAxes(chartOptions);
            expect(result).toBeFalsy();
        });

        it("should return false when min/max is not set", () => {
            const result = shouldFollowPointerForDualAxes(dualAxesChartOptions);
            expect(result).toBeFalsy();
        });

        it("should return true when min/max is set in left Y axis", () => {
            const chartOptions = {
                ...dualAxesChartOptions,
                yAxisProps: {
                    min: "10",
                    max: "20",
                },
            };
            const result = shouldFollowPointerForDualAxes(chartOptions);
            expect(result).toBeTruthy();
        });

        it("should return true when min/max is set in right Y axis", () => {
            const chartOptions = {
                ...dualAxesChartOptions,
                secondary_yAxisProps: {
                    min: "10",
                    max: "20",
                },
            };
            const result = shouldFollowPointerForDualAxes(chartOptions);
            expect(result).toBeTruthy();
        });

        it("should return true when min/max is set in both Y axes", () => {
            const chartOptions = {
                ...dualAxesChartOptions,
                yAxisProps: {
                    min: "10",
                    max: "20",
                },
                secondary_yAxisProps: {
                    min: "10",
                    max: "20",
                },
            };
            const result = shouldFollowPointerForDualAxes(chartOptions);
            expect(result).toBeTruthy();
        });
    });

    describe("pointInRange", () => {
        it("should return true when data point is in the axis range", () => {
            expect(
                pointInRange(5, {
                    minAxisValue: 2,
                    maxAxisValue: 8,
                }),
            ).toBeTruthy();
        });

        it("should return true when data point is on the left edge of axis range", () => {
            expect(
                pointInRange(5, {
                    minAxisValue: 5,
                    maxAxisValue: 8,
                }),
            ).toBeTruthy();
        });

        it("should return true when data point is on the right edge of axis range", () => {
            expect(
                pointInRange(5, {
                    minAxisValue: 2,
                    maxAxisValue: 5,
                }),
            ).toBeTruthy();
        });

        it("should return false when data point is outside of axis range", () => {
            expect(
                pointInRange(5, {
                    minAxisValue: -5,
                    maxAxisValue: 2,
                }),
            ).toBeFalsy();
        });
    });
    describe("getStackedExtremeValues", () => {
        const seriesNegativeAndPositiveValues = [
            {
                color: "rgb(0, 0, 0)",
                name: "<b>aaa</b>",
                data: [
                    {
                        name: "data1",
                        y: 25,
                    },
                    {
                        name: "data2",
                        y: 75,
                    },
                    {
                        name: "data3",
                        y: 30,
                    },
                ],
                visible: true,
            },
            {
                color: "rgb(0, 0, 0)",
                name: "<b>bbb</b>",
                data: [
                    {
                        name: "data1",
                        y: -25,
                    },
                    {
                        name: "data2",
                        y: -75,
                    },
                    {
                        name: "data3",
                        y: -30,
                    },
                ],
                visible: true,
            },
            {
                color: "rgb(0, 0, 0)",
                name: "<b>bbb</b>",
                data: [
                    {
                        name: "data1",
                        y: 15,
                    },
                    {
                        name: "data2",
                        y: 60,
                    },
                    {
                        name: "data3",
                        y: 20,
                    },
                ],
                visible: true,
            },
            {
                color: "rgb(0, 0, 0)",
                name: "<b>bbb</b>",
                data: [
                    {
                        name: "data1",
                        y: -15,
                    },
                    {
                        name: "data2",
                        y: -60,
                    },
                    {
                        name: "data3",
                        y: -20,
                    },
                ],
                visible: true,
            },
        ];

        const negativeValues = [
            {
                color: "rgb(0, 0, 0)",
                name: "<b>bbb</b>",
                data: [
                    {
                        name: "data1",
                        y: -25,
                    },
                    {
                        name: "data2",
                        y: -75,
                    },
                    {
                        name: "data3",
                        y: -30,
                    },
                ],
                visible: true,
            },
            {
                color: "rgb(0, 0, 0)",
                name: "<b>bbb</b>",
                data: [
                    {
                        name: "data1",
                        y: -15,
                    },
                    {
                        name: "data2",
                        y: -60,
                    },
                    {
                        name: "data3",
                        y: -20,
                    },
                ],
                visible: true,
            },
        ];

        describe("getStackedMaxValue", () => {
            it("should match output for negative and positive values", () => {
                const result = getStackedMaxValue(seriesNegativeAndPositiveValues);
                expect(result).toEqual(135);
            });

            it("should match output for negative values", () => {
                const result = getStackedMaxValue(negativeValues);
                expect(result).toEqual(-15);
            });
        });

        describe("getStackedMinValue", () => {
            it("should match output for negative and positive values", () => {
                const result = getStackedMinValue(seriesNegativeAndPositiveValues);
                expect(result).toEqual(-135);
            });

            it("should match output for negative values", () => {
                const result = getStackedMinValue(negativeValues);
                expect(result).toEqual(-135);
            });
        });
    });

    describe("getChartAlignmentConfiguration", () => {
        function getCommonChartOptionsMock(width: number, height: number) {
            const getBoundingClientRect = jest.fn().mockReturnValue({ width, height });
            return {
                options: {
                    chart: {
                        type: VisualizationTypes.DONUT,
                    },
                },
                container: {
                    getBoundingClientRect,
                },
                update: jest.fn(),
            };
        }

        it.each([[TOP, 0, undefined, undefined, 100], [BOTTOM, undefined, 0, 100, undefined]])(
            "should update chart margin %s",
            (
                verticalAlign: ChartAlignTypes,
                spacingTop: number,
                spacingBottom: number,
                marginTop: number,
                marginBottom: number,
            ) => {
                const chart: any = {
                    ...getCommonChartOptionsMock(200, 300),
                    userOptions: {
                        chart: {
                            verticalAlign,
                        },
                    },
                };

                alignChart(chart);

                expect(chart.update).toBeCalledWith(
                    {
                        chart: {
                            spacingTop,
                            spacingBottom,
                            marginTop,
                            marginBottom,
                            className: `s-highcharts-donut-aligned-to-${verticalAlign}`,
                        },
                    },
                    false,
                    false,
                    false,
                );
            },
        );

        it("should not update when rectangle container is horizontal", () => {
            const chart: any = {
                ...getCommonChartOptionsMock(300, 200),
            };

            alignChart(chart);

            expect(chart.update).toBeCalledWith(
                {
                    chart: {
                        spacingTop: undefined,
                        spacingBottom: undefined,
                        marginTop: undefined,
                        marginBottom: undefined,
                        className: "s-highcharts-donut-aligned-to-middle",
                    },
                },
                false,
                false,
                false,
            );
        });

        it.each([["middle"], [undefined]])(
            "should not update when verticalAlign is %s",
            (verticalAlign: ChartAlignTypes) => {
                const chart: any = {
                    ...getCommonChartOptionsMock(200, 300),
                    userOptions: {
                        chart: {
                            verticalAlign,
                        },
                    },
                };

                alignChart(chart);

                expect(chart.update).toBeCalledWith(
                    {
                        chart: {
                            spacingTop: undefined,
                            spacingBottom: undefined,
                            marginTop: undefined,
                            marginBottom: undefined,
                            className: "s-highcharts-donut-aligned-to-middle",
                        },
                    },
                    false,
                    false,
                    false,
                );
            },
        );
    });
});
