/*
 * SPDX-FileCopyrightText: 2020 Nordic Semiconductor ASA
 * SPDX-FileContributor: 2025 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <string.h>

#if CONFIG_BLE_MESH_BLOB_CLI || CONFIG_BLE_MESH_BLOB_SRV

#include "mesh/utils.h"
#include "mesh_v1.1/mbt/blob.h"
#include "mesh_v1.1/mbt/blob_cli.h"
#include "mesh_v1.1/mbt/blob_srv.h"

#define BT_MESH_BLOB_OP_XFER_GET        BLE_MESH_MODEL_OP_2(0x83, 0x00)
#define BT_MESH_BLOB_OP_XFER_START      BLE_MESH_MODEL_OP_2(0x83, 0x01)
#define BT_MESH_BLOB_OP_XFER_CANCEL     BLE_MESH_MODEL_OP_2(0x83, 0x02)
#define BT_MESH_BLOB_OP_XFER_STATUS     BLE_MESH_MODEL_OP_2(0x83, 0x03)
#define BT_MESH_BLOB_OP_BLOCK_GET       BLE_MESH_MODEL_OP_2(0x83, 0x05)
#define BT_MESH_BLOB_OP_BLOCK_START     BLE_MESH_MODEL_OP_2(0x83, 0x04)
#define BT_MESH_BLOB_OP_CHUNK           BLE_MESH_MODEL_OP_1(0x66)
#define BT_MESH_BLOB_OP_BLOCK_STATUS    BLE_MESH_MODEL_OP_1(0x67)
#define BT_MESH_BLOB_OP_BLOCK_REPORT    BLE_MESH_MODEL_OP_1(0x68)
#define BT_MESH_BLOB_OP_INFO_GET        BLE_MESH_MODEL_OP_2(0x83, 0x06)
#define BT_MESH_BLOB_OP_INFO_STATUS     BLE_MESH_MODEL_OP_2(0x83, 0x07)

#define BLOB_BLOCK_NOT_SET 0xffff

#define BLOB_CHUNK_SDU_OVERHEAD                                                                    \
        (BLE_MESH_MODEL_OP_LEN(BT_MESH_BLOB_OP_CHUNK) + 2 + BLE_MESH_MIC_SHORT)

#define BLOB_CHUNK_SIZE_MAX(sdu_max) ((sdu_max) - BLOB_CHUNK_SDU_OVERHEAD)
#define BLOB_CHUNK_SDU_LEN(chunk_size) (BLOB_CHUNK_SDU_OVERHEAD + (chunk_size))

#if CONFIG_BLE_MESH_LONG_PACKET
#if CONFIG_BLE_MESH_ALIGN_CHUNK_SIZE_TO_MAX_SEGMENT ||                                              \
        CONFIG_BLE_MESH_RX_BLOB_CHUNK_SIZE > BLOB_CHUNK_SIZE_MAX(BLE_MESH_EXT_RX_SDU_MAX)
#define BLOB_RX_CHUNK_SIZE BLOB_CHUNK_SIZE_MAX(BLE_MESH_EXT_RX_SDU_MAX)
#else
#define BLOB_RX_CHUNK_SIZE CONFIG_BLE_MESH_RX_BLOB_CHUNK_SIZE
#endif
#else // CONFIG_BLE_MESH_LONG_PACKET
#if CONFIG_BLE_MESH_ALIGN_CHUNK_SIZE_TO_MAX_SEGMENT ||                                              \
        CONFIG_BLE_MESH_RX_BLOB_CHUNK_SIZE > BLOB_CHUNK_SIZE_MAX(BLE_MESH_RX_SDU_MAX)
#define BLOB_RX_CHUNK_SIZE BLOB_CHUNK_SIZE_MAX(BLE_MESH_RX_SDU_MAX)
#else
#define BLOB_RX_CHUNK_SIZE CONFIG_BLE_MESH_RX_BLOB_CHUNK_SIZE
#endif
#endif

#if CONFIG_BLE_MESH_LONG_PACKET
#if CONFIG_BLE_MESH_ALIGN_CHUNK_SIZE_TO_MAX_SEGMENT ||                                              \
        CONFIG_BLE_MESH_TX_BLOB_CHUNK_SIZE > BLOB_CHUNK_SIZE_MAX(BLE_MESH_EXT_TX_SDU_MAX)
#define BLOB_TX_CHUNK_SIZE BLOB_CHUNK_SIZE_MAX(BLE_MESH_EXT_TX_SDU_MAX)
#else
#define BLOB_TX_CHUNK_SIZE CONFIG_BLE_MESH_TX_BLOB_CHUNK_SIZE
#endif
#else // CONFIG_BLE_MESH_LONG_PACKET
#if CONFIG_BLE_MESH_ALIGN_CHUNK_SIZE_TO_MAX_SEGMENT ||                                              \
        CONFIG_BLE_MESH_TX_BLOB_CHUNK_SIZE > BLOB_CHUNK_SIZE_MAX(BLE_MESH_TX_SDU_MAX)
#define BLOB_TX_CHUNK_SIZE BLOB_CHUNK_SIZE_MAX(BLE_MESH_TX_SDU_MAX)
#else
#define BLOB_TX_CHUNK_SIZE CONFIG_BLE_MESH_TX_BLOB_CHUNK_SIZE
#endif
#endif // CONFIG_BLE_MESH_LONG_PACKET

/* Utility macros for calculating log2 of a number at compile time.
 * Used to determine the log2 representation of the block size, which
 * is configured as a raw number, but encoded as log2.
 *
 * The macros expand to a series of ternary expressions, effectively
 * searching through power of twos until a match is found.
 * According to MshMBTv1.0, the block size cannot be larger than 2^20,
 * so we'll stop the search at 20.
 */
