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

// mc-range-mincover-generator.template.h is meant to be included in another
// source file.

#if !(defined(UINT_T) && defined(UINT_C) && defined(UINT_FMT_S) && defined(DECORATE_NAME))
#ifdef __INTELLISENSE__
#define UINT_T uint32_t
#define UINT_C UINT32_C
#define UINT_FMT_S PRIu32
#define DECORATE_NAME(Name) Name##_u32
#else
#error All of UINT_T, UINT_C, UINT_FMT_S, UINT_FMT_ARG, and DECORATE_NAME must be defined before #including this file
#endif
#endif

#define BITS (sizeof(UINT_T) * CHAR_BIT)

#define ZERO UINT_C(0)

// Default for UINT_FMT_ARG
#ifndef UINT_FMT_ARG
#define UINT_FMT_ARG(X) X
#endif

// Default comparison
#ifndef UINT_LESSTHAN
#define UINT_LESSTHAN(A, B) ((A) < (B))
#endif

#ifndef MC_UINT_MAX
#define MC_UINT_MAX ~(UINT_C(0))
#endif

// Default addition
#ifndef UINT_ADD
#define UINT_ADD(A, B) ((A) + (B))
#endif
#ifndef UINT_SUB
#define UINT_SUB(A, B) ((A) - (B))
#endif

// Default lshift (also handles negatives as right-shift)
#ifndef UINT_LSHIFT
static inline UINT_T DECORATE_NAME(_mc_default_lshift)(UINT_T lhs, int off) {
    if (off < 0) {
        return lhs >> -off;
    } else {
        return lhs << off;
    }
}

#define UINT_LSHIFT DECORATE_NAME(_mc_default_lshift)
#endif

#ifndef UINT_BITOR
#define UINT_BITOR(A, B) ((A) | (B))
#endif

static inline int DECORATE_NAME(_mc_compare)(UINT_T lhs, UINT_T rhs) {
    if (UINT_LESSTHAN(lhs, rhs)) {
        return -1;
    } else if (UINT_LESSTHAN(rhs, lhs)) {
        return 1;
    } else {
        return 0;
    }
}

#define UINT_COMPARE DECORATE_NAME(_mc_compare)

// MinCoverGenerator models the MinCoverGenerator type added in
// SERVER-68600.
typedef struct {
    UINT_T _rangeMin;
    UINT_T _rangeMax;
    size_t _sparsity;
    int32_t _trimFactor;
    // _maxlen is the maximum bit length of edges in the mincover.
    size_t _maxlen;
} DECORATE_NAME(MinCoverGenerator);

static inline DECORATE_NAME(MinCoverGenerator)
    * DECORATE_NAME(MinCoverGenerator_new)(UINT_T rangeMin,
                                           UINT_T rangeMax,
                                           UINT_T max,
                                           size_t sparsity,
                                           mc_optional_int32_t opt_trimFactor,
                                           mongocrypt_status_t *status) {
    BSON_ASSERT_PARAM(status);

    if (UINT_COMPARE(rangeMin, rangeMax) > 0) {
        CLIENT_ERR("Range min (%" UINT_FMT_S ") must be less than or equal to range max (%" UINT_FMT_S
                   ") for range search",
                   UINT_FMT_ARG(rangeMin),
                   UINT_FMT_ARG(rangeMax));
        return NULL;
    }
    if (UINT_COMPARE(rangeMax, max) > 0) {
        CLIENT_ERR("Range max (%" UINT_FMT_S ") must be less than or equal to max (%" UINT_FMT_S ") for range search",
                   UINT_FMT_ARG(rangeMax),
                   UINT_FMT_ARG(max));
        return NULL;
    }

    if (sparsity == 0) {
        CLIENT_ERR("Sparsity must be > 0");
        return NULL;
    }
    size_t maxlen = (size_t)BITS - DECORATE_NAME(mc_count_leading_zeros)(max);
    int32_t trimFactor = trimFactorDefault(maxlen, opt_trimFactor);
    if (trimFactor != 0 && mc_cmp_greater_equal_su(trimFactor, maxlen)) {
        CLIENT_ERR("Trim factor must be less than the number of bits (%zu) used to represent an element of the domain, "
                   "but got %" PRId32,
                   maxlen,
                   trimFactor);
        return NULL;
    }
    if (trimFactor < 0) {
        CLIENT_ERR("Trim factor must be >= 0, but got (%" PRId32 ")", trimFactor);
        return NULL;
    }
    DECORATE_NAME(MinCoverGenerator) *mcg = bson_malloc0(sizeof(DECORATE_NAME(MinCoverGenerator)));
    mcg->_rangeMin = rangeMin;
    mcg->_rangeMax = rangeMax;
    mcg->_maxlen = (size_t)BITS - DECORATE_NAME(mc_count_leading_zeros)(max);
    mcg->_sparsity = sparsity;
    mcg->_trimFactor = trimFactor;
    return mcg;
}

static inline void DECORATE_NAME(MinCoverGenerator_destroy)(DECORATE_NAME(MinCoverGenerator) * mcg) {
    bson_free(mcg);
}

// applyMask applies a mask of 1 bits starting from the right.
// Bits 0 to bit-1 are replaced with 1. Other bits are left as-is.
static inline UINT_T DECORATE_NAME(applyMask)(UINT_T value, size_t maskedBits) {
    const UINT_T ones = MC_UINT_MAX;

    BSON_ASSERT(maskedBits <= (size_t)BITS);
    BSON_ASSERT(maskedBits >= 0);

    if (maskedBits == 0) {
        return value;
    }

    const size_t shift = ((size_t)BITS - maskedBits);
    const UINT_T mask = UINT_LSHIFT(ones, -(int)shift);
    return UINT_BITOR(value, mask);
}

