/*
 * All Rights Reserved (C) 2021-2025
 * Exoligent SARL (Colombes, France)
 * Website: www.exoligent.com
 * Author : Maxime Coroyer <maxime.coroyer@exoligent.com>
 */

#include "sta.h"

int tst_session_start(struct tst_session *sess)
{
    int i, j, var_cnt;
    uint8_t station_number = sess->station_number;
    uint8_t station_bitrate = sess->station_bitrate;
    uint32_t station_tr_ustime = (sess->station_tr_ustime) ? \
        sess->station_tr_ustime : tr_ustime[station_bitrate];
    uint32_t station_ts_ustime = (sess->station_ts_ustime) ? \
        sess->station_ts_ustime : ts_ustime[station_bitrate];
    const struct pwrfip_version *lib_version = pwrfip_version_get();
    uint16_t w_id_base = (station_number == 0) ? 0x3000 : 0x3100;
    uint16_t r_id_base = (station_number == 0) ? 0x3100 : 0x3000;
    uint16_t local_sap = (station_number == 0) ? 0x0C00 : 0x0C01;
    uint16_t remote_sap = (station_number == 0) ? 0x0C01 : 0x0C00;
    struct pwrfip_node_cfg node_cfg;
    struct pwrfip_var_cfg var_cfg;
    struct pwrfip_msg_cfg msg_cfg;
    struct pwrfip_ba_mcycle_cfg mcycle_cfg;
    struct pwrfip_ba_wind_cfg ba_wind_cfg[TST_BA_WIND_COUNT];
    struct pwrfip_ba_request ba_req[TST_BA_PER_REQ_COUNT];

    memset(&node_cfg, 0, sizeof(struct pwrfip_node_cfg));
    memset(&var_cfg, 0, sizeof(struct pwrfip_var_cfg));
    memset(&mcycle_cfg, 0, sizeof(struct pwrfip_ba_mcycle_cfg));
    memset(&ba_wind_cfg, 0, TST_BA_WIND_COUNT * sizeof(struct pwrfip_ba_wind_cfg));

#if defined(__QNX__) || defined(_WIN32) || defined(_WIN64)
    uint64_t start, stop;
#else
    struct timeval start, stop;
#endif

    /* pwrfip - versions */
    _print(app, info, "test: v%s - pwrfip lib: v%d.%d.%d\n", APP_VERSION,
        lib_version->info.major, lib_version->info.minor, lib_version->info.patch);

    _print(station_number, info, "[fip] node configuration\n");
    /* set node config */
    /* general node configuration */
    node_cfg.fip_phy_addr = station_number;
    node_cfg.fip_seg_num = 0;
    node_cfg.fip_frm_type = PWRFIP_FRM_WORLDFIP;
    node_cfg.fip_bitrate = station_bitrate + 1;
    node_cfg.turn_around_ustime = station_tr_ustime;
    node_cfg.silence_ustime = station_ts_ustime;
    node_cfg.enable_bimedium = 1;
    /* msg configuration */
    node_cfg.msg.enable = 1;
    if (node_cfg.msg.enable) {
        node_cfg.msg.rx_fifo_size = 0; /* 0 to set default value (30) */
        /*
         * We ignore messages received on all segments, except segment 0
         * with a limitation access (i.e. only FIP IDs configured with the
         * ability to receive messages will be allowed)
         */
        node_cfg.msg.rx_segment_tab[0] = PWRFIP_MSG_SEG_ACCEPT_LTD;
        for (i = 1; i < 256; ++i)
            node_cfg.msg.rx_segment_tab[i] = PWRFIP_MSG_SEG_IGNORE;

        /* set periodic channel number 1 */
        node_cfg.msg.tx_per_fifo_size[0] = 0; /* default value for tx periodic
                                               * channel number 1:
                                               * tx fifo depth = 24 messages
                                               */
        node_cfg.msg.tx_per_fifo_id[0] = local_sap; /* id attached to per ch 1 */
        /* the other channels are configured to the minimum values */
        node_cfg.msg.tx_aper_fifo_size = 1; /* minimal value (1) */
        for (i = 1; i < PWRFIP_MSG_TX_CH_PER_CNT; ++i) {
            node_cfg.msg.tx_per_fifo_size[i] = 1; /* minimal value (1) */
            node_cfg.msg.tx_per_fifo_id[i] = 0; /* no id attached to other
                                                 * periodic channels */
        }
        node_cfg.msg.tx_max_repeat = 3;
    }
    /* ba configuration (if master active) */
    node_cfg.ba.enable = 1;
    if (node_cfg.ba.enable) {
        /* we calculate startup and election times for BA */
        struct pwrfip_ba_startup_cfg ba_stup_cfg = {
            .mode = PWRFIP_BA_STUP_STANDARD,
            .silence_ustime = ts_ustime[station_bitrate],
            .max_phy_addr = 255,
            .max_prio = 0,
            .my_phy_addr = node_cfg.fip_phy_addr,
            .my_prio = (station_number == 0) ? 0 : 1,
        };
        pwrfip_ba_startup_calculate(&node_cfg.ba.start_ustime,
                &node_cfg.ba.election_ustime, &ba_stup_cfg);
        _print(station_number, info, "[fip] bus arbiter infos\n");
        _print(station_number, info, "   start        : %dus\n",
                node_cfg.ba.start_ustime);
        _print(station_number, info, "   election     : %dus\n",
                node_cfg.ba.election_ustime);
        node_cfg.ba.priority = (station_number == 0) ? 0 /* highest */ : 1;
        node_cfg.ba.msg_req_fifo_size = 0; /* 0 to set default value (60) */
        node_cfg.ba.urgent_var_req_fifo_size = 0; /* 0 to set default value (60) */
        node_cfg.ba.normal_var_req_fifo_size = 0; /* 0 to set default value (60) */
    }
    /* node identification configuration */
    node_cfg.ident.manufacturer_name = "Exoligent\0";
    node_cfg.ident.model_name = "PowerFIP chip\0";
    node_cfg.ident.revision = 0x10; /* v1.0 */
    node_cfg.ident.tag_name = NULL;
    node_cfg.ident.vendor_field = NULL;

    /* protocol extension: extend variable length to 1024B */
    node_cfg.prtcl_ext.enable = 1;
    if (node_cfg.prtcl_ext.enable) {
        node_cfg.prtcl_ext.enable_var_long_form = 1;
    }

    /* hardware access */
    node_cfg.dev = sess->dev; /* attach pci device to node configuration */

    /* app handlers */
    node_cfg.pwrfip_msg_recv_handler = tst_pwrfip_msg_recv_handler;
    node_cfg.pwrfip_msg_send_handler = tst_pwrfip_msg_send_handler;
    node_cfg.pwrfip_urg_req_handler = NULL; /* no urgent aper. var request to treat */
    node_cfg.pwrfip_nor_req_handler = NULL; /* no normal aper. var request to treat */
    node_cfg.pwrfip_ba_state_handler = tst_pwrfip_ba_state_handler;
    node_cfg.pwrfip_ba_sync_handler = tst_pwrfip_ba_sync_handler;
    node_cfg.pwrfip_error_handler = tst_pwrfip_error_handler;
    node_cfg.pwrfip_reset_handler = tst_pwrfip_reset_handler;

    /**
     * User app context
     * note: Useful to retrieve the session context from a pwrfip callback
     */
    node_cfg.user_ctx = (void*) sess;

    _print(station_number, info, "[fip] node init\n");
    _gettime(&start);
    sess->node.n = pwrfip_node_init(&node_cfg);
    if (!sess->node.n) {
       _print(station_number, err, "node initialization failed: %s\n",
            pwrfip_strerror(errno));
        return -1;
    }
    _gettime(&stop);
    update_stats(&start, &stop, &sess->stats.nb_init,
        &sess->stats.cumul_init_duration, &sess->stats.min_init_duration,
        &sess->stats.max_init_duration);

    print_node_infos(sess->node.n);

    /* create aele (fip node context) */
    sess->node.al = pwrfip_aele_create(sess->node.n);
    if (!sess->node.al) {
        _print(station_number, err, "aele creation failed: %s\n",
            pwrfip_strerror(errno));
        goto fail;
    }

    /* => create fip variables */
    var_cnt = (station_number) ? TST_STA1_VAR_COUNT : TST_STA0_VAR_COUNT;
    for (i = 0; i < var_cnt; ++i) {
        struct pwrfip_var_cfg *var_cfg = &tst_var_cfg[station_number][i];
        struct pwrfip_var *var;
        switch (var_cfg->type)
        {
        case PWRFIP_VAR_TYPE_PROD:
            var = pwrfip_var_create(sess->node.al, var_cfg);
            if (!var) {
                _print(station_number, err,
                    "produced variable creation failed: %s\n",
                    pwrfip_strerror(errno));
                goto fail1;
            }
            sess->node.prod_varlist[sess->node.prod_varcnt] = var;
            sess->node.prod_varcnt++;
        break;
        case PWRFIP_VAR_TYPE_CONS:
            var = pwrfip_var_create(sess->node.al, var_cfg);
            if (!var) {
                _print(station_number, err,
                    "consumed variable creation failed: %s\n",
                    pwrfip_strerror(errno));
                goto fail1;
            }
            sess->node.cons_varlist[sess->node.cons_varcnt] = var;
            sess->node.cons_varcnt++;
        break;
        default: break;
        }
    }

    /* => create fip messages */
    /*      -> build produced msglist */
    memset(&msg_cfg, 0, sizeof(msg_cfg));
    msg_cfg.type = PWRFIP_MSG_TYPE_SEND;
    msg_cfg.tx.channel = PWRFIP_MSG_TX_CH_PER_1;
    msg_cfg.tx.ack_mode = PWRFIP_MSG_TX_ACK_MODE_SDN;
    msg_cfg.hdr.src.seg = 0;
    msg_cfg.hdr.src.lsap = local_sap; /* ssap */
    msg_cfg.hdr.dst.seg = 0;
    msg_cfg.hdr.dst.lsap = remote_sap; /* dsap (=target) */
    msg_cfg.pwrfip_msg_handler = NULL;
    sess->node.prod_msglist[0] = pwrfip_msg_create(sess->node.al, &msg_cfg);
    if (!sess->node.prod_msglist[0]) {
        _print(station_number, err,
            "produced message creation failed: %s\n",
            pwrfip_strerror(errno));
        goto fail2;
    }
    /*      -> build consumed msglist */
    memset(&msg_cfg, 0, sizeof(msg_cfg));
    msg_cfg.type = PWRFIP_MSG_TYPE_RECV;
    msg_cfg.hdr.src.seg = 0;
    msg_cfg.hdr.src.lsap = remote_sap; /* ssap */
    msg_cfg.hdr.dst.seg = 0;
    msg_cfg.hdr.dst.lsap = local_sap; /* dsap (=target) */
    msg_cfg.pwrfip_msg_handler = NULL;
    sess->node.cons_msglist[0] = pwrfip_msg_create(sess->node.al, &msg_cfg);
    if (!sess->node.cons_msglist[0]) {
        _print(station_number, err,
            "consumed message creation failed: %s\n",
            pwrfip_strerror(errno));
        goto fail2;
    }

    /* create the bus arbiter macrocycles */
    /* mcycle 1 */
    /* => build a periodic requests list [for periodic mcycle wind] */
    for (i = 0, j = 0; i < TST_VAR_COUNT; ++i) {
        uint16_t id_base = (i < TST_PROD_VAR_COUNT) ? w_id_base : r_id_base;
        /* j reset because of id_base change */
        j = (i == TST_PROD_VAR_COUNT) ? 1 : j + 1;
        ba_req[i].type = PWRFIP_BA_ID_DAT;
        ba_req[i].id = id_base + j;
    }
    /* add id_msg(0x0C00) - periodic msg from sta0 to sta1 */
    ba_req[i].type = PWRFIP_BA_ID_MSG;
    ba_req[i].id = 0x0C00;
    /* add id_msg(0x0C01) - periodic msg from sta1 to sta0 */
    ++i;
    ba_req[i].type = PWRFIP_BA_ID_MSG;
    ba_req[i].id = 0x0C01;
    /* add ba synchro id (0x9003) */
    ++i;
    ba_req[i].type = PWRFIP_BA_ID_DAT;
    ba_req[i].id = 0x9003;

    /* => build the macrocycle window configurations */
    /*    1 - periodic variable/message window */
    ba_wind_cfg[0].type = PWRFIP_BA_WIND_PER;
    ba_wind_cfg[0].per.req_cnt = TST_BA_PER_REQ_COUNT;
    ba_wind_cfg[0].per.req_list = &ba_req[0];
    /*    3 - resync wait window */
    ba_wind_cfg[1].type = PWRFIP_BA_WIND_WAIT;
    ba_wind_cfg[1].wait.end_ustime = TST_BA_MCYCLE_US;
    ba_wind_cfg[1].wait.is_silent = 0; /* fill window with padding frames */
    ba_wind_cfg[1].wait.is_ext_resync = 0; /* internal resynchronization */
    /* => attach window configurations to macrocycle configuration */
    mcycle_cfg.wind_cnt = TST_BA_WIND_COUNT;

    mcycle_cfg.wind_list = &ba_wind_cfg[0];
    sess->node.mc_list[0] = pwrfip_ba_mcycle_create(sess->node.n, &mcycle_cfg);
    if (!sess->node.mc_list[0]) {
        _print(station_number, err, "macrocycle creation failed: %s\n",
            pwrfip_strerror(errno));
        goto fail2;
    }

    /* mcycle 2 */
    {
        ba_req[0].type = PWRFIP_BA_ID_DAT;
        ba_req[0].id = 0x3001;
        ba_req[1].type = PWRFIP_BA_ID_DAT;
        ba_req[1].id = 0x3101;
        ba_wind_cfg[0].type = PWRFIP_BA_WIND_PER;
        ba_wind_cfg[0].per.req_cnt = 2;
        ba_wind_cfg[0].per.req_list = &ba_req[0];
        ba_wind_cfg[1].type = PWRFIP_BA_WIND_WAIT;
        ba_wind_cfg[1].wait.end_ustime = 1000; /* 1ms */
        mcycle_cfg.wind_cnt = TST_BA_WIND_COUNT;
        mcycle_cfg.wind_list = &ba_wind_cfg[0];
        sess->node.mc_list[1] = pwrfip_ba_mcycle_create(sess->node.n, &mcycle_cfg);
    }

    _print(station_number, info, "[fip] node start\n");
    _gettime(&start);
    if (pwrfip_node_start(sess->node.al, &sess->node.mc_list[0], 2)) {
        _print(station_number, err, "node starting failed: %s\n",
            pwrfip_strerror(errno));
        goto fail3;
    }
    _gettime(&stop);
    update_stats(&start, &stop, &sess->stats.nb_start,
        &sess->stats.cumul_start_duration, &sess->stats.min_start_duration,
        &sess->stats.max_start_duration);

    if (node_cfg.ba.enable) {
        _print(station_number, info, "[fip] ba start\n");
        if (pwrfip_ba_start(sess->node.mc_list[0])) {
            _print(station_number, err, "ba macrocycle starting failed: %s\n",
                pwrfip_strerror(errno));
            goto fail3;
        }
    }

    return 0;

fail3: /* ba mcycle deletion. pwrfip_node_exit does the job also */
    for (i = 0; i < TST_BA_MCYCLE_CNT; ++i)
        pwrfip_ba_mcycle_delete(sess->node.mc_list[i]);
fail2: /* msg deletion. pwrfip_node_exit does the job also */
    for (i = 0; i < TST_CONS_MSG_COUNT; ++i)
        pwrfip_msg_delete(sess->node.cons_msglist[i]);

    for (i = 0; i < TST_PROD_MSG_COUNT; ++i)
        pwrfip_msg_delete(sess->node.prod_msglist[i]);
fail1: /* var+aele deletion. pwrfip_node_exit does the job also */
    for (i = 0; i < TST_CONS_VAR_COUNT; ++i)
        pwrfip_var_delete(sess->node.cons_varlist[i]);

    for (i = 0; i < TST_PROD_VAR_COUNT; ++i)
        pwrfip_var_delete(sess->node.prod_varlist[i]);
    pwrfip_aele_delete(sess->node.al);
fail:
    pwrfip_node_exit(sess->node.n);
    return -1;
}

