/*
 * Copyright 2022-present MongoDB, Inc.
 *
 * 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.
 */

#include "test-mongocrypt.h"

#include "mc-optional-private.h"
#include "mc-range-edge-generation-private.h"

#include <float.h> // DBL_MIN

#define MAX_INT32_EDGES 33

typedef struct {
    int32_t value;
    mc_optional_int32_t min;
    mc_optional_int32_t max;
    size_t sparsity;
    // expectEdges includes a trailing NULL pointer.
    const char *expectEdges[MAX_INT32_EDGES + 1];
    const char *expectError;
    int32_t trimFactor;
} Int32Test;

#undef MAX_INT32_EDGES

static void print_edges_compared(mc_edges_t *edgesGot, const char *const *edgesExpected) {
    TEST_STDERR_PRINTF("edges got ... begin\n");
    for (size_t i = 0; i < mc_edges_len(edgesGot); i++) {
        TEST_STDERR_PRINTF("  %s\n", mc_edges_get(edgesGot, i));
    }
    TEST_STDERR_PRINTF("edges got ... end\n");

    TEST_STDERR_PRINTF("edges expected ... begin\n");
    const char *const *iter = edgesExpected;
    while (*iter != NULL) {
        TEST_STDERR_PRINTF("  %s\n", *iter);
        iter++;
    }
    TEST_STDERR_PRINTF("edges expected ... end\n");
}

static void _test_getEdgesInt32(_mongocrypt_tester_t *tester) {
    static const Int32Test tests[] = {
        {.value = 2,
         .min = OPT_I32_C(0),
         .max = OPT_I32_C(7),
         .sparsity = 1,
         .expectEdges = {"root", "010", "0", "01"}},
        {.value = 2, .min = OPT_I32_C(0), .max = OPT_I32_C(7), .sparsity = 2, .expectEdges = {"root", "010", "01"}},
        {.value = 1, .sparsity = 0, .expectError = "sparsity must be 1 or larger"},
        {.value = 2,
         .min = OPT_I32_C(0),
         .max = OPT_I32_C(7),
         .sparsity = 1,
         .trimFactor = 1,
         .expectEdges = {"010", "0", "01"}},
        {.value = 2,
         .min = OPT_I32_C(0),
         .max = OPT_I32_C(7),
         .sparsity = 1,
         .trimFactor = 2,
         .expectEdges = {"010", "01"}},
        {.value = 2,
         .min = OPT_I32_C(0),
         .max = OPT_I32_C(7),
         .sparsity = 1,
         .trimFactor = 3,
         .expectError =
             "trimFactor must be less than the number of bits (3) used to represent an element of the domain"},
        {.value = 2,
         .min = OPT_I32_C(0),
         .max = OPT_I32_C(7),
         .sparsity = 1,
         .trimFactor = -1,
         .expectError = "trimFactor must be >= 0"},
        {.value = 2,
         .min = OPT_I32_C(0),
         .max = OPT_I32_C(7),
         .sparsity = 2,
         .trimFactor = 0,
         .expectEdges = {"root", "010", "01"}},
        {.value = 2,
         .min = OPT_I32_C(0),
         .max = OPT_I32_C(7),
         .sparsity = 2,
         .trimFactor = 1,
         .expectEdges = {"010", "01"}},
        {.value = 2,
         .min = OPT_I32_C(0),
         .max = OPT_I32_C(7),
         .sparsity = 2,
         .trimFactor = 2,
         .expectEdges = {"010", "01"}},
#include "data/range-edge-generation/edges_int32.cstruct"
    };

    for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
        mongocrypt_status_t *const status = mongocrypt_status_new();
        const Int32Test *test = tests + i;
        mc_getEdgesInt32_args_t args = {.value = test->value,
                                        .min = test->min,
                                        .max = test->max,
                                        .sparsity = test->sparsity,
                                        .trimFactor = OPT_I32(test->trimFactor)};
        mc_edges_t *got = mc_getEdgesInt32(args, status);
        if (test->expectError != NULL) {
            ASSERT_OR_PRINT_MSG(NULL == got, "expected error, got success");
            ASSERT_STATUS_CONTAINS(status, test->expectError);
            mongocrypt_status_destroy(status);
            continue;
        }
        ASSERT_OK_STATUS(got != NULL, status);

        size_t numGot = mc_edges_len(got);
        size_t numExpected = 0;
        while (test->expectEdges[numExpected] != NULL) {
            ++numExpected;
        }

        if (numExpected != numGot) {
            print_edges_compared(got, test->expectEdges);
            TEST_ERROR("got %zu edges, expected %zu edges\n", numGot, numExpected);
        }

        for (size_t gotI = 0; gotI < numGot; gotI++) {
            const char *edgeGot = mc_edges_get(got, gotI);
            const char *edgeExpected = test->expectEdges[gotI];
            if (0 == strcmp(edgeGot, edgeExpected)) {
                continue;
            }
            print_edges_compared(got, test->expectEdges);
            TEST_ERROR("edge mismatch at index %zu. %s != %s\n", gotI, edgeGot, edgeExpected);
        }
        mc_edges_destroy(got);
        mongocrypt_status_destroy(status);
    }
}