static inline bool DECORATE_NAME(MinCoverGenerator_isLevelStored)(DECORATE_NAME(MinCoverGenerator) * mcg,
                                                                  size_t maskedBits) {
    BSON_ASSERT_PARAM(mcg);
    size_t level = mcg->_maxlen - maskedBits;
    BSON_ASSERT(mc_in_range_size_t_signed(mcg->_trimFactor));
    size_t trimFactor_sz = (size_t)mcg->_trimFactor;
    return 0 == maskedBits || (level >= trimFactor_sz && 0 == (level % mcg->_sparsity));
}

static char *
DECORATE_NAME(MinCoverGenerator_toString)(DECORATE_NAME(MinCoverGenerator) * mcg, UINT_T start, size_t maskedBits) {
    BSON_ASSERT_PARAM(mcg);
    BSON_ASSERT(maskedBits <= mcg->_maxlen);
    BSON_ASSERT(maskedBits <= (size_t)BITS);
    BSON_ASSERT(maskedBits >= 0);

    if (maskedBits == mcg->_maxlen) {
        return bson_strdup("root");
    }

    UINT_T shifted = UINT_LSHIFT(start, -(int)maskedBits);
    mc_bitstring valueBin = DECORATE_NAME(mc_convert_to_bitstring)(shifted);
    char *ret = bson_strndup(valueBin.str + ((size_t)BITS - mcg->_maxlen + maskedBits), mcg->_maxlen + maskedBits);
    return ret;
}

static inline void DECORATE_NAME(MinCoverGenerator_minCoverRec)(DECORATE_NAME(MinCoverGenerator) * mcg,
                                                                mc_array_t *c,
                                                                UINT_T blockStart,
                                                                size_t maskedBits) {
    BSON_ASSERT_PARAM(mcg);
    BSON_ASSERT_PARAM(c);
    const UINT_T blockEnd = DECORATE_NAME(applyMask)(blockStart, maskedBits);

    if (UINT_COMPARE(blockEnd, mcg->_rangeMin) < 0 || UINT_COMPARE(blockStart, mcg->_rangeMax) > 0) {
        return;
    }

    if (UINT_COMPARE(blockStart, mcg->_rangeMin) >= 0 && UINT_COMPARE(blockEnd, mcg->_rangeMax) <= 0
        && DECORATE_NAME(MinCoverGenerator_isLevelStored)(mcg, maskedBits)) {
        char *edge = DECORATE_NAME(MinCoverGenerator_toString)(mcg, blockStart, maskedBits);
        _mc_array_append_val(c, edge);
        return;
    }

    BSON_ASSERT(maskedBits > 0);

    const size_t newBits = maskedBits - 1u;
    DECORATE_NAME(MinCoverGenerator_minCoverRec)(mcg, c, blockStart, newBits);
    DECORATE_NAME(MinCoverGenerator_minCoverRec)
    (mcg, c, UINT_BITOR(blockStart, UINT_LSHIFT(UINT_C(1), (int)newBits)), newBits);
}

static inline mc_mincover_t *DECORATE_NAME(MinCoverGenerator_minCover)(DECORATE_NAME(MinCoverGenerator) * mcg) {
    BSON_ASSERT_PARAM(mcg);
    mc_mincover_t *mc = mc_mincover_new();
    DECORATE_NAME(MinCoverGenerator_minCoverRec)
    (mcg, &mc->mincover, ZERO, mcg->_maxlen);
    return mc;
}

static inline int32_t DECORATE_NAME(MinCoverGenerator_usedTrimFactor)(DECORATE_NAME(MinCoverGenerator) * mcg) {
    BSON_ASSERT_PARAM(mcg);
    return mcg->_trimFactor;
}

// adjustBounds increments *lowerBound if includeLowerBound is false and
// decrements *upperBound if includeUpperBound is false.
// lowerBound, min, upperBound, and max are expected to come from the result
// of mc_getTypeInfo.
static bool DECORATE_NAME(adjustBounds)(UINT_T *lowerBound,
                                        bool includeLowerBound,
                                        UINT_T min,
                                        UINT_T *upperBound,
                                        bool includeUpperBound,
                                        UINT_T max,
                                        mongocrypt_status_t *status) {
    BSON_ASSERT_PARAM(lowerBound);
    BSON_ASSERT_PARAM(upperBound);

    if (!includeLowerBound) {
        if (UINT_COMPARE(*lowerBound, max) >= 0) {
            CLIENT_ERR("Lower bound (%" UINT_FMT_S ") must be less than the range maximum (%" UINT_FMT_S
                       ") if lower bound is excluded from range.",
                       UINT_FMT_ARG(*lowerBound),
                       UINT_FMT_ARG(max));
            return false;
        }
        *lowerBound = UINT_ADD(*lowerBound, UINT_C(1));
    }
    if (!includeUpperBound) {
        if (UINT_COMPARE(*upperBound, min) <= 0) {
            CLIENT_ERR("Upper bound (%" UINT_FMT_S ") must be greater than the range minimum (%" UINT_FMT_S
                       ") if upper bound is excluded from range.",
                       UINT_FMT_ARG(*upperBound),
                       UINT_FMT_ARG(min));
            return false;
        }
        *upperBound = UINT_SUB(*upperBound, UINT_C(1));
    }
    return true;
}

#undef UINT_T
#undef UINT_C
#undef UINT_FMT_S
#undef UINT_FMT_ARG
#undef DECORATE_NAME
#undef BITS
#undef UINT_COMPARE
#undef UINT_ADD
#undef UINT_SUB
#undef UINT_LSHIFT
#undef UINT_BITOR
#undef MC_UINT_MAX
#undef ZERO
#undef UINT_LESSTHAN
