/*
 * 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 <bson/bson.h>

#include "mc-check-conversions-private.h"

#include "mc-array-private.h"
#include "mc-cmp-private.h"
#include "mc-range-edge-generation-private.h" // mc_count_leading_zeros_u32
#include "mc-range-encoding-private.h"        // mc_getTypeInfo32, trimFactorDefault
#include "mc-range-mincover-private.h"
#include "mongocrypt-private.h"

struct _mc_mincover_t {
    /* mincover is an array of `char*` edge strings. */
    mc_array_t mincover;
    int32_t usedTrimFactor; // The `trimFactor` that was used to produce this mincover.
};

static mc_mincover_t *mc_mincover_new(void) {
    mc_mincover_t *mincover = bson_malloc0(sizeof(mc_mincover_t));
    _mc_array_init(&mincover->mincover, sizeof(char *));
    return mincover;
}

const char *mc_mincover_get(mc_mincover_t *mincover, size_t index) {
    BSON_ASSERT_PARAM(mincover);
    if (mincover->mincover.len == 0 || index > mincover->mincover.len - 1u) {
        return NULL;
    }
    return _mc_array_index(&mincover->mincover, char *, index);
}

size_t mc_mincover_len(mc_mincover_t *mincover) {
    BSON_ASSERT_PARAM(mincover);
    return mincover->mincover.len;
}

int32_t mc_mincover_get_used_trimFactor(const mc_mincover_t *mincover) {
    return mincover->usedTrimFactor;
}

void mc_mincover_destroy(mc_mincover_t *mincover) {
    if (NULL == mincover) {
        return;
    }
    for (size_t i = 0; i < mincover->mincover.len; i++) {
        char *val = _mc_array_index(&mincover->mincover, char *, i);
        bson_free(val);
    }
    _mc_array_destroy(&mincover->mincover);
    bson_free(mincover);
}

#define UINT_T uint32_t
#define UINT_C UINT32_C
#define UINT_FMT_S PRIu32
#define DECORATE_NAME(N) N##_u32
#include "mc-range-mincover-generator.template.h"

#define UINT_T uint64_t
#define UINT_C UINT64_C
#define UINT_FMT_S PRIu64
#define DECORATE_NAME(N) N##_u64
#include "mc-range-mincover-generator.template.h"

// The 128-bit version is only required for Decimal128, otherwise generates
// unused-fn warnings
#if MONGOCRYPT_HAVE_DECIMAL128_SUPPORT
#define UINT_T mlib_int128
#define UINT_C MLIB_INT128
#define UINT_FMT_S "s"
#define UINT_FMT_ARG(X) (mlib_int128_format(X).str)
#define DECORATE_NAME(N) N##_u128
#define UINT_LESSTHAN(L, R) (mlib_int128_ucmp(L, R) < 0)
#define UINT_ADD mlib_int128_add
#define UINT_SUB mlib_int128_sub
#define UINT_LSHIFT mlib_int128_lshift
#define MC_UINT_MAX MLIB_INT128_UMAX
#define UINT_BITOR mlib_int128_bitor
#include "mc-range-mincover-generator.template.h"
#endif // MONGOCRYPT_HAVE_DECIMAL128_SUPPORT

