/**
 * @file        multichannel_rx_cpp.c
 * @brief       BMAPI example: Notification based, highly efficient, multiple channel receiving example.
 * @author      busmust
 * @version     1.0.0.1
 * @copyright   Copyright 2022 by Busmust Tech Co.,Ltd <br>
 *              All rights reserved. Property of Busmust Tech Co.,Ltd.<br>
 *              Restricted rights to use, duplicate or disclose of this code are granted through contract.
 */
#ifdef __GNUC__
#include <unistd.h>
#elif defined(_MSC_VER)
#include <Windows.h>
#define usleep(us) Sleep((us)/1000)
#endif
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "bmapi.h"

#define BMAPI_CAN_MODE BM_CAN_NORMAL_MODE

#define TEST_MSG_RX_TIMEOUT 10000
#define TEST_STARTUP_TIMEOUT 1000 /* Startup and wait for Busmaster to transmit test messages */
#define TEST_CHANNEL_COUNT 8
#define TEST_PATTERN_VERIFY /* Enable this macro to verify rx messages */

static const uint8_t dlc2len[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64 };

uint32_t msgcount[TEST_CHANNEL_COUNT] = { 0 };
uint32_t errcount[TEST_CHANNEL_COUNT] = { 0 };

time_t t0 = 0;
time_t t1 = 0;
time_t t2 = 0;

#ifdef TEST_PATTERN_VERIFY
static bool verify(BM_CanMessageTypeDef* msg)
{
    if (msg->ctrl.rx.DLC >= 8)
    {
        /* Assuming valid test message to be: DLC >= 8, payload[*]=id&0xFF */
        /* e.g. id=0x123, dlc=8, payload=[0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23] */
        uint8_t len = dlc2len[msg->ctrl.rx.DLC];
        uint8_t pattern = (msg->id.SID & 0xFFU);
        for (uint8_t i = 0; i < len; i++)
        {
            if (msg->payload[i] != pattern)
            {
                return 0;
            }
        }

        return 1;
    }

    return 0;
}

static void print_msg(int channel, uint32_t timestamp, const BM_CanMessageTypeDef* msg)
{
    int len = dlc2len[msg->ctrl.rx.DLC];
    printf(
        "\n\n\n\nError: Bad message from channel %d: ts=%u, id=%x, len=%d, payload=\n[",
        channel, timestamp, msg->id.SID, len
    );
    for (int i = 0; i < len; i++)
    {
        printf("%02x", msg->payload[i]);
    }
    printf("]\n\n\n\n");
}
#endif

static void print_stat()
{
    int elapsed = t2 > t0 ? (int)(t2 - t0) : 1;
    printf(
        "\033[1A\033[K\033[1A\033[K\033[1A\033[K"
        "MSG : %09u  %09u  %09u  %09u  %09u  %09u  %09u  %09u\n"
        "ERR : %09u  %09u  %09u  %09u  %09u  %09u  %09u  %09u\n"
        "FPS : %09u  %09u  %09u  %09u  %09u  %09u  %09u  %09u\n",
        msgcount[0], msgcount[1], msgcount[2], msgcount[3], msgcount[4], msgcount[5], msgcount[6], msgcount[7],
        errcount[0], errcount[1], errcount[2], errcount[3], errcount[4], errcount[5], errcount[6], errcount[7],
        msgcount[0] / elapsed, msgcount[1] / elapsed, msgcount[2] / elapsed, msgcount[3] / elapsed, 
        msgcount[4] / elapsed, msgcount[5] / elapsed, msgcount[6] / elapsed, msgcount[7] / elapsed
    );
}

/**
 * @brief BMAPI test program: Transmit or receive messages according to command line arguments:
 * @param[in]  argc    Number of command line arguments
 * @param[in]  argv[0] Path of this program
 * @param[in]  argv[1] Channel: Selected CAN port index
 * @param[in]  argv[2] Mode: only "tx" or "rx" is supported
 * @param[in]  argv[3] nmessages: Number of messages to receive/transmit within a round
 * @param[in]  argv[4] nrounds: Number of rounds to transmit
 * @param[in]  argv[4] ncycles: Round cycle, a delay value in milliseconds between consecutive rounds
 * @return     Program exit code
 */