#define MAX_INT64_EDGES 65

typedef struct {
    int64_t value;
    mc_optional_int64_t min;
    mc_optional_int64_t max;
    size_t sparsity;
    // expectEdges includes a trailing NULL pointer.
    const char *expectEdges[MAX_INT64_EDGES + 1];
    const char *expectError;
    uint32_t trimFactor;
} Int64Test;

#undef MAX_INT64_EDGES

static void _test_getEdgesInt64(_mongocrypt_tester_t *tester) {
    static const Int64Test tests[] = {
        {.value = INT64_C(2),
         .min = OPT_I64_C(0),
         .max = OPT_I64_C(7),
         .sparsity = 1,
         .expectEdges = {"root", "010", "0", "01"}},
        {.value = INT64_C(2),
         .min = OPT_I64_C(0),
         .max = OPT_I64_C(7),
         .sparsity = 2,
         .expectEdges = {"root", "010", "01"}},
        {.value = INT64_C(2),
         .min = OPT_I64_C(0),
         .max = OPT_I64_C(7),
         .sparsity = 1,
         .trimFactor = 1,
         .expectEdges = {"010", "0", "01"}},
        {.value = INT64_C(2),
         .min = OPT_I64_C(0),
         .max = OPT_I64_C(7),
         .sparsity = 1,
         .trimFactor = 2,
         .expectEdges = {"010", "01"}},
        {.value = INT64_C(2),
         .min = OPT_I64_C(0),
         .max = OPT_I64_C(7),
         .sparsity = 1,
         .trimFactor = 3,
         .expectError =
             "trimFactor must be less than the number of bits (3) used to represent an element of the domain"},
        {.value = 1, .sparsity = 0, .expectError = "sparsity must be 1 or larger"},
#include "data/range-edge-generation/edges_int64.cstruct"
    };

    for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
        mongocrypt_status_t *const status = mongocrypt_status_new();
        const Int64Test *test = tests + i;
        mc_getEdgesInt64_args_t args = {.value = test->value,
                                        .min = test->min,
                                        .max = test->max,
                                        .sparsity = test->sparsity,
                                        .trimFactor = OPT_I32(test->trimFactor)};
        mc_edges_t *got = mc_getEdgesInt64(args, status);
        if (test->expectError != NULL) {
            ASSERT_OR_PRINT_MSG(NULL == got, "expected error, got success");
            ASSERT_STATUS_CONTAINS(status, test->expectError);
            mongocrypt_status_destroy(status);
            continue;
        }
        ASSERT_OK_STATUS(got != NULL, status);

        size_t numGot = mc_edges_len(got);
        size_t numExpected = 0;
        while (test->expectEdges[numExpected] != NULL) {
            ++numExpected;
        }

        if (numExpected != numGot) {
            print_edges_compared(got, test->expectEdges);
            TEST_ERROR("got %zu edges, expected %zu edges\n", numGot, numExpected);
        }

        for (size_t gotI = 0; gotI < numGot; gotI++) {
            const char *edgeGot = mc_edges_get(got, gotI);
            const char *edgeExpected = test->expectEdges[gotI];
            if (0 == strcmp(edgeGot, edgeExpected)) {
                continue;
            }
            print_edges_compared(got, test->expectEdges);
            TEST_ERROR("edge mismatch at index %zu. %s != %s\n", gotI, edgeGot, edgeExpected);
        }
        mc_edges_destroy(got);
        mongocrypt_status_destroy(status);
    }
}

#define MAX_DOUBLE_EDGES 65

typedef struct {
    double value;
    mc_optional_double_t min; // Unused. Kept for consistency with server test data.
    mc_optional_double_t max; // Unused. Kept for consistency with server test data.
    size_t sparsity;
    // expectEdges includes a trailing NULL pointer.
    const char *expectEdges[MAX_DOUBLE_EDGES + 1];
    const char *expectError;
} DoubleTest;

#undef MAX_DOUBLE_EDGES