// Check bounds and return an error message including the original inputs.
#define IDENTITY(X) X
#define LESSTHAN(L, R) ((L) < (R))
#define CHECK_BOUNDS(args, FMT, FormatArg, LessThan)                                                                   \
    if (1) {                                                                                                           \
        if ((args).min.set) {                                                                                          \
            if (LessThan((args).upperBound, (args).min.value)) {                                                       \
                CLIENT_ERR("Upper bound (%" FMT ") must be greater than or equal to the range minimum (%" FMT ")",     \
                           FormatArg((args).upperBound),                                                               \
                           FormatArg((args).min.value));                                                               \
                return false;                                                                                          \
            }                                                                                                          \
            if (!(args).includeUpperBound && !LessThan((args.min.value), (args.upperBound))) {                         \
                CLIENT_ERR("Upper bound (%" FMT ") must be greater than the range minimum (%" FMT                      \
                           ") if upper bound is excluded from range",                                                  \
                           FormatArg((args).upperBound),                                                               \
                           FormatArg((args).min.value));                                                               \
                return false;                                                                                          \
            }                                                                                                          \
        }                                                                                                              \
        if ((args).max.set) {                                                                                          \
            if (LessThan((args).max.value, (args).lowerBound)) {                                                       \
                CLIENT_ERR("Lower bound (%" FMT ") must be less than or equal to the range maximum (%" FMT ")",        \
                           FormatArg((args).lowerBound),                                                               \
                           FormatArg((args).max.value));                                                               \
                return false;                                                                                          \
            }                                                                                                          \
            if (!(args).includeLowerBound && !LessThan((args).lowerBound, (args).max.value)) {                         \
                CLIENT_ERR("Lower bound (%" FMT ") must be less than the range maximum (%" FMT                         \
                           ") if lower bound is excluded from range",                                                  \
                           FormatArg((args).lowerBound),                                                               \
                           FormatArg((args).max.value));                                                               \
                return false;                                                                                          \
            }                                                                                                          \
        }                                                                                                              \
    } else                                                                                                             \
        (void)0

mc_mincover_t *mc_getMincoverInt32(mc_getMincoverInt32_args_t args, mongocrypt_status_t *status, bool use_range_v2) {
    BSON_ASSERT_PARAM(status);
    CHECK_BOUNDS(args, PRId32, IDENTITY, LESSTHAN);
    mc_OSTType_Int32 a, b;
    if (!mc_getTypeInfo32((mc_getTypeInfo32_args_t){.min = args.min, .max = args.max, .value = args.lowerBound},
                          &a,
                          status)) {
        return NULL;
    }
    if (!mc_getTypeInfo32((mc_getTypeInfo32_args_t){.min = args.min, .max = args.max, .value = args.upperBound},
                          &b,
                          status)) {
        return NULL;
    }

    BSON_ASSERT(a.min == b.min);
    BSON_ASSERT(a.max == b.max);

    if (!adjustBounds_u32(&a.value, args.includeLowerBound, a.min, &b.value, args.includeUpperBound, b.max, status)) {
        return NULL;
    }

    MinCoverGenerator_u32 *mcg =
        MinCoverGenerator_new_u32(a.value, b.value, a.max, args.sparsity, args.trimFactor, status, use_range_v2);
    if (!mcg) {
        return NULL;
    }
    mc_mincover_t *mc = MinCoverGenerator_minCover_u32(mcg);
    mc->usedTrimFactor = MinCoverGenerator_usedTrimFactor_u32(mcg);
    MinCoverGenerator_destroy_u32(mcg);
    return mc;
}

mc_mincover_t *mc_getMincoverInt64(mc_getMincoverInt64_args_t args, mongocrypt_status_t *status, bool use_range_v2) {
    BSON_ASSERT_PARAM(status);
    CHECK_BOUNDS(args, PRId64, IDENTITY, LESSTHAN);
    mc_OSTType_Int64 a, b;
    if (!mc_getTypeInfo64((mc_getTypeInfo64_args_t){.min = args.min, .max = args.max, .value = args.lowerBound},
                          &a,
                          status)) {
        return NULL;
    }
    if (!mc_getTypeInfo64((mc_getTypeInfo64_args_t){.min = args.min, .max = args.max, .value = args.upperBound},
                          &b,
                          status)) {
        return NULL;
    }

    BSON_ASSERT(a.min == b.min);
    BSON_ASSERT(a.max == b.max);

    if (!adjustBounds_u64(&a.value, args.includeLowerBound, a.min, &b.value, args.includeUpperBound, b.max, status)) {
        return NULL;
    }

    MinCoverGenerator_u64 *mcg =
        MinCoverGenerator_new_u64(a.value, b.value, a.max, args.sparsity, args.trimFactor, status, use_range_v2);
    if (!mcg) {
        return NULL;
    }
    mc_mincover_t *mc = MinCoverGenerator_minCover_u64(mcg);
    mc->usedTrimFactor = MinCoverGenerator_usedTrimFactor_u64(mcg);
    MinCoverGenerator_destroy_u64(mcg);
    return mc;
}

