/**
 * @file hw_replay_demo.cpp
 * @brief BMAPI Example: Hardware-based message replay functionality
 * @author busmust
 * @version 1.0.0.1
 * @copyright Copyright 2020 by Busmust Tech Co., Ltd.
 */

#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "bmapi.h"
#include "bm_usb_private_def.h"
#include "osal.h"

#define BMAPI_MAX_CHANNEL_COUNT 32
#define MAX_TX_MSG_COUNT        400
#define TX_MSG_DLC              8
#define RX_TIMEOUT              1000000
#define TX_TIMEOUT              5000
#define BMAPI_CAN_MODE          BM_CAN_EXTERNAL_LOOPBACK_MODE
#define BITRATE_NOMINAL         500
#define BITRATE_DATA            2000
#define SAMPLE_POS_NOMINAL      70
#define SAMPLE_POS_DATA         70
#define ENABLE_OFFLINE_REPLAY    0

/**
 * @brief Prepares an array of CAN FD messages for transmission
 *
 * @param msgs Pointer to array of BM_DataTypeDef structures to populate
 * @param nmsg Number of messages to prepare
 */
void prepare_tx_msg(BM_DataTypeDef* msgs, int nmsg)
{
    // Validate input parameters to avoid null pointer dereference or invalid count
    if (msgs == NULL || nmsg <= 0)
    {
        return;
    }

    // Base parameters for message generation (configurable as needed)
    const uint32_t base_id = 0x100;       // Base CAN ID (messages increment from this value)
    const uint8_t data_len = TX_MSG_DLC;  // Data length (8 bytes, standard for CAN FD data frames)
    uint8_t base_data[64] = { 0 };

    // Loop to populate each message in the array
    for (int i = 0; i < nmsg; i++)
    {
        // Calculate unique ID for current message (increment by 1 per message)
        uint32_t current_id = base_id + i;
        memcpy(&base_data[0], &i, sizeof(current_id));
        msgs[i].timestamp = i * 10000;
        BM_INIT_CAN_FD_DATA(
            msgs[i],               // Target message in the array
            current_id,            // Unique CAN ID for this message
            data_len,              // Data length (fixed to 8 bytes here)
            0,                     // IDE: 0 = Standard Frame (11-bit ID), 1 = Extended Frame (29-bit)
            1,                     // FDF: 1 = Enable CAN FD mode, 0 = Classic CAN mode
            1,                     // BRS: 1 = Use Bit Rate Switch (data phase uses FD bitrate), 0 = No switch
            0,                     // RTR: 0 = Data Frame (carries data), 1 = Remote Request Frame (no data)
            0,                     // ESI: 0 = Error Active state, 1 = Error Passive state
            base_data              // Data payload for the message
        );
    }
}


/**
 * @brief Main function demonstrating BMAPI hardware replay functionality
 * This program demonstrates: Hardware-based message replay functionality
 * @param[in]  argc    Number of command line arguments
 * @param[in]  argv    Array of command line arguments
 * @return     Program exit code (0 for success, non-zero for errors)
 */