int main(int argc, char* argv[])
{
    BM_ChannelHandle channels[TEST_CHANNEL_COUNT] = { 0 };
    BM_NotificationHandle notifications[TEST_CHANNEL_COUNT] = { 0 };
    BM_ChannelInfoTypeDef channelinfos[TEST_CHANNEL_COUNT] = { 0 };
    int nchannels = sizeof(channelinfos) / sizeof(channelinfos[0]);
    const char* channelids = argc > 1 ? argv[1] : "0-7";
    int firstchannelid = atoi(channelids);
    int lastchannelid = firstchannelid;
    int notificationIndex = -1;
    int exitcode = 0;
    BM_BitrateTypeDef bitrate;
    BM_StatusTypeDef error = BM_ERROR_OK;
    int i;

    if (channelids)
    {
        const char* lastidstr = strchr(channelids, '-');
        if (lastidstr)
        {
            lastchannelid = atoi(&lastidstr[1]);
        }
    }

    printf(
        "\n"
        "========== BMAPI TEST PROGRAM ==========\n"
        "========= BUSMUST TECH Co.,Ltd. ========\n"
    );
    printf("========= BMAPI VER [%08x] =========\n", BM_GetVersion());

    /* Step1: Initialize BMAPI library before any other operation */
    printf("Initializing BMAPI library ...\n");
    error = BM_Init();
    if (error != BM_ERROR_OK)
    {
        exitcode = 1;
        goto __exit;
    }

    /* Step2: Enumerate connected device channels */
    printf("Enumerating channels ...\n");
    error = BM_Enumerate(channelinfos, &nchannels);
    if (error != BM_ERROR_OK || lastchannelid >= nchannels)
    {
        printf("Input Error: There are %d available Busmust device, port index %d is invalid.\n", nchannels, lastchannelid);
        exitcode = 2;
        goto __exit;
    }

    for (i = 0; i < nchannels; i++)
    {
        printf("[%d] %s\n", i, channelinfos[i].name);
    }
    for (int channelid = firstchannelid; channelid <= lastchannelid; channelid++)
    {
        /* Step3: Open the selected channel and configure baudrate */
        printf("Opening channel %s in RX mode ...\n", channelinfos[channelid].name);
        memset(&bitrate, 0, sizeof(bitrate));
        bitrate.nbitrate = 1000;
        bitrate.dbitrate = 5000;
        bitrate.nsamplepos = 75;
        bitrate.dsamplepos = 75;
        /* BTR is not mandatory, hardware will calculate BTR using bitrate&samplepos automatically */
        error = BM_OpenEx(&channels[channelid], &channelinfos[channelid], BMAPI_CAN_MODE, channelid <= firstchannelid + 1 ? BM_TRESISTOR_120 : BM_TRESISTOR_DISABLED, &bitrate, NULL, 0);
        if (error != BM_ERROR_OK)
        {
            exitcode = 3;
            goto __exit;
        }

        error = BM_GetNotification(channels[channelid], &notifications[channelid]);
        if (error != BM_ERROR_OK)
        {
            exitcode = 4;
            goto __exit;
        }
    }
    printf("Waiting %d milliseconds for TX app to startup ...\n", TEST_STARTUP_TIMEOUT);
    usleep(TEST_STARTUP_TIMEOUT * 1000);

    /* Step4: Listen for notifications in parallel  */
    printf("Channel [%d-%d] RX stats :\n\n\n\n", firstchannelid, lastchannelid);
    while ((notificationIndex = BM_WaitForNotifications(&notifications[firstchannelid], lastchannelid - firstchannelid + 1, TEST_MSG_RX_TIMEOUT)) >= 0)
    {
        int sourcechannelid = firstchannelid + notificationIndex;
        BM_StatusTypeDef error = BM_ERROR_OK;
        BM_CanMessageTypeDef msg;
        uint32_t port;
        uint32_t timestamp;

        /* Note that a notification event might indicate multiple new messages, so keep reading until you cannot get more. */
        while ((error = BM_ReadCanMessage(channels[sourcechannelid], &msg, &port, &timestamp)) == BM_ERROR_OK)
        {
            msgcount[sourcechannelid]++;
#ifdef TEST_PATTERN_VERIFY
            if (!verify(&msg))
            {
                print_msg(sourcechannelid, timestamp, &msg);
                errcount[sourcechannelid]++;
            }
#endif
        }
        /* BM_ERROR_QRCVEMPTY should not be considered as an error, otherwise report the error */
        if (error != BM_ERROR_QRCVEMPTY)
        {
            printf("\nFailed to receive message, error=%d.\n", error);
            exitcode = 5;
            goto __exit;
        }
        if ((t2 = time(NULL)) > t1 + 5)
        {
            /* Print RX stat each second */
            print_stat();
            t1 = t2;
        }
        if (!t0)
        {
            t0 = t2;
        }
    }
    t2 -= TEST_MSG_RX_TIMEOUT / 1000;
    print_stat();
    printf("\nTimeout, stopping app.\n");

    /* Step5: Close the opened channel and release connected hardware */
    for (int channelid = firstchannelid; channelid <= lastchannelid; channelid++)
    {
        printf("Closing channel %s ...\n", channelinfos[channelid].name);
        error = BM_Close(channels[channelid]);
        if (error != BM_ERROR_OK)
        {
            exitcode = 5;
            goto __exit;
        }
    }

__exit:
    if (error != BM_ERROR_OK)
    {
        char buffer[256] = { 0 };
        BM_GetErrorText(error, buffer, sizeof(buffer), 0);
        printf("Error 0X%08X: %s.\n", error, buffer);
    }
    /* Step6: Cleanup library resource */
    BM_UnInit();
    printf("Exit RX app.\n");
    return exitcode;
}

/**
 * End of file
 */