int tst_session_stop(struct tst_session* sess)
{
    uint8_t station_number = sess->station_number;

    /* print node report */
    tst_node_report(sess);

    /* print session stats */
    _print(station_number, info, "** session stats:\n");
    _print(station_number, info,
        "  mcycle_counter: %"PRIu64"\n", sess->stats.nb_sync_cb);

    if (sess->stats.nb_sync_cb) {
        uint64_t avg_duration = ((float) sess->stats.cumul_sync_cb_duration) \
                    / sess->stats.nb_sync_cb;
        _print(station_number, info,
            "    - occurrence period  (us): min=%"PRIu64", max=%"PRIu64"\n",
            sess->stats.min_sync_cb_last,
            sess->stats.max_sync_cb_last);
        _print(station_number, info,
            "    - treatment duration (us): min=%"PRIu64", avg=%"PRIu64", max=%"PRIu64"\n",
            sess->stats.min_sync_cb_duration, avg_duration,
            sess->stats.max_sync_cb_duration);
        _print(station_number, info, "  msg:\n");
        _print(station_number, info, "    - send : ok=%"PRIu64" [warn=%"PRIu64"]\n",
            sess->stats.tx_msg_ok, sess->stats.tx_msg_warn);
        _print(station_number, info, "    - recv : ok=%"PRIu64" [warn=%"PRIu64"]\n",
            sess->stats.rx_msg_ok, sess->stats.rx_msg_warn);
        _print(station_number, info, "  var:\n");
        _print(station_number, info, "    - write: ok=%"PRIu64" [warn=%"PRIu64"]\n",
            sess->stats.w_var_ok, sess->stats.w_var_warn);
        _print(station_number, info,
            "    - read : ok=%"PRIu64" [warn=%"PRIu64"/payload_err=%"PRIu64"]\n",
            sess->stats.r_var_ok, sess->stats.r_var_warn, sess->stats.r_var_pyld_err);
    }

    /* stop the node and delete all resources attached (aele, var, mcycle..) */
    _print(station_number, info, "[fip] node stop\n");
    if (pwrfip_node_exit(sess->node.n)) {
        _print(station_number, err, "node exiting failed: %s\n",
            pwrfip_strerror(errno));
        return -1;
    }
    _print(station_number, info, "[fip] node exit ok\n");
    return 0;
}