int main(int argc, char* argv[])
{
    BM_ReplayConfigTypeDef replay = { 0 };
    BM_NotificationHandle notification = NULL;
    BM_ChannelInfoTypeDef channelinfos[BMAPI_MAX_CHANNEL_COUNT];
    BM_ChannelHandle channel = NULL;
    /* Prepare all message contents for the transmit buffer in advance */
    BM_DataTypeDef msgs[MAX_TX_MSG_COUNT] = { 0 };
    uint32_t timestamps[MAX_TX_MSG_COUNT] = { 0 };
    uint32_t nmsg = 0;
    int nchannels = sizeof(channelinfos) / sizeof(channelinfos[0]);
    int channelid = 0;
    int exitcode = 0;
    BM_BitrateTypeDef bitrate;
    BM_StatusTypeDef error = BM_ERROR_OK;
    int i;
    int nreadmsg = 0;
    int count = 0;
    printf(
        "\n"
        "===========    BMAPI HW REPLAY PROGRAM    ==========\n"
        "===========     BUSMUST TECH Co.,Ltd.     ==========\n"
        __DATE__ " " __TIME__ "\n"
    );
    /* Step 1: Initialize BMAPI library before any other operation */
    printf("Initializing BMAPI library ...\n");
    error = BM_Init();
    if (error != BM_ERROR_OK)
    {
        exitcode = 1;
        goto __exit;
    }
    /* Step 2: Enumerate connected device channels */
    printf("Enumerating channels ...\n");
    error = BM_EnumerateByCap(channelinfos, &nchannels, BM_CAN_CAP);
    if (error != BM_ERROR_OK)
    {
        printf("Operation error occurred\n");
        exitcode = 2;
        goto __exit;
    }

    if (channelid >= nchannels)
    {
        printf("There are %d available bus devices, port index %d is invalid.\n", channelid, nchannels);
        exitcode = 2;
        goto __exit;
    }

    for (i = 0; i < nchannels; i++)
    {
        printf("[%d] %s\n", i, channelinfos[i].name);
    }

    /* Step 3: Open the selected channel and configure baudrate */
    printf("Opening channel %s (%d.%d.%d.%d) in normal mode ...\n",
        channelinfos[channelid].name,
        channelinfos[channelid].version[0],
        channelinfos[channelid].version[1],
        channelinfos[channelid].version[2],
        channelinfos[channelid].version[3]
    );
    memset(&bitrate, 0, sizeof(bitrate));
    bitrate.nbitrate = BITRATE_NOMINAL;      
    bitrate.dbitrate = BITRATE_DATA;         
    bitrate.nsamplepos = SAMPLE_POS_NOMINAL; 
    bitrate.dsamplepos = SAMPLE_POS_DATA;    
    /* BTR is not mandatory, hardware will calculate BTR using bitrate&samplepos automatically */
    error = BM_OpenEx(&channel, &channelinfos[channelid], BMAPI_CAN_MODE, BM_TRESISTOR_120, &bitrate, NULL, 0);
    if (error != BM_ERROR_OK)
    {
        exitcode = 3;
        goto __exit;
    }
    // Get notification handle for the channel
    printf("Getting channel notification handle ...\n");
    error = BM_GetNotification(channel, &notification);
    if (error != BM_ERROR_OK)
    {
        exitcode = 4;
        goto __exit;
    }

    BM_DeviceHandle device;
    BM_GetDevice(channel, &device);
    // Set buffer type for replay functionality
    /* Configure and start fully automatic sequence playback */
    replay.version = 1;
    if (ENABLE_OFFLINE_REPLAY)
    {
        strcpy_s(replay.path.format, sizeof(replay.path.format), "0000.BBD");
    }
    else
    {
        strcpy_s(replay.path.format, sizeof(replay.path.format), "<RAMBUF>");
    }
    /* Step 4: Initialize replay mode to disabled */
    replay.mode = BM_STORAGE_DISABLED;
    BM_SetReplay(device, &replay);

    /* Step 5: Prepare transmit messages */
    nmsg = MAX_TX_MSG_COUNT;
    printf("Preparing %d transmit messages ...\n", nmsg);
    prepare_tx_msg(msgs, nmsg);

    /* Step 6: Configure buffer for replay functionality */ 
    if (ENABLE_OFFLINE_REPLAY)
    {
        BM_SetBuffer(device, BM_WRITE_BUFFER, BM_REPLAYFILE_BUFFER);
    }
    else
    {
        BM_SetBuffer(device, BM_WRITE_BUFFER, BM_REPLAYQ_BUFFER);
    }


    /* Step 7: Download messages to hardware buffer */
    BM_WriteMultiple(channel, msgs, &nmsg, TX_TIMEOUT, timestamps);


    /* Step 7: Restore default buffer configuration */
    BM_SetBuffer(device, BM_WRITE_BUFFER, BM_DEFAULT_BUFFER);

    /* Step 8: Configure and start automatic sequence playback */
    replay.version = 1;
    replay.mode = BM_STORAGE_ALWAYS_ON; /* Start playing sequence immediately after BM_SetReplay call */
    replay.channels = 0xFFFFU; /* Play messages from any channel in the sequence */
    replay.direction = 0x3U; /* Play messages in any direction from the sequence */
    replay.cyclic = 0; /* Set to 1 to enable cyclic playback of this sequence, 0 for single playback */
    BM_SetReplay(device, &replay);

    // Save configuration if offline replay is enabled
    if (ENABLE_OFFLINE_REPLAY)
    {
        BM_SaveConfig(channel,
            BM_CAN_MODE |
            BM_CAN_BITRATE |
            BM_CAN_TERMINAL_RESISTOR |
            BM_CAN_REPLAY_CONFIG
        );
    }
    /* Automatic periodic transmission of messages has started at this point */
    Sleep(1000);
    /* Step 9: Set mode to disabled to stop automatic sequence playback */
    replay.mode = BM_STORAGE_DISABLED;
    BM_SetReplay(device, &replay);

    printf("Closing channel ...\n");
    error = BM_Close(channel);
    if (error != BM_ERROR_OK)
    {
        exitcode = 5;
        goto __exit;
    }

__exit:
    // Print error information if any occurred
    if (error != BM_ERROR_OK)
    {
        char buffer[256] = { 0 };
        BM_GetErrorText(error, buffer, sizeof(buffer), 0);
        printf("Error 0X%08X: %s.\n", error, buffer);
    }

    /* Step 10: Cleanup library resources */
    BM_UnInit();
    printf("Exit app.\n");
    return exitcode;
}