static void _test_getEdgesDouble(_mongocrypt_tester_t *tester) {
    static const DoubleTest tests[] = {
#include "data/range-edge-generation/edges_double.cstruct"
    };

    for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
        mongocrypt_status_t *const status = mongocrypt_status_new();
        const DoubleTest *test = tests + i;
        const uint32_t trimFactor = 0; // At present, all test cases expect trimFactor=0.
        mc_getEdgesDouble_args_t args = {.value = test->value,
                                         .sparsity = test->sparsity,
                                         .trimFactor = OPT_I32(trimFactor)};
        mc_edges_t *got = mc_getEdgesDouble(args, status);

        if (test->expectError != NULL) {
            if (NULL != got) {
                TEST_ERROR("test %zu expected error, got success", i);
            }
            ASSERT_STATUS_CONTAINS(status, test->expectError);
            mongocrypt_status_destroy(status);
            continue;
        }
        ASSERT_OK_STATUS(got != NULL, status);

        size_t numGot = mc_edges_len(got);
        size_t numExpected = 0;
        while (test->expectEdges[numExpected] != NULL) {
            ++numExpected;
        }

        if (numExpected != numGot) {
            print_edges_compared(got, test->expectEdges);
            TEST_ERROR("test %zu got %zu edges, expected %zu edges\n", i, numGot, numExpected);
        }

        for (size_t gotI = 0; gotI < numGot; gotI++) {
            const char *edgeGot = mc_edges_get(got, gotI);
            const char *edgeExpected = test->expectEdges[gotI];
            if (0 == strcmp(edgeGot, edgeExpected)) {
                continue;
            }
            print_edges_compared(got, test->expectEdges);
            TEST_ERROR("test %zu got edge mismatch at index %zu. %s != %s\n", i, gotI, edgeGot, edgeExpected);
        }
        mc_edges_destroy(got);
        mongocrypt_status_destroy(status);
    }
}

#if MONGOCRYPT_HAVE_DECIMAL128_SUPPORT()
#define MAX_DEC128_EDGES 129

typedef struct {
    mc_dec128 value;
    mc_optional_dec128_t min;
    mc_optional_dec128_t max;
    int sparsity;
    // expectEdges includes a trailing NULL pointer.
    const char *expectEdges[MAX_DEC128_EDGES + 1];
    const char *expectError;
} Decimal128Test;

#undef MAX_DEC128_EDGES

static void _test_getEdgesDecimal128(_mongocrypt_tester_t *tester) {
    Decimal128Test tests[] = {
#include "data/range-edge-generation/edges_decimal128.cstruct"
    };

    for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
        const Decimal128Test *test = tests + i;
        mongocrypt_status_t *const status = mongocrypt_status_new();
        const uint32_t trimFactor = 0; // At present, all test cases expect trimFactor=0.
        mc_getEdgesDecimal128_args_t args = {.value = test->value,
                                             // Some edges specify min/max values, but we don't use them (yet)
                                             //  .min = test->min,
                                             //  .max = test->max,
                                             .sparsity = (size_t)test->sparsity,
                                             .trimFactor = OPT_I32(trimFactor)};
        mc_edges_t *got = mc_getEdgesDecimal128(args, status);

        if (test->expectError != NULL) {
            if (NULL != got) {
                TEST_ERROR("test %zu expected error, got success", i);
            }
            ASSERT_STATUS_CONTAINS(status, test->expectError);
            mongocrypt_status_destroy(status);
            continue;
        }
        ASSERT_OK_STATUS(got != NULL, status);

        size_t numGot = mc_edges_len(got);
        size_t numExpected = 0;
        while (test->expectEdges[numExpected] != NULL) {
            ++numExpected;
        }

        if (numExpected != numGot) {
            print_edges_compared(got, test->expectEdges);
            TEST_ERROR("test %zu got %zu edges, expected %zu edges\n", i, numGot, numExpected);
        }
        for (size_t gotI = 0; gotI < numGot; gotI++) {
            const char *edgeGot = mc_edges_get(got, gotI);
            const char *edgeExpected = test->expectEdges[gotI];
            if (0 == strcmp(edgeGot, edgeExpected)) {
                continue;
            }
            print_edges_compared(got, test->expectEdges);
            TEST_ERROR("test %zu got edge mismatch at index %zu. (actual) '%s' "
                       "!= '%s' (expected)\n",
                       i,
                       gotI,
                       edgeGot,
                       edgeExpected);
        }
        mc_edges_destroy(got);
        mongocrypt_status_destroy(status);
    }
}
#endif // MONGOCRYPT_HAVE_DECIMAL128_SUPPORT