int tst_node_report(struct tst_session* sess)
{
    uint8_t station_number = sess->station_number;
    uint16_t fd_status;
    struct pwrfip_node_report report;

    if (pwrfip_node_report_get(sess->node.n, &report)) {
        _print(station_number, err, "node report: %s\n",
            pwrfip_strerror(errno));
        return -1;
    }

// #if defined(_WIN32) || defined(_WIN64)
//    system("cls");
// #else
//    system("clear");
// #endif
    fd_status = report.medium_status.state;
    _print(station_number, info, "** local report:\n");
    _print(station_number, info, "  node_state    : %d (%s)\n",
        report.node_status.state, pwrfip_node_state_str[report.node_status.state]);
    _print(station_number, info, "  node_op       : %d (%s)\n",
        report.node_status.op, pwrfip_node_op_str[report.node_status.op]);
    _print(station_number, info, "  ba_state      : %d (%s)\n",
        report.ba_status.state, pwrfip_ba_state_str[report.ba_status.state]);
    _print(station_number, info, "  ba_window     : %d (%s)\n",
        report.ba_status.window, pwrfip_ba_wind_str[report.ba_status.window]);
    _print(station_number, info, "  medium_state  : 0x%04x\n", fd_status);
    _print(station_number, info, "    [channel 1]\n");
    _print(station_number, info, "      enable    : %s\n",
        (fd_status & PWRFIP_MEDIUM_STATE_CH1_VALID) ? "yes": "no");
    _print(station_number, info, "      tx_err    : %s\n",
        (fd_status & PWRFIP_MEDIUM_STATE_CH1_TX_ERROR) ? "yes": "no");
    _print(station_number, info, "      watchdog  : %s\n",
        (fd_status & PWRFIP_MEDIUM_STATE_CH1_WATCHDOG) ? "yes": "no");
    _print(station_number, info, "    [channel 2]\n");
    _print(station_number, info, "      enable    : %s\n",
        (fd_status & PWRFIP_MEDIUM_STATE_CH2_VALID) ? "yes": "no");
    _print(station_number, info, "      tx_err    : %s\n",
        (fd_status & PWRFIP_MEDIUM_STATE_CH2_TX_ERROR) ? "yes": "no");
    _print(station_number, info, "      watchdog  : %s\n",
        (fd_status & PWRFIP_MEDIUM_STATE_CH2_WATCHDOG) ? "yes": "no");
    _print(station_number, info, "  tx_err        :\n");
    _print(station_number, info, "    ok          : %d\n", report.tx_err.ok);
    _print(station_number, info, "    collision   : %d\n", report.tx_err.collision);
    _print(station_number, info, "    consistency : %d\n", report.tx_err.consistency);
    _print(station_number, info, "    not_fresh   : %d\n", report.tx_err.not_fresh);
    _print(station_number, info, "  rx_err        :\n");
    _print(station_number, info, "    ok          : %d\n", report.rx_err.ok);
    _print(station_number, info, "    pre_mis     : %d\n", report.rx_err.pre_mis);
    _print(station_number, info, "    fsd_mis     : %d\n", report.rx_err.fsd_mis);
    _print(station_number, info, "    fsd_unk     : %d\n", report.rx_err.fsd_unk);
    _print(station_number, info, "    fed_mis     : %d\n", report.rx_err.fed_mis);
    _print(station_number, info, "    crc_bad     : %d\n", report.rx_err.crc_bad);
    _print(station_number, info, "    pdu_bad     : %d\n", report.rx_err.pdu_bad);
    _print(station_number, info, "    len_bad     : %d\n", report.rx_err.len_bad);
    _print(station_number, info, "    not_fresh   : %d\n", report.rx_err.not_fresh);
    _print(station_number, info, "    not_prompt  : %d\n", report.rx_err.not_prompt);
    return 0;
}