#define _BLOB_LOG_2_CEIL(l, x) ((x) <= (1U << l)) ? l :
#define _BLOB_LOG_2_FLOOR(l, x) ((x) < (1U << (l + 1))) ? l :

#define BLOB_BLOCK_SIZE_LOG_CEIL(x) (LISTIFY(20, _BLOB_LOG_2_CEIL, (), x) 20)
#define BLOB_BLOCK_SIZE_LOG_FLOOR(x) (LISTIFY(20, _BLOB_LOG_2_FLOOR, (), x) 20)

/* Log2 representation of the minimum block size */
#define BLOB_BLOCK_SIZE_LOG_MIN BLOB_BLOCK_SIZE_LOG_CEIL(CONFIG_BLE_MESH_BLOB_BLOCK_SIZE_MIN)
/* Log2 representation of the maximum block size */
#define BLOB_BLOCK_SIZE_LOG_MAX BLOB_BLOCK_SIZE_LOG_FLOOR(CONFIG_BLE_MESH_BLOB_BLOCK_SIZE_MAX)

#if defined(CONFIG_BLE_MESH_BLOB_SRV)
#define BLOB_BLOCK_REPORT_STATUS_MSG_MAXLEN (                                                  \
                                        MAX(sizeof(((struct bt_mesh_blob_block *)0)->missing), \
                                            CONFIG_BLE_MESH_BLOB_SRV_PULL_REQ_COUNT * 3))
#define BLOB_BLOCK_STATUS_MSG_MAXLEN (5 +                                                      \
                                      MAX(sizeof(((struct bt_mesh_blob_block *)0)->missing),   \
                                          CONFIG_BLE_MESH_BLOB_SRV_PULL_REQ_COUNT * 3))
#else
#define BLOB_BLOCK_REPORT_STATUS_MSG_MAXLEN sizeof(((struct bt_mesh_blob_srv *)0)->block.missing)
#define BLOB_BLOCK_STATUS_MSG_MAXLEN (5 + sizeof(((struct bt_mesh_blob_srv *)0)->block.missing))
#endif

#define BLOB_XFER_STATUS_MSG_MAXLEN (17 + sizeof(((struct bt_mesh_blob_srv *)0)->state.blocks))

enum bt_mesh_blob_chunks_missing {
    BT_MESH_BLOB_CHUNKS_MISSING_ALL,
    BT_MESH_BLOB_CHUNKS_MISSING_NONE,
    BT_MESH_BLOB_CHUNKS_MISSING_SOME,
    BT_MESH_BLOB_CHUNKS_MISSING_ENCODED,
};

static inline size_t blob_block_size(size_t xfer_size, uint8_t block_size_log,
                                     uint32_t idx)
{
    if (((idx + 1U) << block_size_log) <= xfer_size) {
        return (1U << block_size_log);
    }

    return xfer_size & BIT_MASK(block_size_log);
}

static inline void blob_chunk_missing_set(uint8_t *missing_chunks,
                                          int idx, bool missing)
{
    WRITE_BIT(missing_chunks[idx / 8], idx % 8, missing);
}

static inline bool
blob_chunk_missing_get(const uint8_t *missing_chunks, int idx)
{
    return !!(missing_chunks[idx / 8] & BIT(idx % 8));
}

static inline void blob_chunk_missing_set_all(struct bt_mesh_blob_block *block)
{
    size_t bytes = block->chunk_count / 8;

    memset(block->missing, 0xff, bytes);
    if (block->chunk_count % 8) {
        block->missing[bytes] = BIT_MASK(block->chunk_count % 8);
    }
}

static inline void blob_chunk_missing_set_none(struct bt_mesh_blob_block *block)
{
    memset(block->missing, 0, sizeof(block->missing));
}

#if CONFIG_BLE_MESH_BLOB_CLI
/** @brief Perform a message broadcast to all BLOB Transfer Client Target nodes.
 *
 *  Will send to a group or each Target node individually, repeating until
 *  all Target nodes have responded or the retry time has run out.
 *
 *  @param cli BLOB Transfer Client instance
 *  @param ctx Broadcast context
 */
void blob_cli_broadcast(struct bt_mesh_blob_cli *cli,
                        const struct blob_cli_broadcast_ctx *ctx);

/** @brief Register that a Target node responded to a broadcast.
 *
 *  @param cli    BLOB Transfer Client instance
 *  @param target Target node that responded.
 */
void blob_cli_broadcast_rsp(struct bt_mesh_blob_cli *cli,
                            struct bt_mesh_blob_target *target);

/** @brief Notify the BLOB Transfer Client that the requested transmission is complete.
 *
 *  Should be called once for each call to the @ref blob_cli_broadcast_ctx.send
 *  callback.
 *
 *  @param cli BLOB Transfer Client instance.
 */
void blob_cli_broadcast_tx_complete(struct bt_mesh_blob_cli *cli);

/** @brief Aborts any ongoing BLOB Transfer Client operations.
 *
 * @param cli BLOB Transfer Client instance.
 */
void blob_cli_broadcast_abort(struct bt_mesh_blob_cli *cli);
#endif /* CONFIG_BLE_MESH_BLOB_CLI */
#endif /* CONFIG_BLE_MESH_BLOB_CLI || CONFIG_BLE_MESH_BLOB_SRV */