static void _test_count_leading_zeros(_mongocrypt_tester_t *tester) {
    ASSERT_CMPSIZE_T(mc_count_leading_zeros_u64(UINT64_C(0)), ==, 64);
    ASSERT_CMPSIZE_T(mc_count_leading_zeros_u64(UINT64_C(1)), ==, 63);
    ASSERT_CMPSIZE_T(mc_count_leading_zeros_u64(UINT64_MAX), ==, 0);
    ASSERT_CMPSIZE_T(mc_count_leading_zeros_u64((~UINT64_C(0)) >> 1), ==, 1);

    ASSERT_CMPSIZE_T(mc_count_leading_zeros_u32(UINT32_C(0)), ==, 32);
    ASSERT_CMPSIZE_T(mc_count_leading_zeros_u32(UINT32_C(1)), ==, 31);
    ASSERT_CMPSIZE_T(mc_count_leading_zeros_u32(UINT32_MAX), ==, 0);
    ASSERT_CMPSIZE_T(mc_count_leading_zeros_u32((~UINT32_C(0)) >> 1), ==, 1);

    ASSERT_CMPSIZE_T(mc_count_leading_zeros_u128(MLIB_INT128(0)), ==, 128);
    ASSERT_CMPSIZE_T(mc_count_leading_zeros_u128(MLIB_INT128(8)), ==, 124);
    ASSERT_CMPSIZE_T(mc_count_leading_zeros_u128((mlib_int128)MLIB_INT128_FROM_PARTS(0, 8)), ==, 60);
}

typedef struct {
    uint32_t in;
    const char *expect;
} bitstring_u32_test;

typedef struct {
    uint64_t in;
    const char *expect;
} bitstring_u64_test;

typedef struct {
    mlib_int128 in;
    const char *expect;
} bitstring_u128_test;

static void _test_convert_to_bitstring(_mongocrypt_tester_t *tester) {
    // Test uint32_t.
    {
        bitstring_u32_test tests[] = {{.in = 0, .expect = "00000000000000000000000000000000"},
                                      {.in = 1, .expect = "00000000000000000000000000000001"},
                                      {.in = 123, .expect = "00000000000000000000000001111011"},
                                      {.in = UINT32_MAX, .expect = "11111111111111111111111111111111"},
                                      {.in = UINT32_MAX - 1u, .expect = "11111111111111111111111111111110"}};
        for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
            bitstring_u32_test *test = tests + i;
            mc_bitstring got = mc_convert_to_bitstring_u32(test->in);
            ASSERT_STREQUAL(test->expect, got.str);
        }
    }
    // Test uint64_t.
    {
        bitstring_u64_test tests[] = {{.in = 0,
                                       .expect = "0000000000000000000000000000000000000000000000000000000000"
                                                 "000000"},
                                      {.in = 1,
                                       .expect = "0000000000000000000000000000000000000000000000000000000000"
                                                 "000001"},
                                      {.in = 123,
                                       .expect = "0000000000000000000000000000000000000000000000000000000001"
                                                 "111011"},
                                      {.in = UINT64_MAX,
                                       .expect = "1111111111111111111111111111111111111111111111111111111111"
                                                 "111111"},
                                      {.in = UINT64_MAX - 1u,
                                       .expect = "1111111111111111111111111111111111111111111111111111111111"
                                                 "111110"}};
        for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
            bitstring_u64_test *test = tests + i;
            mc_bitstring got = mc_convert_to_bitstring_u64(test->in);
            ASSERT_STREQUAL(test->expect, got.str);
        }
    }
    // Tests for u128
    {
        bitstring_u128_test tests[] = {
            {
                .in = MLIB_INT128(0),
                .expect = "00000000000000000000000000000000000000000000000000000000"
                          "00000000000000000000000000000000000000000000000000000000"
                          "0000000000000000",
            },
            {
                .in = MLIB_INT128(1),
                .expect = "00000000000000000000000000000000000000000000000000000000"
                          "00000000000000000000000000000000000000000000000000000000"
                          "0000000000000001",
            },
            {
                .in = MLIB_INT128(256),
                .expect = "00000000000000000000000000000000000000000000000000000000"
                          "00000000000000000000000000000000000000000000000000000000"
                          "0000000100000000",
            },
            {
                .in = mlib_int128_from_string("0b1011010010001011101010010100101010010101010101001010010100100"
                                              "101010101010010001010011010110110100110010101010010101001111010"
                                              "1011",
                                              NULL),
                .expect = "10110100100010111010100101001010100101010101010010100101"
                          "00100101010101010010001010011010110110100110010101010010"
                          "1010011110101011",
            },
        };
        for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
            bitstring_u128_test *test = tests + i;
            mc_bitstring got = mc_convert_to_bitstring_u128(test->in);
            ASSERT_STREQUAL(test->expect, got.str);
        }
    }
}

void _mongocrypt_tester_install_range_edge_generation(_mongocrypt_tester_t *tester) {
    INSTALL_TEST(_test_getEdgesInt32);
    INSTALL_TEST(_test_getEdgesInt64);
    INSTALL_TEST(_test_getEdgesDouble);
#if MONGOCRYPT_HAVE_DECIMAL128_SUPPORT()
    INSTALL_TEST(_test_getEdgesDecimal128);
#endif
    INSTALL_TEST(_test_count_leading_zeros);
    INSTALL_TEST(_test_convert_to_bitstring);
}