void tst_pwrfip_msg_recv_handler(struct pwrfip_node *node,
                struct pwrfip_msg *cons_msg, struct pwrfip_event *event)
{
    const char *err_str = (cons_msg->error < _PWRFIP_MSG_ERR_MAX) ? \
        pwrfip_msg_err_str[cons_msg->error] : "unknown error";
    struct tst_session *sess = (struct tst_session *) node->infos->cfg.user_ctx;
    msg_src src = (sess->station_number == 1) ? 1 : 0;

    /**
     * We check if the message has been correctly received
     */
    if (cons_msg->error) {
        /* error detected: report a warning here */
        _print(src, warn, "RX MSG: %s\n", err_str);
        sess->stats.rx_msg_warn++;
    } else {
        /* message correctly received */
        // _print(src, evt, "RX MSG [-> header: src=0x%08x dst=0x%08x]\n",
        //     cons_msg->hdr.src.addr, cons_msg->hdr.dst.addr);
        // _print(src, evt, "RX MSG: %s\n", cons_msg->buffer);
        sess->stats.rx_msg_ok++;
    }
}

void tst_pwrfip_msg_send_handler(struct pwrfip_node *node,
                struct pwrfip_msg *prod_msg, struct pwrfip_event *event)
{
    const char *err_str = (prod_msg->error < _PWRFIP_MSG_ERR_MAX) ? \
        pwrfip_msg_err_str[prod_msg->error] : "unknown error";
    struct tst_session *sess = (struct tst_session *) node->infos->cfg.user_ctx;
    msg_src src = (sess->station_number == 1) ? 1 : 0;