// mc_getMincoverDouble implements the Mincover Generation algorithm described
// in SERVER-68600 for double.
mc_mincover_t *mc_getMincoverDouble(mc_getMincoverDouble_args_t args, mongocrypt_status_t *status, bool use_range_v2) {
    BSON_ASSERT_PARAM(status);
    CHECK_BOUNDS(args, "g", IDENTITY, LESSTHAN);

    mc_OSTType_Double a, b;
    if (!mc_getTypeInfoDouble((mc_getTypeInfoDouble_args_t){.value = args.lowerBound,
                                                            .min = args.min,
                                                            .max = args.max,
                                                            .precision = args.precision},
                              &a,
                              status,
                              use_range_v2)) {
        return NULL;
    }
    if (!mc_getTypeInfoDouble((mc_getTypeInfoDouble_args_t){.value = args.upperBound,
                                                            .min = args.min,
                                                            .max = args.max,
                                                            .precision = args.precision},
                              &b,
                              status,
                              use_range_v2)) {
        return NULL;
    }

    BSON_ASSERT(a.min == b.min);
    BSON_ASSERT(a.max == b.max);

    if (!adjustBounds_u64(&a.value, args.includeLowerBound, a.min, &b.value, args.includeUpperBound, b.max, status)) {
        return NULL;
    }

    MinCoverGenerator_u64 *mcg =
        MinCoverGenerator_new_u64(a.value, b.value, a.max, args.sparsity, args.trimFactor, status, use_range_v2);
    if (!mcg) {
        return NULL;
    }
    mc_mincover_t *mc = MinCoverGenerator_minCover_u64(mcg);
    mc->usedTrimFactor = MinCoverGenerator_usedTrimFactor_u64(mcg);
    MinCoverGenerator_destroy_u64(mcg);
    return mc;
}

#if MONGOCRYPT_HAVE_DECIMAL128_SUPPORT
mc_mincover_t *
mc_getMincoverDecimal128(mc_getMincoverDecimal128_args_t args, mongocrypt_status_t *status, bool use_range_v2) {
    BSON_ASSERT_PARAM(status);
#define ToString(Dec) (mc_dec128_to_string(Dec).str)
    CHECK_BOUNDS(args, "s", ToString, mc_dec128_less);

    mc_OSTType_Decimal128 a, b;
    if (!mc_getTypeInfoDecimal128((mc_getTypeInfoDecimal128_args_t){.value = args.lowerBound,
                                                                    .min = args.min,
                                                                    .max = args.max,
                                                                    .precision = args.precision},
                                  &a,
                                  status,
                                  use_range_v2)) {
        return NULL;
    }
    if (!mc_getTypeInfoDecimal128((mc_getTypeInfoDecimal128_args_t){.value = args.upperBound,
                                                                    .min = args.min,
                                                                    .max = args.max,
                                                                    .precision = args.precision},
                                  &b,
                                  status,
                                  use_range_v2)) {
        return NULL;
    }

    BSON_ASSERT(mlib_int128_eq(a.min, b.min));
    BSON_ASSERT(mlib_int128_eq(a.max, b.max));

    if (!adjustBounds_u128(&a.value, args.includeLowerBound, a.min, &b.value, args.includeUpperBound, b.max, status)) {
        return NULL;
    }

    MinCoverGenerator_u128 *mcg =
        MinCoverGenerator_new_u128(a.value, b.value, a.max, args.sparsity, args.trimFactor, status, use_range_v2);
    if (!mcg) {
        return NULL;
    }
    mc_mincover_t *mc = MinCoverGenerator_minCover_u128(mcg);
    mc->usedTrimFactor = MinCoverGenerator_usedTrimFactor_u128(mcg);
    MinCoverGenerator_destroy_u128(mcg);
    return mc;
}
#endif // MONGOCRYPT_HAVE_DECIMAL128_SUPPORT