    /**
     * We check if the message has been correctly sent
     */
    if (prod_msg->error) {
        /* error detected: report a warning here */
        _print(src, warn, "TX MSG: %s\n", err_str);
        sess->stats.tx_msg_warn++;
    } else {
        /* message correctly sent */
        // _print(src, evt, "TX MSG [-> header: src=0x%08x dst=0x%08x]\n",
        //     prod_msg->hdr.src.addr, prod_msg->hdr.dst.addr);
        // _print(src, evt, "TX MSG: %s\n", prod_msg->buffer);
        sess->stats.tx_msg_ok++;
    }
}

void tst_pwrfip_ba_state_handler(struct pwrfip_node *node,
        struct pwrfip_event *event)
{
    struct tst_session *sess = (struct tst_session *) node->infos->cfg.user_ctx;
    msg_src src = (sess->station_number == 1) ? 1 : 0;
    char *ba_state_str;
    switch (event->code) {
    case PWRFIP_EVT_BA_ACTIVITY: ba_state_str = "active"; break;
    case PWRFIP_EVT_BA_STOP_TMO:
    case PWRFIP_EVT_BA_STOP_ERR:
    case PWRFIP_EVT_BA_STOP_USR: ba_state_str = "stopped"; break;
    case PWRFIP_EVT_BA_IDLE:     ba_state_str = "idle"; break;
    default: ba_state_str = "unknown"; break;
    }
    _print(src, evt, "BA_STATE [=> %s]\n", ba_state_str);
}

void tst_pwrfip_error_handler(struct pwrfip_node *node,
        enum pwrfip_error_code code)
{
    struct tst_session *sess = (struct tst_session *) node->infos->cfg.user_ctx;
    msg_src src = (sess->station_number == 1) ? 1 : 0;
    if (running)
        _print(src, err,
            "error_handler: %s (err_code=%d)\n",
            pwrfip_strerror(code),
            code);
    running = 0;
}

void tst_pwrfip_reset_handler(struct pwrfip_node *node)
{
    struct tst_session *sess = (struct tst_session *) node->infos->cfg.user_ctx;
    msg_src src = (sess->station_number == 1) ? 1 : 0;
    if (pwrfip_device_reset(node->infos->cfg.dev)) {
        _print(src, err, "reset failed: %s\n", pwrfip_strerror(errno));
        return;
    }
    _print(src, evt, "reset component (powerfip)\n");
}

void tst_pwrfip_ba_sync_handler(struct pwrfip_node *node,
        struct pwrfip_var *v, struct pwrfip_event *event)
{
    struct tst_session *sess = (struct tst_session *) node->infos->cfg.user_ctx;
    int i, j;
    msg_src src = (sess->station_number == 1) ? 1 : 0;
    unsigned long last_us = 0;
    static int is_first = 1;
#if defined(__QNX__) || defined(_WIN32) || defined(_WIN64)
    uint64_t start, stop;
    static uint64_t start_treatment, stop_treatment, last_treatment = 0;
#else
    struct timeval start, stop;
    static struct timeval start_treatment, stop_treatment,
                last_treatment = { .tv_sec = 0, .tv_usec = 0, };
#endif

    /**
     * We avoid logging the warnings at app startup and shutdown:
     * - at startup: to give the remote node time to write its variables
     *   at least once.
     * - at shutdown: to avoid not significant warns.
     */
    int log_warn = (!running || is_first) ? 0 : 1;

    _gettime(&start_treatment);

    /* get bus arbiter status */
    pwrfip_ba_status_get(sess->node.n, &sess->node.ba_status);

    /* write vars */
    {
        static uint64_t w_counter = 0;
        w_counter++;
        for (i = 0; i < TST_PROD_VAR_COUNT; ++i) {
            struct pwrfip_var *var = sess->node.prod_varlist[i];
            /* update payload value */
            /* for each variable, we increment all payload bytes content
             * of 1 */
            uint8_t w_byte = ((uint8_t) var->id) + ((uint8_t) w_counter);
            memset(var->buffer, w_byte, var->bsz);

            _gettime(&start);
            if (pwrfip_var_write(var)) {
                _print(src, err, "w_var[0x%04x] failed: %s\n", var->id,
                    pwrfip_strerror(errno));
                /* we consider this error as fatal error; so we
                 * stop the test */
                running = 0;
                return;
            }
            if (var->error) {
                const char *err_str = (var->error < _PWRFIP_VAR_ERR_MAX) ? \
                        pwrfip_var_err_str[var->error] : "unknown error";
                if (log_warn) {
                    _print(src, warn, "w_var[0x%04x] state: %s\n",
                        var->id, err_str);
                    sess->stats.w_var_warn++;
                }
                continue;
            }
            sess->stats.w_var_ok++;

            _gettime(&stop);

            sess->stats.bsz_w_var[i] = var->bsz;
            update_stats(&start, &stop, &sess->stats.nb_w_var_by_id[i],
                &sess->stats.cumul_w_var_by_id_duration[i],
                &sess->stats.min_w_var_by_id_duration[i],
                &sess->stats.max_w_var_by_id_duration[i]);

            // printf("w_var[0x%04x]: ", var->id);
            // for (j = 0; j < var->bsz; ++j)
            //     printf("%02x ", var->buffer[j]);
            // printf("\n");
        }
    }

    /* read vars */
    {
        for (i = 0; i < TST_CONS_VAR_COUNT; ++i) {
            struct pwrfip_var *var = sess->node.cons_varlist[i];

            //_usleep(50);
            _gettime(&start);
            if (pwrfip_var_read(var)) {
                _print(src, err, "r_var[0x%04x] failed: %s\n", var->id,
                    pwrfip_strerror(errno));
                /* we consider this error as fatal error; so we
                 * stop the test */
                running = 0;
                return;
            }
            if (var->error) {
                const char *err_str = (var->error < _PWRFIP_VAR_ERR_MAX) ? \
                        pwrfip_var_err_str[var->error] : "unknown error";
                if (log_warn) {
                    /* we print the var_state error, only if the variable
                     * has already been received once */
                        _print(src, warn, "r_var[0x%04x] state: %s\n",
                        var->id, err_str);
                    if (var->error != PWRFIP_VAR_NEVER_RECEIVED) {
                        //_print(src, warn, "r_var[0x%04x] state: %s\n",
                        //var->id, err_str);
                        for (j = 0; j < var->bsz; ++j)
                            printf("%02x ", var->buffer[j]);
                        printf("\n");
                    }
                    sess->stats.r_var_warn++;
                }
                continue;
            }

            sess->stats.r_var_ok++;
            _gettime(&stop);

            sess->stats.bsz_r_var[i] = var->bsz;
            update_stats(&start, &stop, &sess->stats.nb_r_var_by_id[i],
                &sess->stats.cumul_r_var_by_id_duration[i],
                &sess->stats.min_r_var_by_id_duration[i],
                &sess->stats.max_r_var_by_id_duration[i]);

            /**
             * Extra: check consistency of received payload data
             *  - All bytes of the payload should be equals
             */
            {
                int fault = 0;
                uint8_t r_byte;
                /* we check if all bytes are equals */
                uint8_t wait_byte = var->buffer[0];
                for (j = 0; j < var->bsz; ++j) {
                    if (var->buffer[j] != wait_byte) {
                        fault = 1;
                        r_byte = var->buffer[j];
                        break;
                    }
                }
                if (log_warn && fault) {
                    /* if a fault is detected, we print it and
                     * increment the err counter */
                    sess->stats.r_var_pyld_err++;
                    _print(src, warn,
                        "r_var[0x%04x] payload error [r_byte=0x%02x vs "
                        "wait_byte=0x%02x] (total_err=%ld)\n", var->id,
                        r_byte, wait_byte,
                        sess->stats.r_var_pyld_err);
                    printf("r_var[0x%04x]: ", var->id);
                    for (j = 0; j < var->bsz; ++j)
                        printf("%02x ", var->buffer[j]);
                    printf("\n");
                }
            }
        }
    }

    /* write msg */
    {
        struct pwrfip_msg *prod_msg = sess->node.prod_msglist[0];
        static uint16_t bsz = 0;

        bsz = (bsz % 256) + 1;
        prod_msg->bsz = bsz;
        i = bsz - 1;

        /* fill each byte of payload with the 'msg_bsz - 1' value */
        memset(prod_msg->buffer, (uint8_t) i, bsz);

        //_usleep(50);
        _gettime(&start);
        if (pwrfip_msg_send(prod_msg)) {
            _print(src, err,
                "w_msg[header: src=0x%08x dst=0x%08x] failed: %s\n",
                prod_msg->hdr.src.addr, prod_msg->hdr.dst.addr,
                pwrfip_strerror(errno));
            /* we consider this error as fatal error; so we
             * stop the test */
            running = 0;
            return;
        }
        if (prod_msg->error) {
            const char *err_str = (prod_msg->error < _PWRFIP_MSG_ERR_MAX) ? \
                    pwrfip_msg_err_str[prod_msg->error] : "unknown error";
            if (log_warn) {
                _print(src, warn,
                    "w_msg[header: src=0x%08x dst=0x%08x] state: %s\n",
                    prod_msg->hdr.src.addr, prod_msg->hdr.dst.addr,
                    err_str);
            }
        } else {
            /* no error, we record the measured time */
            _gettime(&stop);
            update_stats(&start, &stop, &sess->stats.nb_w_msg[i],
                &sess->stats.cumul_w_msg_duration[i],
                &sess->stats.min_w_msg_duration[i],
                &sess->stats.max_w_msg_duration[i]);
        }
    }

    _gettime(&stop_treatment);
    last_us = (is_first) ? 0 : get_elapsed_us(start_treatment, last_treatment);
    is_first = 0;
    last_treatment = start_treatment;

    /**
     * Update session stats
     */
    /* => min/max interval between two network events (synch. var) */
    if (last_us)
        update_min_max(last_us, &sess->stats.min_sync_cb_last,
            &sess->stats.max_sync_cb_last);
    /* => event treatment duration */
    update_stats(&start_treatment, &stop_treatment, &sess->stats.nb_sync_cb,
        &sess->stats.cumul_sync_cb_duration,
        &sess->stats.min_sync_cb_duration,
        &sess->stats.max_sync_cb_duration);
}

void print_node_infos(struct pwrfip_node *node)
{
    struct tst_session *sess = (struct tst_session *) node->infos->cfg.user_ctx;
    msg_src src = (sess->station_number == 1) ? 1 : 0;
    struct pwrfip_node_infos *inf = node->infos;

    _print(src, info, "[fip] node infos\n");
    _print(src, info, "   cpu_id       : 0x%" PRIx64 "\n",
        inf->cpu_id);
    _print(src, info, "   bss_bsz      : %dB [max_bsz: %dB]\n", inf->bss_bsz,
        inf->bss_max_bsz);
    _print(src, info,
        "   gateware     : v%d.%d.%d [build date: %02d/%02d/%02d]\n",
        inf->gw_version.info.major,
        inf->gw_version.info.minor,
        inf->gw_version.info.patch,
        inf->gw_version.date.info.month,
        inf->gw_version.date.info.day,
        inf->gw_version.date.info.year);
    _print(src, info,
        "   firmware     : v%d.%d.%d [build date: %02d/%02d/%02d]\n",
        inf->fw_version.info.major,
        inf->fw_version.info.minor,
        inf->fw_version.info.patch,
        inf->fw_version.date.info.month,
        inf->fw_version.date.info.day,
        inf->fw_version.date.info.year);
    _print(src, info,
        "   driver       : v%d.%d.%d [build date: %02d/%02d/%02d]\n",
        inf->drv_version.info.major,
        inf->drv_version.info.minor,
        inf->drv_version.info.patch,
        inf->drv_version.date.info.month,
        inf->drv_version.date.info.day,
        inf->drv_version.date.info.year);
    _print(src, info,
        "   library      : v%d.%d.%d [build date: %02d/%02d/%02d]\n",
        inf->lib_version.info.major,
        inf->lib_version.info.minor,
        inf->lib_version.info.patch,
        inf->lib_version.date.info.month,
        inf->lib_version.date.info.day,
        inf->lib_version.date.info.year);
}
